In September 2006, a Debian maintainer tried to make a memory-checker warning go away, and in doing so quietly reduced the number of distinct SSH keys his users’ machines could generate from astronomically many to 32,767. Not thousands of times fewer. Down to a number you can list in a text file. For the next twenty months, a fresh 2048-bit RSA key generated on Debian or Ubuntu was one of a few tens of thousands of possibilities — and anyone could compute the whole set in advance.

It got a CVE, CVE-2008-0166, and a Debian advisory, DSA-1571, when Debian’s Luciano Bello found it in May 2008. But the number is what people remember. Thirty-two thousand seven hundred and sixty-seven keys per architecture, key type and size, for nearly two years of the internet’s most security-conscious operating systems. The story of how two identical-looking lines of C did that is the best cautionary tale I know about the gap between “code that looks wrong” and “code that is load-bearing.”

The randomness came from uninitialized memory, on purpose

To understand the bug you have to swallow something uncomfortable first: OpenSSL’s random number generator deliberately read from uninitialized memory.

A cryptographic PRNG needs a pool of unpredictable bits to seed itself. OpenSSL gathered entropy from the usual places, but it also, as an extra dribble, mixed in the contents of an uninitialized buffer — memory it had allocated but never written. The values in there are whatever the last owner left behind: garbage, but garbage that varies. It contributed almost nothing to the real entropy, and it was arguably a bad idea, but it was intentional.

The problem is that reading uninitialized memory is exactly what tools like Valgrind and Purify exist to scream about. To a memory checker, “use of uninitialised value” is a red flag for a whole class of real bugs, and here it fired on OpenSSL every time anything touched the RNG — a constant, noisy false alarm drowning out warnings the maintainer actually cared about.

So the maintainer went looking for the lines causing the noise.

Two identical lines, one of them holding the whole thing up

In md_rand.c there were two calls that looked exactly the same:

MD_Update(&m, buf, j);

Same function, same arguments, character for character. One of them lived in the routine that pulls random bytes out of the pool, and it was mixing in that uninitialized buffer — the harmless-to-remove one. The other lived in ssleay_rand_add, the routine whose entire job is to stir new entropy into the pool: every byte OpenSSL collected from the system, from process state, from the caller, went through that line.

They were indistinguishable by sight. And the maintainer, reasonably unsure whether crypto code that looked like it was reading garbage could be safely changed, did the responsible thing: he asked upstream. In a 2006 thread on the openssl-dev mailing list titled “Random number generator, uninitialised data and valgrind” he pointed at the lines and asked whether removing them was okay. An OpenSSL developer, Ulf Möller, replied: “If it helps with debugging, I’m in favor of removing them.”

That is the whole hinge of the disaster. The maintainer read it as a green light. But openssl-dev was a busy public list, not the security team; the reply was a quick agreement to a question about a snippet, from someone who almost certainly did not realize that one of those two lines was the sole path by which real entropy reached the pool. Nobody in the exchange was looking at both call sites and their surrounding functions at once. A one-line approval to a decontextualized question stood in for a review that never happened.

Both lines came out. One removal was nearly a no-op. The other severed the connection between all that carefully gathered entropy and the generator that was supposed to use it.

What was left was the process ID

With ssleay_rand_add gutted, the entropy OpenSSL kept collecting had nowhere to go. It was still gathered; it just wasn’t mixed in anymore. The one variable input that still reached the seed was the current process ID.

On Linux, the default maximum PID is 32,768. So the seed space for the entire PRNG — for ssh-keygen, for the OpenSSL command that builds your TLS keys, for OpenVPN, for anything that asked Debian’s OpenSSL for randomness — collapsed to at most 32,767 values for a given architecture, key type, and key size. Generate an RSA key, a DSA key, an SSH host key: it was one of a few tens of thousands, decided almost entirely by which PID ssh-keygen happened to get.

This is the part that turns a bug into a catastrophe. A weak key you might guess with enough effort is a risk. A key drawn from a set of 32,767 that an attacker can enumerate completely, ahead of time, is not a key at all. It’s a padlock whose every possible combination fits on an index card.

You didn’t need to break the math — you needed a list

Within days of the disclosure, HD Moore and others did the obvious, devastating thing: they generated all of the possible keys. Every RSA and DSA key of common sizes that a broken Debian box could produce, precomputed and packaged for download. Checking whether a server’s key was vulnerable became a lookup. Getting into a server that used one became: try the 32,767 candidate private keys against its SSH login until one works — minutes of effort, no cryptanalysis, no cleverness.

And it was worse than “Debian servers are exposed,” because the poison traveled with the key, not the operating system. If you generated a certificate signing request on your Debian laptop in 2007 and installed the resulting certificate on a rock-solid FreeBSD server, that key was still one of the 32,767. If an admin made an SSH keypair on Ubuntu and copied the public half onto a hundred machines running anything at all, all hundred now trusted a guessable key. The vulnerability was baked into every artifact the broken generator ever touched, wherever it ended up. Debian had to ship a blocklist package and teach OpenSSH to reject known-weak keys outright, because you couldn’t fix this by patching OpenSSL — the bad keys were already deployed all over the internet, on systems that had never run Debian.

They kept turning up for years. In 2015, seven years after the advisory, a researcher who collected GitHub users’ public SSH keys still found Debian weak keys among them — some with push access to well-known projects — and GitHub had to revoke them. Long after anyone had an excuse.

The cleanup and the catastrophe were the same edit

Here’s what I can’t stop thinking about. There was no malice, no incompetence in the usual sense, no missed obvious step. A maintainer saw genuinely alarming warnings from a respected tool. He correctly identified that they came from reading uninitialized memory, which is a real bug pattern worth eliminating. He didn’t trust himself to change crypto code alone, so he asked the upstream project. He got an affirmative answer from an actual OpenSSL developer. Every individual move was the responsible one. The result was the worst cryptographic failure in the history of a major distribution.

The trap was that the two MD_Update lines were typographically identical while being semantically opposite — one was noise, one was the load-bearing wall — and nothing in the code, the tooling, or the conversation surfaced the difference. Valgrind can tell you that a line reads uninitialized memory. It cannot tell you that the same-looking line three functions away is the only thing feeding your entropy pool. The memory checker was measuring one property of the code; the property that mattered was invisible to it. And the human review that might have caught it got compressed into a mailing-list one-liner about a snippet nobody saw in full.

The lessons people usually draw are “distros shouldn’t patch upstream crypto” and “don’t blindly obey your linter,” and both are fine as far as they go. But the deeper one is about where danger actually lives. It wasn’t in the algorithm — RSA was fine, the math was fine. It was in a change small enough that everyone involved treated it as small: too minor for a security review, too obvious to double-check, too identical-looking to distinguish. The most dangerous edits are rarely the ones that look risky. They’re the ones that look like tidying up.