# Predictable or Repeated: The Only Two Ways Cryptographic Randomness Betrays You

> Every key, nonce, IV, and salt fails in one of two ways: predictable when it must be unpredictable, or repeated when it must be unique. A field guide.

*Published: 2026-07-04*
*Canonical: https://paragmali.com/blog/predictable-or-repeated-the-only-two-ways-cryptographic-rand*
*© Parag Mali. All rights reserved.*

---
<TLDR>
Every cryptographic key, nonce, IV, and salt is only as safe as two invisible properties: was it *unpredictable* when it had to be, and *unique* when it had to be? A cryptographically secure pseudorandom number generator (CSPRNG) manufactures unpredictability by stretching a little true entropy into unlimited output, and fails when the seed is guessable, the state cloned, the design back-doored, or the draw biased. But that is only half the story: the four roles each demand a different mix of unique, unpredictable, and secret, so a perfect generator still gets you owned if you reuse a GCM nonce or predict a CBC IV. A field guide to both halves, through every famous break.
</TLDR>

## 1. Two Lines of Code

In 1996, two Berkeley graduate students needed only seconds to reconstruct the secret keys protecting Netscape's SSL. The RC4 and RSA math was flawless. The browser had simply seeded its random number generator from the time of day and a couple of process IDs, and there were only so many of those [@src-netscape1996]. In 2008, a one-line change to Debian's OpenSSL quietly collapsed the space of every key it generated, for SSH, SSL, and OpenVPN, down to about 32,768 possibilities, small enough to enumerate on a laptop over lunch [@src-debian2008].

In both cases the cipher was flawless and the keys were guessable. That is the whole problem with cryptographic randomness: it is the one primitive whose failures are invisible, and there are only two of them.

Look closely at Netscape first. Ian Goldberg and David Wagner reverse-engineered the Navigator's seeding code and found that the entire unpredictable input to the session key came from the current time, the process ID, and the parent process ID [@src-netscape1996]. An attacker who knew roughly when you connected, and process IDs live in a small range, could reproduce the seed and therefore the key. The seed *was* the secret, and the secret was enumerable. Nothing about RC4 or RSA had to break; the attacker simply recomputed what the generator would produce.

Debian, twelve years later, is the same mistake wearing a different costume. A developer commented out a line in OpenSSL that fed unpredictable data into the seed, silencing a memory-analysis warning about reading uninitialized bytes.<Sidenote>The specific tool that flagged the warning is often reported as Valgrind, sometimes as Purify; the Debian advisory itself names neither, so we will not assert one. The load-bearing fact is the collapsed keyspace, which is not in dispute [@src-debian2008].</Sidenote> With that input gone, the only entropy left was the process ID, so the effective keyspace for roughly two years of Debian-generated keys shrank to about $2^{15}$ values, and CVE-2008-0166 was born [@src-debian2008, @src-cve2008]. Every SSH host key, TLS certificate, and OpenVPN key produced on an affected system could be pre-computed and looked up in a table.

<Definition term="CSPRNG (Cryptographically Secure Pseudorandom Number Generator)">
A deterministic algorithm that stretches a short, truly-random *seed* into an arbitrarily long output stream that no efficient adversary can distinguish from uniform random bits, even after observing part of the stream. The word *cryptographically* is what separates it from an ordinary statistical PRNG: the guarantee is about unpredictability to a computationally bounded attacker, not merely about passing statistical tests.
</Definition>

Neither of these was a broken algorithm. Both were broken randomness, and the reason they feel like the same story is that they *are* the same story. A weak or misused random value emits bytes that look perfect, pass every statistical test, and are catastrophically broken anyway. The generator hums along; the ciphertext validates; the signature verifies. The failure is silent until someone else computes what you computed.

> **Note:** A broken cipher raises an error. A bad MAC rejects. A weak or reused random value does neither: it produces output that is statistically indistinguishable from good randomness and passes every test you can run against it, while being fully predictable or fatally repeated to an attacker who knows where to look. You cannot detect the failure by inspecting the output. This is why randomness bugs survive code review, ship to production, and are found years later by the people exploiting them.

Once you have seen these two breaks side by side, the organizing claim of this article is hard to unsee. A random value can betray you in exactly one of two ways, and the failure can happen in exactly one of two places.

> **Key idea:** There are only two ways a random value fails: it is *predictable* when it needed to be *unpredictable*, or *repeated* when it needed to be *unique*. And each failure happens in exactly one of two places: the *generator* that manufactures the bits, or the *role* that consumes them (key, nonce, IV, salt). Two knobs, two places. Every famous break in this article is a single knob turned the wrong way in a single place.

This is Part 2 of a field guide for protocol designers. Part 1 argued that the standard security definition, IND-CPA, *mandates* randomized encryption: a scheme that encrypts the same message the same way twice cannot hide whether two ciphertexts carry the same plaintext, so a nonce or IV *is* the randomness the definition demands. This part supplies, and disciplines, that randomness. By the end you should be able to look at any randomness bug, Netscape or Debian or the ones still shipping today, and name on sight which knob turned and which place failed.

Both breaks were the generator half failing on the predictability knob. To see why that failure was almost inevitable, we have to go back to the moment cryptography decided to put all of its secrecy into a string of unguessable bits.

## 2. Why Randomness Became a Primitive

In 1883, Auguste Kerckhoffs told cryptographers something that sounds obvious now and was radical then: assume the enemy knows your algorithm [@src-kerckhoffs1883]. Do not build your security on the secrecy of the machine, because the machine will be captured, reverse-engineered, or sold. Build it on the *key*. That single move relocated all of a system's secrecy into one place, a string of bits, and quietly made the unpredictability of that string the foundation of everything, without ever saying where the string should come from.

Sixty-six years later, Claude Shannon made the requirement mathematical. In *Communication Theory of Secrecy Systems* (1949), he proved that the one-time pad achieves *perfect* secrecy: a ciphertext reveals nothing whatsoever about the plaintext, not even to an adversary with unlimited computing power [@src-shannon1949].

But the proof comes with a receipt. Perfect secrecy demands a key that is perfectly random and at least as long as the message, used once. Randomness stopped being a nice-to-have and became a first-class, provable requirement, the raw material perfect secrecy is quite literally made of. Hold onto the price tag on that key; it comes back in Section 8 as the gap no engineering closes.

<Definition term="Entropy and min-entropy">
*Entropy* measures how much true unpredictability a source contains. For cryptography the conservative measure is *min-entropy*, defined for a distribution $X$ as $H_\infty(X) = -\log_2 \max_x \Pr[X = x]$. It is governed by the single most likely outcome, that is, by the attacker's best single guess. A source with $n$ bits of min-entropy resists guessing about as well as $n$ uniformly random bits. This worst-case framing is deliberate: an average-case measure like Shannon entropy can look healthy while one dominant value makes the source easy to guess.
</Definition>

But where do perfectly random bits come from? In 1951 John von Neumann issued the warning that still hangs over the whole field. Studying ways to generate random digits on the first computers, he tried the *middle-square method*, squaring a number and taking the middle digits as the next value, and watched it collapse into short repeating cycles or fall to zero. His verdict was characteristically sharp.

<PullQuote>
"Any one who considers arithmetical methods of producing random digits is, of course, in a state of sin." -- John von Neumann, 1951 [@src-vonneumann1951]
</PullQuote>

The point was not that deterministic recipes are useless, but that arithmetic alone only *simulates* randomness; it cannot manufacture the genuine article [@src-vonneumann1951]. A formula has no unpredictability of its own. Whatever you get out was already determined by what you put in.

So for a while, randomness was treated as a scarce physical resource you harvested and rationed. In 1955 the RAND Corporation published *A Million Random Digits with 100,000 Normal Deviates*, an entire book whose content was a table of random numbers generated from electronic noise, sold so that scientists could look up randomness the way they looked up logarithms [@src-rand1955].<Sidenote>Two years later the UK built ERNIE (Electronic Random Number Indicator Equipment) to draw Premium Bond winners from thermal noise. It is a lovely piece of history, but no contemporaneous engineering primary survives that we could verify, so treat it as color, not evidence [@src-ernie1957].</Sidenote> Randomness you could buy in a bookstore is a charming idea and a useless one for cryptography, because a published table is, by definition, known to your adversary, and it cannot produce a fresh, secret, per-session value on demand.

<Aside label="Randomness you can extract, but not from arithmetic">
Von Neumann's ban is on arithmetic *manufacturing* randomness from nothing. It says nothing against *extracting* clean randomness from a messy physical source that already contains some, which is exactly what a modern entropy source does, and what a [fuzzy extractor](/blog/fuzzy-extractors-windows-hello/) does when it turns a noisy biometric into a stable key. Extraction concentrates existing unpredictability; it never conjures it. Garbage in, garbage out remains the law.
</Aside>

The conceptual breakthrough that made a *deterministic* generator respectable arrived in the early 1980s. Andrew Yao (1982) and Manuel Blum with Silvio Micali (1984) redefined what "random enough" should mean for cryptography [@src-yao1982, @src-blummicali1984]. Their answer was not statistical but *computational*: a sequence is cryptographically strong if no efficient algorithm, given all the output bits so far, can predict the next bit with probability meaningfully better than one-half.

<Definition term="Next-bit test">
A pseudorandom generator passes the *next-bit test* if no probabilistic polynomial-time algorithm, shown any prefix of the output sequence, can guess the following bit with advantage non-negligibly above $1/2$. Yao proved this test is equivalent to indistinguishability from uniform: pass the next-bit test and you pass *every* efficient statistical test at once [@src-yao1982, @src-blummicali1984, @src-katzlindell]. This is unpredictability made rigorous, and it is a yardstick no finite battery of statistical tests can ever meet.
</Definition>

This is knob number one, unpredictability, turned into a precise definition. Notice what it does not promise: it says nothing about where the seed comes from, and it holds only against a *bounded* adversary. Both caveats will return to bite. But the intellectual foundation was now in place. Shannon and Kerckhoffs had made unguessable bits the foundation of everything; von Neumann had warned that no formula can produce them from thin air. So engineers reached for the nearest deterministic recipe that merely *looked* random, and walked straight into the first disaster.

## 3. Statistical Randomness That Was Not Unpredictable

Here is a fact that should bother you. The Mersenne Twister, the default random generator in Python, Ruby, PHP, R, MATLAB, and countless other systems [@src-python-random], passes essentially every statistical randomness test ever devised. Its output is beautifully uniform, its period is astronomically long, and it is *completely predictable*. Observe 624 of its 32-bit outputs and you can reconstruct its entire internal state and compute every value it will ever emit, forward and backward [@src-mersenne1998]. That gap, between "looks random" and "is unguessable," is the mistake that organizes the rest of this article.

Start with the simplest offender. A *linear congruential generator* (LCG) produces its next value with $x_{n+1} = (a \cdot x_n + c) \bmod m$. It is fast, it is fine for shuffling a deck in a video game, and it is fatal for cryptography, because given a handful of outputs you can solve for the hidden constants and then predict everything. There is no unpredictability here at all, only arithmetic dressed up to look busy.

The Mersenne Twister (MT19937, 1998) is vastly more sophisticated, with 19,937 bits of state and excellent equidistribution, but it shares the same fatal property: it is *linear* over the field $\mathbb{F}_2$ [@src-mersenne1998]. Every output bit is a fixed linear function of the state bits. Collect enough outputs and you have a system of linear equations whose solution is the state. Sophistication did not buy unpredictability, because unpredictability was never what the design optimized for.

<Definition term="Statistical PRNG versus computational unpredictability">
A *statistical PRNG* is built to produce output with good distributional properties: uniformity, long period, no detectable correlations. A *cryptographically secure* generator is built to a strictly harder standard: no efficient adversary who has seen past output can predict future output, the next-bit test of Section 2. The two goals are independent. The Mersenne Twister maximizes the first and fails the second completely. Passing statistical batteries is necessary but nowhere near sufficient for cryptography.
</Definition>

The first genuinely *cryptographic* generators came from the banking world. The ANSI X9.17 and later X9.31 designs used a block cipher (two-key Triple-DES in X9.17; Triple-DES or AES in X9.31) rather than a linear recurrence [@src-hac, @src-ansix931], so predicting the output required breaking the cipher itself, not solving a linear system. This was real progress.

But it moved the whole burden onto a single secret: the seed key $K$ baked into the construction. If $K$ leaks, or worse, if it is *hard-coded* into shipped firmware, the generator becomes fully predictable to anyone who reads the binary. That is not a hypothetical. It is exactly the DUHK attack we will meet in Section 4, where hard-coded X9.31 keys let researchers decrypt live VPN traffic [@src-duhk2018].

And then there is the naive-seeding family, the Netscape lineage: take a strong-enough algorithm and feed it a weak seed built from the clock, the process ID, and the parent process ID [@src-netscape1996]. The algorithm is irrelevant if the seed is enumerable. In 2005 the IETF wrote this lesson into a Best Current Practice, RFC 4086, whose Section 3 explicitly warns against seeding from clocks and serial numbers because they are low-entropy and guessable [@src-rfc4086]. The rule was written in blood.

<RunnableCode lang="js" title="A guessable seed is a guessable key (the Netscape and Debian shape)">{`
// The cipher is never touched. Only the seed is guessed.
// We model a small seed space, like Netscape's time+PID+PPID
// or Debian's collapsed ~2^15..2^18 keyspace.

function prng(seed) {                 // a toy stand-in for the real generator
  let s = seed >>> 0;
  s = (Math.imul(1103515245, s) + 12345) >>> 0;   // classic LCG constants
  return s;                            // the victim's "key material"
}

const SEED_BITS = 18;                  // pretend this is all the entropy there was
const seedSpace = 1 << SEED_BITS;      // 262144 possibilities
const secretSeed = Math.floor(Math.random() * seedSpace);
const victimKey = prng(secretSeed);    // a "256-bit key" -- but only 18 bits of real entropy

// Attacker knows the seed came from a small space and enumerates it:
let tries = 0, recovered = -1;
for (let guess = 0; guess < seedSpace; guess++) {
  tries++;
  if (prng(guess) === victimKey) { recovered = guess; break; }
}

console.log('seed space:', seedSpace, 'possibilities');
console.log('attacker tries needed:', tries);
console.log('key recovered:', recovered === secretSeed);
console.log('note: a 256-bit key with 18 bits of seed entropy is an 18-bit key');
`}</RunnableCode>

> **Note:** The clock, the process ID, the MAC address, a counter: all are low-entropy and often knowable to an attacker. A generator seeded from them has only as much unpredictability as the seed, no matter how strong the algorithm downstream. RFC 4086 codifies this as a rule: do not seed from clocks or serial numbers [@src-rfc4086]. In practice you should never seed a CSPRNG by hand at all; ask the operating system, which is the entire lesson of Sections 5 and 10.

> **Note:** This is the first place your mental model has to shift. Statistical quality and cryptographic unpredictability are *different properties*, and the second does not follow from the first. The Mersenne Twister is the proof: it sails through the test batteries and collapses after 624 outputs [@src-mersenne1998]. Unpredictability is a *computational* claim about what an adversary can compute, not a *statistical* claim about how the bits are distributed. Tests can falsify a generator; they can never certify one, a limit we make precise in Section 8.

The theorists had, in fact, already named the generator that gets this right.

<Aside label="The provably-secure generator nobody ships">
Blum, Blum and Shub (1986) built a generator whose unpredictability *provably* reduces to the hardness of factoring large integers: break the generator and you have factored the modulus [@src-bbs1986]. It is the gold standard on paper. It is also almost never deployed, because it computes only a few bits per modular squaring, orders of magnitude too slow for a system handing out billions of random bytes a day. It is a deliberate non-deployment: the theoretically cleanest answer, benched for being impractical, while the field builds fast generators on other assumptions.
</Aside>

The Mersenne Twister is not a villain, incidentally.<Sidenote>For Monte-Carlo simulation, numerical integration, and procedural graphics, the Mersenne Twister is excellent and appropriate. It is disqualified only for cryptography, where its predictability is disqualifying. "Wrong for crypto" is not "wrong for everything."</Sidenote> The lesson is narrower and sharper: statistical excellence is not unpredictability, and unpredictability is the only thing cryptography cares about. Turning that computational ideal into something an operating system can hand out billions of times a day took four decades and a string of named catastrophes. Here is how the generator evolved.

## 4. CSPRNG Design, Generation by Generation

No single insight fixed cryptographic randomness. Instead there was a ratchet. Each generation of generator worked until a *named* disaster exposed the one thing it got wrong, and that disaster defined the next generation. Walk the ladder and you will notice every rung is one of the two knobs failing in the *generator* half of the problem. The consumer-half failures, PS3 and BEAST and the rest, are on the ladder too, because they drove design changes, but their real home is Section 6.

<Definition term="DRBG (Deterministic Random Bit Generator)">
NIST's term for the deterministic-expansion core of a modern generator: the algorithm that takes a seed and produces output bits by repeatedly applying a cryptographic function (a block cipher, a hash, or an HMAC). "DRBG" names the *expansion* half specifically; it does not include the entropy source that supplies the seed. A DRBG never creates unpredictability, it only stretches whatever the seed already contained.
</Definition>

<Definition term="Seed">
The short, high-entropy input a DRBG expands. Its length is set by the target security level (for example, 256 bits of seed for a 256-bit security level). Everything the generator's output can resist is bounded by the min-entropy of this seed: expand a 15-bit seed into a gigabyte of output and you have a gigabyte with 15 bits of unpredictability, which is exactly what happened to Debian.
</Definition>

**Generation 0, physical and tabular (1949 to 1957).** Harvest physical noise; if you must reuse it, print it in a book [@src-rand1955]. Superseded because a static table cannot deliver a fresh, secret, per-session value on demand, and von Neumann's middle-square degenerated to short cycles [@src-vonneumann1951].

**Generation 1, statistical PRNGs with ad-hoc seeding (1960s to 1998).** LCGs and the Mersenne Twister, seeded from the clock and the PID. Two knob-one failures stacked on top of each other: the generator is linearly invertible (624 outputs recover a Mersenne Twister) and the seed is enumerable (Netscape) [@src-mersenne1998, @src-netscape1996].

**Generation 2, block-cipher PRNGs (ANSI X9.17 and X9.31, 1985 to 1998).** A cipher instead of a linear recurrence, so the output is not linearly invertible. It failed because all security rested on a secret seed key $K$, and real products hard-coded $K$ into firmware. In 2017 the DUHK attack recovered full generator state and decrypted VPN traffic on devices such as FortiOS by exploiting a hard-coded X9.31 key, with no entropy input, no reseed, and no forward secrecy to save them [@src-duhk2018].

**Generation 3, OS entropy pools (Linux /dev/random, Ts'o 1994).** The operating system harvests timing from interrupts, keystrokes, and disk activity into a hashed pool, *estimates* how much entropy it holds, and blocks when the estimate runs low [@src-random-c, @src-random4man]. This was the first mass-deployed OS CSPRNG and a real advance, but it had three weaknesses: entropy estimation is guesswork; blocking starves fresh systems at boot and spawned years of `/dev/random`-versus-`/dev/urandom` confusion; and low boot entropy breaks real keys at scale. In 2012, *Mining Your Ps and Qs* scanned the internet and found that 5.57% of TLS hosts and 9.60% of SSH hosts shared keys, with thousands of RSA moduli factorable because devices generated keys before their pools had any entropy [@src-heninger2012, @src-factorable]. The independent *Ron was wrong, Whit is right* study found shared RSA prime factors across millions of moduli the same year [@src-lenstra2012], and in 2013 researchers factored 184 RSA keys from Taiwan's government-certified Citizen Digital Certificate smartcards, hardware that had passed FIPS and Common Criteria evaluation [@src-smartfacts, @src-coppersmith2013]. The Linux RNG's internal state had already been shown recoverable by cryptanalysis in 2006 [@src-gutterman2006], and the Windows generator was analyzed the following year [@src-dorrendorf2007].

**Generation 4, standardized DRBGs (NIST SP 800-90A, 2006 onward).** Hash_DRBG, HMAC_DRBG, and CTR_DRBG: a seed plus a cryptographic function stretched deterministically, with a defined reseed limit (at most $2^{48}$ generate requests) and a backtracking-resistant update step [@src-sp80090a]. And, shipped in the *same document*, the field's most instructive cautionary tale: Dual_EC_DRBG. It had a documented output *bias*, distinguishable from uniform independently of any back door (Schoenmakers and Sidorenko, 2006) [@src-schoenmakers2006], and, far worse, a *trapdoor*: whoever chose its elliptic-curve points $P$ and $Q$ could recover the internal state from about 32 bytes of output and predict all future output (Shumow and Ferguson, 2007) [@src-shumow2007]. It was reported in 2013 as a deliberate program [@src-reuters2013] and caught weaponized in Juniper's ScreenOS in 2015 [@src-checkoway2016]. A generator can pass every statistical test and still be *owned* by whoever chose its constants.

<Mermaid caption="Seven generations of cryptographic randomness (Gen 0 to Gen 6), and the named break that ended each one">
flowchart TD
    G0["Gen 0: physical and tabular (1949 to 1957)"] -->|"a table makes no fresh secret"| G1["Gen 1: statistical PRNGs, ad-hoc seeds"]
    G1 -->|"MT 624 outputs, Netscape seed"| G2["Gen 2: block-cipher PRNGs (X9.17, X9.31)"]
    G2 -->|"DUHK hard-coded key"| G3["Gen 3: OS entropy pools (Linux, 1994)"]
    G3 -->|"boot starvation, Mining Ps and Qs"| G4["Gen 4: standardized DRBGs (SP 800-90A)"]
    G4 -->|"Dual EC back door"| G5["Gen 5: accumulation, ChaCha20, getrandom"]
    G5 -->|"fork or VM-snapshot clone"| G6["Gen 6: converged validated architecture (90B, 90A, 90C)"]
</Mermaid>

**Generation 5, entropy accumulation, stream-cipher cores, and non-blocking syscalls (1999 to 2022).** Yarrow (1999) [@src-yarrow1999] led to Fortuna (2003), whose 32 staggered pools *abolish entropy estimation* altogether [@src-fortuna2003, @src-freebsd-random]. The output core moved to ChaCha20, Bernstein's refinement of his earlier Salsa20 cipher [@src-chacha, @src-snuffle]: OpenBSD re-cored `arc4random` on it in 2014 [@src-arc4random], and Linux replaced its non-blocking pool with a ChaCha20 CRNG in kernel 4.8, October 2016 [@src-linux48].<Sidenote>Pin this milestone at Linux 4.8 (October 2016), commit e192be9d9a30555. A "5.6 / 2020" figure sometimes cited refers to an unrelated change in how `/dev/random` handles `O_NONBLOCK`, not the ChaCha20 output core [@src-linux48].</Sidenote> Jason Donenfeld's 5.17 to 5.18 rewrite in 2022 added BLAKE2s extraction, per-CPU fast key erasure, and unified `/dev/random` with `/dev/urandom` [@src-donenfeld2022]. The `getrandom(2)` syscall (2014) blocks *once* at boot until the pool is seeded, then never again [@src-getrandom]. One hazard the generator alone cannot fix remained: `fork()` and VM-snapshot cloning duplicate the entire generator state and re-emit identical values, a *uniqueness* failure measured in the wild by Ristenpart and Yilek (2010) and Everspaugh et al. (2014) [@src-ristenpart2010, @src-everspaugh2014].<Sidenote>OpenBSD kept the name `arc4random` for source compatibility even though the "RC4" it originally stood for is long gone; since 2014 the bytes come from ChaCha20 [@src-arc4random].</Sidenote>

**Generation 6, the converged validated architecture (2018 to 2026, current state of the art).** A *validated* entropy source (SP 800-90B) [@src-sp80090b] seeds a *computational* DRBG (SP 800-90A) [@src-sp80090a], and the two are tied together by SP 800-90C [@src-sp80090c], with continuous reseeding, forward secrecy, clone defense, and no single component trusted on its own. We draw the whole pipeline in Section 5.

<Mermaid caption="CTR_DRBG: generate, then re-key, so earlier output cannot be rolled back">
flowchart TD
    K["State: Key and counter block V"] --> G["Generate: AES-CTR keystream from Key and V"]
    G --> O["Hand the requested bytes to the caller"]
    G --> U["Update: re-derive a fresh Key and V"]
    U --> K2["New state replaces the old one, so prior output cannot be recomputed (backtracking resistance)"]
    K2 --> R["After up to 2 to the 48 requests, reseed from the entropy source"]
    R --> K
</Mermaid>

Now put every break on one page. This is the spine of the article: read down the two middle columns and each disaster resolves to a single knob turned the wrong way in a single place.

**Table 1: The Failure Catalog.**

| Break | Year | Knob broken | Place | Root cause | Lesson |
|-------|------|-------------|-------|-----------|--------|
| Netscape SSL [@src-netscape1996] | 1996 | predictable | generator | Seed was time + PID + PPID | Never seed from the clock or a PID |
| Debian OpenSSL [@src-debian2008] | 2008 | predictable | generator | Entropy input removed, keyspace ~2^15 | Never delete an entropy source |
| PS3 signing key [@src-fail0verflow2010] | 2010 | repeated | role (nonce) | Same ECDSA nonce k on every signature | A signature nonce must be unique |
| Android Bitcoin [@src-android2013] | 2013 | repeated | role (nonce) | SecureRandom mis-init repeated k | Seed the RNG before drawing a nonce |
| Mining Ps and Qs [@src-heninger2012] | 2012 | predictable | generator | Keys generated before pool seeded | Do not make keys at cold boot |
| Dual_EC_DRBG [@src-shumow2007] | 2007-15 | predictable | generator | Trapdoor in chosen points P, Q | Choose constants verifiably; never trust one source |
| VM-snapshot reset [@src-ristenpart2010] | 2010-14 | repeated | generator | Snapshot clones generator state | Force a reseed after any clone |
| DUHK [@src-duhk2018] | 2017 | predictable | generator | Hard-coded X9.31 seed key | Never hard-code the seed key |
| BEAST [@src-cve2011] | 2011 | predictable | role (CBC IV) | TLS 1.0 chained the previous ciphertext block as IV | A CBC IV must be unpredictable |
| GCM nonce reuse in TLS [@src-cve2016] | 2016 | repeated | role (nonce) | Repeated 96-bit nonce under one key | Never reuse a GCM nonce per key |
| CTR_DRBG cache attack [@src-cohney2020] | 2020 | predictable | generator | AES-table cache timing leaks state | Prefer a constant-time core |
| PuTTY P-521 [@src-putty2024] | 2024 | predictable | role (nonce) | Biased k, first 9 bits always zero [@src-putty-oss] | A nonce must be uniform or derived |

Two columns, two values each. Every row is one knob times one place. Hold this table in mind; Section 10 hands it back to you as the Common Misuse catalog, the same evidence seen from the developer's chair.

<Aside label="Distrust is architected in">
Dual_EC did more than embarrass a standard; it changed how the field designs generators. Before it, the working assumption was "trust the standardized construction." After it, the assumption became "mix multiple sources and trust no single one, and choose your constants so anyone can verify there is no hidden structure." That is why modern kernels fold hardware RNG output *into* a pool rather than using it raw, why NIST's newer curves come with published nothing-up-my-sleeve derivations, and why SP 800-90C validates the *whole* pipeline instead of asking you to trust the source and the DRBG separately [@src-sp80090c].

The lesson generalizes: a generator that can be back-doored by whoever picks its constants is not secure just because it passes tests. This philosophy shapes Sections 5 and 7.
</Aside>

The most surprising fix of all runs the other direction. After the PlayStation 3, the Bitcoin wallets, and eventually PuTTY, all showed that a *random* per-signature nonce is catastrophic when it repeats or leaks,<Sidenote>Recovery differs by failure mode. A *repeated* nonce means two signatures share $k$, so simple algebra recovers the private key, as on the PlayStation 3. A *biased* nonce, like PuTTY's nine always-zero leading bits, falls to a lattice attack on the *Hidden Number Problem*: each signature leaks a few key bits, and lattice reduction solves for the key once about 60 accumulate [@src-putty-oss].</Sidenote> RFC 6979 (2013) responded by *removing* the randomness: it derives the ECDSA nonce deterministically from the private key and the message, so that signers, in the RFC's own words,

<PullQuote>
"...do not need access to a source of high-quality randomness." -- RFC 6979, on deterministic ECDSA nonces [@src-rfc6979]
</PullQuote>

The fix for a randomness disaster was *less* randomness. That inversion is the clue that the generator is only half the story, and often not the interesting half. Seven generations and a dozen named disasters later, the modern architecture is strikingly simple once you see what every break was really teaching. Strip away the history and one design remains, built from two layers most engineers never separate.

## 5. The Modern Architecture, and the Two Layers Underneath It

Every fix in Section 4 was pushing toward one shape, and here it is. A *true entropy source* seeds a *computational DRBG* that stretches a little unpredictability into unlimited output, with continuous reseeding, forward secrecy, and clone defense, and with distrust of any single component built in. Nothing exotic. Just forty years of scar tissue drawn as a single pipeline.

<Mermaid caption="The modern RNG pipeline: a little true entropy, stretched safely to unlimited output">
flowchart LR
    E["Entropy sources: interrupts, RDSEED, TPM"] --> A["Accumulate and condition"]
    A --> D["DRBG expansion: AES-CTR or ChaCha20"]
    D --> I["OS interface: getrandom, arc4random, BCryptGenRandom"]
    I --> R["Consumer roles: key, nonce, IV, salt"]
    D -.->|"reseed and fast key erasure"| A
</Mermaid>

Read it left to right. Physical noise (interrupt timing, the `RDSEED` instruction, a TPM) is accumulated and conditioned into a seed. A DRBG expands that seed into as many bytes as anyone asks for. An operating-system interface hands those bytes to your program, which pours them into one of four roles. The dotted return edge carries the two properties that keep the whole thing honest: reseeding, which periodically mixes fresh entropy back in, and forward secrecy, which we define in a moment.

The distinction the whole subject turns on is the one from Sections 2 and 3, now with a job. Computational unpredictability is about what an adversary can compute, and the DRBG delivers it by construction, provided the seed carried real entropy. A DRBG does not *create* entropy; it stretches it. Garbage seed in, garbage out, which is why the entire left side of the diagram exists.

<Definition term="Forward secrecy (fast key erasure)">
A generator has *forward secrecy* if an attacker who compromises its state at some moment still cannot reconstruct output it produced *earlier*. ChaCha20-based generators achieve it by *fast key erasure*: after generating a block, the first bytes of fresh keystream overwrite the generator's own key, and only the remaining bytes are handed out [@src-donenfeld2022]. The key that produced earlier output no longer exists anywhere, so a later memory capture cannot roll time backward.
</Definition>

<Definition term="Reseeding">
Periodically folding fresh entropy into the generator's state, on a schedule (SP 800-90A mandates a reseed within $2^{48}$ requests) or on an event (after a `fork()`, after a VM resumes from a snapshot) [@src-sp80090a]. Reseeding gives *backward* secrecy: even if the state was compromised, once enough new entropy is mixed in, future output becomes unpredictable again.
</Definition>

<Mermaid caption="Fast key erasure gives forward secrecy">
sequenceDiagram
    participant S as Generator state
    participant O as Output
    S->>O: emit one block of keystream
    S->>S: overwrite the key with fresh keystream
    Note over S: the old key is gone, so earlier output cannot be recomputed
</Mermaid>

Now the correction that clears away half the folklore in this field: the difference between an *OS interface* and a *CPU instruction*. These are two different layers, and conflating them is the source of endless bad advice.

An **OS-provided interface** is what your code should call: `getrandom(2)` or `/dev/urandom` on Linux, `getentropy(3)` or `arc4random(3)` on BSD and macOS, `BCryptGenRandom` or `ProcessPrng` on Windows [@src-getrandom, @src-arc4random]. On Linux, `getrandom` blocks *once* until the pool is first seeded and then never again, which is what finally ended the `/dev/random`-versus-`/dev/urandom` dilemma at the interface level [@src-getrandom, @src-lwn2014].

A **CPU instruction** is what the operating system *consumes*, not what you call. Intel's `RDRAND` returns the output of an on-die hardware DRBG; `RDSEED` returns near-raw, seed-grade entropy [@src-intel-drng]. The kernel does not hand these to you and does not trust them on their own. It *mixes* them into its pool alongside other sources, precisely the post-Dual_EC discipline: never let one opaque component be the whole answer [@src-donenfeld2022].

> **Note:** The rule is a single sentence: applications call the OS interface; only the kernel touches the CPU instruction. If you find yourself calling `RDRAND` directly in application code, you have skipped the layer that mixes, reseeds, and forward-secures the output, and you are trusting one piece of opaque silicon with the whole job, the exact mistake Dual_EC taught the field never to make [@src-shumow2007]. Call `getrandom`, `getentropy`, or `BCryptGenRandom`, and let the kernel decide how much to trust the hardware [@src-getrandom, @src-donenfeld2022].

<Mermaid caption="OS interfaces versus CPU instructions: who calls what">
flowchart TD
    APP["Your application"] -->|"calls this"| OSI["OS interface: getrandom, arc4random, BCryptGenRandom"]
    OSI --> POOL["Kernel entropy pool and DRBG"]
    POOL --> OSI
    RDRAND["CPU RDRAND: on-die DRBG output"] -.->|"mixed in, never trusted alone"| POOL
    RDSEED["CPU RDSEED: seed-grade entropy"] -.->|"mixed in, never trusted alone"| POOL
</Mermaid>

<Aside label="Why the OS mixes rather than trusts">
So why not skip the pool and read `RDRAND` directly? Because mixing is what turns "trust no single source" from a slogan into a working defense. If the kernel folds `RDRAND` into a pool that also holds interrupt timing and a saved seed, then even a compromised `RDRAND` cannot by itself make the output predictable, because it is only one of several inputs to the mix. Trust is not placed in any one component; it is *diluted* across all of them. We return to the hardware-versus-software question in Section 7.
</Aside>

Seen this way, the generator's entire job is to manufacture knob number one, unpredictability, and to keep manufacturing it reliably. Everything else in the pipeline, reseeding, forward secrecy, clone defense, architected distrust, exists to stop that one property from silently failing. The four decades of history collapse into a single sentence: keep the seed unguessable, keep the state fresh, and never let one component be the whole answer.

Windows implements this same pipeline with a CTR_DRBG tree behind `BCryptGenRandom` and `ProcessPrng`; the platform-specific internals, including how the kernel seeds per-processor generators and defends against VM cloning, are the subject of a dedicated [companion article on the Windows CSPRNG](/blog/a-key-is-only-as-unguessable-as-the-dice-that-made-it-inside/), and the [CNG API surface](/blog/cng-architecture-bcrypt-ncrypt-ksps/) itself lives in a companion post. We will not re-derive them here.

That is the generator half, essentially solved as engineering. But a flawless generator is only half the battle, because the same perfect byte stream is catastrophic if you pour it into the wrong role. What, exactly, does each role demand?

## 6. What Ships in 2026, and the Four Roles

By 2026 the generator architecture is settled engineering, and most real-world breaks happen *after* the generator, when good bytes are used wrong. So we cover what ships in a paragraph, then spend the rest of the section on the practitioner's heart of the matter: the four roles and the exact property mix each one demands.

What ships: the SP 800-90A trio (CTR_DRBG, HMAC_DRBG, Hash_DRBG) [@src-sp80090a]; Fortuna on FreeBSD [@src-freebsd-random]; and ChaCha20-based generators reachable through Linux `getrandom` and OpenBSD or macOS `arc4random` [@src-donenfeld2022, @src-arc4random].

The 2025 capstone is SP 800-90C, which stops treating "the entropy source" and "the DRBG" as separate acts of faith and validates the whole pipeline as one of four RBG construction classes: RBG1 (seeded once, no ongoing source, for constrained devices), RBG2 (continuous access to a validated SP 800-90B source, the common server case), RBG3 (an enhanced, full-entropy construction combining a physical source directly with a DRBG), and RBGC (chained constructions built from approved components) [@src-sp80090c].

A pre-draft SP 800-90A Rev. 2 adds an XOF_DRBG built on the SHAKE functions from FIPS 202, forward-looking but not yet final [@src-sp80090a-r2].

Post-quantum cryptography makes randomness *more* foundational, not less. The 2024 standards each draw fresh CSPRNG output per operation: ML-KEM consumes random seeds during key generation and encapsulation [@src-fips203], ML-DSA draws a fresh value per signature [@src-fips204], and SLH-DSA pulls several seeds per operation [@src-fips205]. More draws mean more surface for a weak generator to ruin, which is why the [post-quantum migration guides](/blog/post-quantum-cryptography-on-windows-the-thirty-year-migrati/) for these algorithms treat the CSPRNG as a first-class dependency.

Now the centerpiece. There are exactly four roles a random value plays in cryptography, and each demands a *different* combination of three properties: *uniqueness*, *unpredictability*, and *secrecy*. Getting the combination wrong is how a perfect generator still gets you owned.

**Table 2: The four roles and what each one owes.**

| Role | Unique? | Unpredictable? | Secret? | Canonical failure if violated |
|------|:------:|:-------------:|:------:|------------------------------|
| **Key** | yes | yes | **yes** | Guessable key: Netscape 1996, Debian 2008 [@src-debian2008] |
| **CBC IV** | yes | **yes** | no | Predictable IV enabled BEAST [@src-cve2011] |
| **CTR/GCM nonce** | **yes** | no | no | Repeated nonce leaks the auth key: GCM in TLS 2016 [@src-cve2016] |
| **Salt** | yes | no (but random) | **no** | Reused or global salt enables shared precomputed tables |

Read the differences, because they are the whole point.

<Definition term="Nonce">
A *nonce* is a "number used once." Its one absolute requirement is *uniqueness* per key: no two operations under the same key may ever use the same nonce. A nonce does *not* have to be unpredictable, and it does *not* have to be secret. A simple monotonic counter is a perfectly good nonce, and often the safest one, because a counter cannot accidentally repeat the way random draws can.
</Definition>

<Definition term="Initialization Vector (IV)">
An *IV* is the per-message starting value for a block-cipher mode, and its requirement depends entirely on the mode. In CBC, the IV must be both *unpredictable* and *unique*. In CTR and GCM, the value the spec labels "IV" is really a *nonce*, and needs only to be *unique*. The word "IV" therefore does not name a fixed requirement; the *mode* names the requirement. This is the single most common source of confusion in applied randomness.
</Definition>

The nonce-versus-IV correction deserves stating precisely, because folklore gets it backwards. The *same value slot* is called an "IV" or a "nonce" depending on the mode, and the *mode* dictates the requirement.

A CBC IV must be unpredictable *and* unique. TLS 1.0 chained CBC records by using the previous ciphertext block as the next IV, which is *predictable*, and that predictability is exactly what the BEAST attack exploited in 2011 (CVE-2011-3389). TLS 1.1 restored a fresh random IV, and the modern answer is to move to authenticated encryption entirely [@src-cve2011, @src-imperialviolet].

A CTR or GCM nonce, by contrast, needs *only* to be unique; unpredictability buys nothing. But uniqueness is absolute: reuse a GCM nonce under one key even once and you immediately leak the XOR of the two plaintexts, and the reused authentication tags give a polynomial equation whose roots recover the GHASH subkey $H$ (Joux's forbidden attack), enabling universal forgery. That failure was measured across live TLS servers in 2016 under the name "Nonce-Disrespecting Adversaries" and captured for IBM Domino as CVE-2016-0270 [@src-cve2016].

> **Note:** GCM's security collapses the instant a nonce repeats under a given key. Two messages encrypted with the same key and nonce leak the XOR of their plaintexts *and*, worse, give up the polynomial equation that recovers the GHASH subkey $H$, after which an attacker can forge arbitrary messages that pass authentication [@src-cve2016]. There is no partial failure here and no recovery: one repeat is game over for that key. Use a counter, or if you cannot guarantee uniqueness across senders, use a nonce-misuse-resistant mode (Section 7).

<Mermaid caption="Same value slot, different demands: what the mode asks of your IV or nonce">
flowchart TD
    V["A value in the IV or nonce slot"] --> Q&#123;"Which mode?"&#125;
    Q -->|"CBC"| CBC["Must be unpredictable AND unique. A predictable IV enabled BEAST"]
    Q -->|"CTR or GCM"| CTR["Must be unique only. Reuse leaks the authentication key"]
</Mermaid>

The last role, the salt, is where a different myth does the damage.

<Definition term="Salt">
A *salt* is a unique, random value added to a password before hashing, and it is *not secret*: it is stored in plaintext right next to the resulting hash. Its only job is uniqueness, so that identical passwords hash to different values and one precomputed table cannot attack many accounts at once. A salt needs to be unique and random; it does not need to be unpredictable or hidden.
</Definition>

Believing a salt must be secret, or reusing one global salt across every password, is a common and wrong model. A per-password random salt of 16 bytes, stored openly, does its entire job; hiding it adds nothing, and sharing it across users throws the job away, because then one precomputed table works against every account again [@src-nist-63b].<Sidenote>The same reuse math retired WEP: a short per-packet IV space made keystream reuse inevitable, which broke the confidentiality of the whole scheme [@src-bgw2001]. That story belongs to [cipher agility](/blog/rotating-every-cipher-schannel-and-the-twenty-year-algorithm/) and is told in the SChannel post.</Sidenote>

<RunnableCode lang="js" title="One CSPRNG, four roles, four disciplines">{`
const crypto = require('crypto');

// KEY: secret + unpredictable + high-entropy. Straight from the OS CSPRNG.
const key = crypto.randomBytes(32);          // 256 bits, for AES-256 or ChaCha20

// GCM NONCE: unique only. A monotonic counter guarantees uniqueness by construction.
let counter = 0n;
function nextNonce() {
  const n = Buffer.alloc(12);                // 96-bit GCM nonce
  n.writeBigUInt64BE(counter, 4);            // 64-bit counter in the low bytes
  counter += 1n;                             // never repeats under this key
  return n;
}

// SALT: unique + random, stored in the clear next to the password hash.
const salt = crypto.randomBytes(16);
console.log('salt is PUBLIC, stored with the hash:', salt.toString('hex'));

// The WRONG lines, for contrast:
//   const nonce = Buffer.alloc(12);                 // reused -> repeated knob, in a role
//   const salt  = Buffer.from(String(Math.random())); // Math.random is not a CSPRNG -> predictable

console.log('key bytes:', key.length, 'nonce bytes:', nextNonce().length);
console.log('same generator, three different disciplines');
`}</RunnableCode>

> **Key idea:** A perfect generator poured into the wrong role is as fatal as a broken generator. The key must be secret, unpredictable, and high-entropy; the CBC IV unpredictable and unique; the CTR or GCM nonce unique and nothing more; the salt unique, random, and public. Four roles, one generator, four different disciplines. Learn which mix each role owes and most of the Failure Catalog stops being possible.

Now the reader knows what each role demands. But which generator should manufacture the bits, and is a hardware RNG better than a software one? The honest answer overturns the question.

## 7. CTR_DRBG, Fortuna, and ChaCha, and Hardware Versus Software

"Which cipher makes the best RNG?" is the wrong question. The real axis is *architecture*: how you *source* entropy and how you *expand* it. Get that framing right and the "competitors" turn out to be solving different halves of the same problem.

Start with a category correction. Fortuna is largely an *accumulator*, the sourcing half: its contribution is how it collects and schedules entropy. CTR_DRBG and ChaCha20 are *expanders*, the expansion half: their contribution is how they stretch a seed. A real system runs an accumulator front-end feeding an expander core, so "CTR_DRBG versus Fortuna" is partly a comparison between a fuel tank and an engine.

**Table 3: Deployed generators, head to head.**

| Property | CTR_DRBG | HMAC_DRBG | Fortuna | ChaCha20 |
|----------|----------|-----------|---------|----------|
| Output cost | ~1 AES per 128 bits | ~1 HMAC per block | cipher core per block | 1 ChaCha permutation per 512 bits |
| State size | Key + 128-bit V | two hash-length values | 32 pools + generator key | 256-bit key, per CPU |
| Forward secrecy | Update re-derives (Key, V) | Update re-derives (Key, V) | frequent generator rekey | fast key erasure |
| Side channel | AES-table lookups can leak [@src-cohney2020] | HMAC, low risk | depends on core | constant-time by design [@src-chacha] |
| Entropy philosophy | assumes a 90B seed | assumes a 90B seed | **accumulates**, no estimate | assumes a pool seed |
| Reseed | up to 2^48 requests | up to 2^48 requests | 32-pool staggered | continuous, per CPU |
| FIPS 140 | **yes, validated** | yes | no | not yet |
| Concurrency | per instance | per instance | single system RNG | **per CPU** since Linux 2022 |
| Best suited for | FIPS boundaries, AES-NI | deterministic signatures | accumulator front-end | modern software default |

Two 2026 differences decide most real choices. First, ChaCha20 is *constant-time* by design, with no secret-dependent branches or table lookups,<MarginNote>Constant-time means the code's execution path and memory-access pattern do not depend on secret values, so an attacker watching timing or cache behavior learns nothing about the state.</MarginNote> so it sidesteps the cache-timing state-recovery that Cohney et al. demonstrated against real CTR_DRBG deployments in 2020 [@src-cohney2020, @src-chacha]. Second, CTR_DRBG holds *FIPS 140 validation*, which is exactly why it still dominates inside compliance boundaries and on AES-NI hardware despite the side-channel result [@src-sp80090a]. Linux's 2022 move to per-CPU ChaCha instances closed the historic concurrency gap that once favored other designs [@src-donenfeld2022].

<Definition term="Entropy accumulation versus estimation">
Two honest philosophies for the sourcing half. *Estimation* (NIST SP 800-90B) models the noise source, computes a min-entropy estimate, and runs health tests to catch degradation [@src-sp80090b]. *Accumulation* (Fortuna) refuses to estimate at all: it spreads incoming events across many pools and drains them on a schedule that guarantees at least one pool eventually holds enough entropy, whatever the true rate [@src-fortuna2003, @src-cryptoeng]. One measures; the other structures itself so that measurement is unnecessary.
</Definition>

<Mermaid caption="Fortuna's 32 staggered pools: why a slow attacker can never starve every pool">
flowchart TD
    EV["Entropy events arrive"] --> RR["Round-robin into pools P0 through P31"]
    RR --> P0["P0 drains at every reseed"]
    RR --> PI["Pi drains only when 2 to the i divides the reseed number"]
    RR --> P31["P31 fills rarely but very deeply"]
    P0 --> RK["Reseed the generator key"]
    PI --> RK
    P31 --> RK
</Mermaid>

The hardware-versus-software debate resolves the same way: not by choosing, but by mixing.

**Table 4: Hardware RNG versus software DRBG.**

| | Hardware (RDRAND/RDSEED, TPM) | Software DRBG (CTR/ChaCha) |
|--|-------------------------------|----------------------------|
| Source of unpredictability | physical noise on the die | deterministic expansion of a seed |
| Auditability | **opaque**, you cannot inspect silicon | open, testable, formally analyzable |
| Failure mode | silent bias or back door (the Dual_EC lesson) [@src-shumow2007] | bad seed, state clone, side channel |
| Forward secrecy | vendor-dependent | explicit (key erasure or Update) |
| Verdict | **seed-grade input only** | **auditable expansion + forward secrecy** |

Hardware supplies physical unpredictability but is a black box you cannot audit; software supplies auditable, forward-secret expansion but only stretches a seed. The field's resolution is to fold hardware output *into* the pool rather than trust it directly [@src-donenfeld2022]. "Trust `RDRAND` alone" fell out of favor precisely because Dual_EC proved silicon and standards can both be compromised [@src-shumow2007].

> **Note:** This article cross-references [the Windows CSPRNG](/blog/a-key-is-only-as-unguessable-as-the-dice-that-made-it-inside/) rather than duplicating it. How CNG assembles a per-processor CTR_DRBG tree, seeds it, and defends against VM cloning is covered in full there.

On the consumer side, the same "which construction" question has its own honest table, this time about how much uniqueness burden you are willing to carry.

**Table 5: Plain GCM versus GCM-SIV versus deterministic.**

| | AES-GCM (counter nonce) | AES-GCM (random nonce) | AES-GCM-SIV | RFC 6979 (signatures) |
|--|--------------------------|-------------------------|-------------|------------------------|
| Uniqueness burden | on the caller (counter) | birthday cap ~2^32 per key | **tolerates repeats** | none (deterministic) |
| Cost vs GCM | 1x | 1x | ~2x | not applicable |
| Catastrophic on reuse? | yes | yes | **no** | not applicable |
| Use when | you control a counter | low message volume | uniqueness not guaranteed | ECDSA/DSA nonces |

AES-GCM-SIV (RFC 8452) derives its internal inputs from a synthetic value computed over the nonce *and* the plaintext, so a repeated nonce with different messages no longer leaks the authentication subkey [@src-rfc8452]. In the RFC's own words, such schemes are built so that they

<PullQuote>
"...do not fail catastrophically if a nonce is repeated." -- RFC 8452, on AES-GCM-SIV [@src-rfc8452]
</PullQuote>

It costs roughly twice a plain GCM encryption and is the right answer when you cannot *guarantee* unique nonces, for example across many independent senders. RFC 6979 is the other escape hatch, for signatures: derive the nonce deterministically and the uniqueness problem disappears entirely [@src-rfc6979].

<Aside label="Estimate or accumulate, two honest philosophies">
It is tempting to declare a winner between NIST's estimation and Fortuna's accumulation, but both ship in 2026 and neither is wrong. Estimation gives you a number you can put in a validation report and a health test that fires when the source degrades [@src-sp80090b]. Accumulation gives you robustness without ever having to trust a number, at the cost of absorbing a burst of fresh entropy more slowly, since high-index pools reseed rarely [@src-fortuna2003]. The mature view is that they are different risk trades over the same unmeasurable quantity, which is the subject of the next section.
</Aside>

The engineering looks won: mix a validated source into a constant-time, forward-secret DRBG behind an un-misusable syscall. So why does the field still call randomness an open problem? Because underneath the engineering sit limits no code can cross.

## 8. What You Cannot Know or Prove

Everything so far was engineering, and engineering can be improved. This section is about the floor beneath it: things you fundamentally *cannot* do, no matter how good your code is. This is where confidence turns to humility.

**Min-entropy is unmeasurable.** You cannot *measure* the min-entropy of an unknown physical source from its output; you can only *estimate* it under a model of how the source behaves. SP 800-90B's entire apparatus, its estimators and its health tests, is a disciplined estimate, not a proof [@src-sp80090b]. The whole tower we built in Sections 5 through 7 rests on this one estimate, and if the model is wrong, everything above it is quietly weaker than its paperwork claims.

**Statistical tests can falsify, never certify.** SP 800-22 is explicitly hypothesis testing with P-values: a suite can *reject* a generator, but it can never *prove* unpredictability [@src-sp80022]. The proof by counterexample is the Mersenne Twister from Section 3: it passes the standard batteries and is fully broken by 624 outputs [@src-mersenne1998].<Sidenote>The named suites, SP 800-22, Dieharder, TestU01, are useful for catching gross defects and nothing more. Running more of them does not raise your security; it only lowers the chance you shipped something obviously broken.</Sidenote> "More tests" is not "more secure."

**Every deployed computational CSPRNG rests on an unproven assumption.** The best a deterministic generator achieves is the next-bit guarantee, but that guarantee is conditional: cryptographically secure generators exist *if and only if* one-way functions exist, an equivalence whose hard direction (any one-way function yields a generator) was proved by Hastad, Impagliazzo, Levin, and Luby [@src-hill1999, @src-katzlindell]. The existence of one-way functions is itself unproven, since it would imply that $P \neq NP$. There is no unconditionally secure *pseudorandom* generator. Every CSPRNG you have ever used is secure only relative to a hardness assumption no one has proved.

<Definition term="Birthday bound">
For an $m$-bit random value drawn repeatedly, collisions become likely after roughly $2^{m/2}$ draws, not $2^m$, because the chance is dominated by pairs. For a 96-bit GCM nonce, the 50% collision point sits near $2^{48}$ draws. This is a *provable ceiling*, not an implementation defect. It is why SP 800-38D caps random 96-bit nonces well below that point, at about $2^{32}$ messages per key, to keep the repeat probability negligibly small [@src-sp80038d].
</Definition>

**The birthday bound is a hard uniqueness ceiling.** Randomness cannot buy its way past its own birthday bound. The only escapes are to *stop drawing randomly*, using a counter whose uniqueness is structural, or to *tolerate* repeats with a nonce-misuse-resistant mode. There is no third option in which random draws stay unique forever.

<RunnableCode lang="js" title="The birthday bound in numbers: when do random 96-bit nonces collide?">{`
// Birthday bound: for an m-bit value, collisions get likely near 2^(m/2).
const m = 96;                              // GCM nonce size in bits

const fiftyPercentPoint = Math.pow(2, m / 2);   // ~2^48 draws for 96 bits
console.log('nonce size:', m, 'bits');
console.log('~50% collision near 2^' + (m / 2) + ' =', fiftyPercentPoint.toExponential(3), 'draws');

// Birthday approximation for n draws into a 2^nBits space:
function collisionProb(nBits, n) {
  const space = Math.pow(2, nBits);
  return 1 - Math.exp(-(n * (n - 1)) / (2 * space));
}

// SP 800-38D caps random 96-bit nonces at ~2^32 messages per key.
// Why 2^32 and not 2^48? Because the cap targets a TINY probability, not 50%:
const cap = Math.pow(2, 32);
console.log('at 2^32 draws, P(collision) =', collisionProb(96, cap).toExponential(3));
console.log('that is the safety margin SP 800-38D buys by capping at 2^32');
`}</RunnableCode>

**Back doors are undetectable by construction.** Dual_EC is the proof once more: a standardized generator can pass every statistical test and still be fully predictable to whoever chose its constants $P$ and $Q$ [@src-shumow2007]. You cannot test your way out of this, which is exactly why the field moved to verifiable constants and architected mixing rather than after-the-fact detection.

**Clone defense is mitigation, not elimination.** `fork()` and VM-snapshot cloning duplicate a *good* generator [@src-ristenpart2010, @src-everspaugh2014]. VM Generation ID counters, library fork detection, and forced reseeds shrink the window but do not close it; Ferguson's 2019 Windows RNG whitepaper is explicit that a guest cannot fully solve the snapshot problem on its own, because it may not even know it was cloned until it has already emitted duplicate output [@src-ferguson2019win].

And beneath all of it, the permanent gap Shannon left us in Section 2. His one-time pad is unconditionally secure but unscalable, a fresh truly-random key as long as every message. The CSPRNG is scalable but assumption-dependent. No cryptography closes that gap; we simply chose the scalable side and spend the rest of our effort defending it.

<Mermaid caption="The two-knobs diagnostic: classify any randomness bug in one glance">
flowchart TD
    START["A randomness bug"] --> K&#123;"Which property failed?"&#125;
    K -->|"predictable"| P&#123;"Where did it fail?"&#125;
    K -->|"repeated"| R&#123;"Where did it fail?"&#125;
    P -->|"generator"| PG["Netscape, Debian, Dual EC, DUHK, CTR_DRBG cache"]
    P -->|"role"| PR["BEAST (CBC IV), PuTTY (biased nonce)"]
    R -->|"generator"| RG["VM-snapshot clone"]
    R -->|"role"| RR["PS3, Bitcoin, GCM nonce reuse"]
</Mermaid>

> **Key idea:** You cannot certify unpredictability; you can only fail to falsify it. Min-entropy is estimated, not measured; statistical tests reject but never prove; the whole construction rests on an unproven hardness assumption; and back doors leave no trace in the output. The entire discipline of cryptographic randomness is the management of a risk you cannot directly measure. The surprise is not that randomness sometimes fails. It is that it works as well as it does.

If entropy is unmeasurable and unpredictability uncertifiable, where is randomness still actively failing today?

## 9. Where Randomness Is Still Failing

The architecture is settled; the frontier is not. Here are six places where cryptographic randomness is still an open engineering or standards problem in 2026, honestly contested rather than adjudicated.

**Early-boot entropy on embedded devices, IoT, and VMs.** A device that must generate a key seconds after first power-on, before its pool has filled, is the exact condition *Mining Your Ps and Qs* measured in 2012, and it still recurs in modern IoT field scans [@src-factorable]. Partial fixes exist: `getrandom` blocking once until seeded [@src-getrandom], saved-seed files such as FreeBSD's `/boot/entropy` [@src-freebsd-random], and mixing in `RDSEED` or jitter-based entropy at boot. None fully solves a cold device with no stored seed and no timing history.

**Validating entropy sources at fleet scale.** SP 800-90B health tests run per device, but proving that a noise-source *model* holds across millions of heterogeneous units, over years, across temperature swings and silicon aging, is unsolved [@src-sp80090b]. Fortuna's accumulation sidesteps *estimation* but not *validation*: you still have to argue the events carry entropy at all.

**Making nonce-misuse resistance the default.** Plain AES-GCM fails catastrophically on a single nonce repeat [@src-rfc8452], yet plain AES-GCM, not AES-GCM-SIV, remains the default AEAD offered by most general-purpose libraries. This is an adoption and standards gap, not a research one. TLS 1.3's fixed-IV construction and the pending SP 800-38D revision help at the protocol layer [@src-sp80038d-news], but general-purpose libraries still hand developers the sharp tool by default.

**Post-quantum's larger draws and wider side-channel surface.** ML-DSA and its siblings consume more randomness and add rejection-sampling and secret-dependent code paths, which means more surface for a weak generator or a timing leak to do damage [@src-fips204]. The [post-quantum migration](/blog/the-thirty-year-migration-ships-in-a-pip-install-how-post-qu/) is an opportunity to get seeding right and a risk if it is not.

**Trusting hardware RNGs after Dual_EC.** "Never trust a single hardware source" is settled *practice*, but a general, auditable way to *earn* trust in opaque silicon is open [@src-shumow2007]. Cohney's cache attack showed that even a correct-on-paper CTR_DRBG can leak through microarchitectural side channels, so "the math is right" is not the end of the story [@src-cohney2020].

**Formally verifying RNG code end to end.** Proofs typically assume "the implementation does not leak," but real code leaks through caches, forks, and uninitialized memory. Individual primitives have been verified, but a whole-pipeline proof under a realistic leakage model, from entropy source through DRBG to syscall, is not yet routine.

> **Note:** Notice the pattern across all six. The *expansion* problem, how to stretch a good seed into unlimited unpredictable output, is essentially solved: constant-time cores, forward secrecy, validated constructions. What remains open is *sourcing* (getting and validating real entropy, especially at boot and at fleet scale) and *trust* (how much to believe an opaque hardware source, and how to prove a whole implementation does not leak). The hard part was never the arithmetic. It is the physics and the epistemics around it.

Physical noise is quietly back in fashion, incidentally.<Sidenote>`RDSEED`, on-chip ring-oscillator sources, quantum RNGs, and TPMs are all seeing renewed use as entropy inputs. The difference from the pre-Dual_EC era is that they are *mixed in*, never trusted raw. The physics returned; the blind trust did not.</Sidenote> These are the field's problems. But you have to ship code tomorrow. What are the rules, concretely, per role, that keep you out of the Failure Catalog?

## 10. The Field Guide, Made Operational

Everything collapses to a short set of rules of the form "use X with these parameters in case Y." This is the two-knobs thesis turned into a checklist, and its mirror, the Common Misuse catalog, is simply the Failure Catalog seen from the developer's chair.

Rule zero for the generator: never roll your own, and never hand-seed. Always call the operating system's CSPRNG.

**Table 6: Generator, call this, not this.**

| Platform | Call this | Never this |
|----------|-----------|------------|
| Linux | `getrandom(2)`, `/dev/urandom` [@src-getrandom] | a hand-seeded userspace PRNG, blocking on `/dev/random` |
| BSD, macOS | `getentropy(3)`, `arc4random(3)` [@src-arc4random] | `rand()`, `random()` |
| Windows | `BCryptGenRandom`, `ProcessPrng` | anything seeded by hand (see the CNG post) |
| Language stdlib | Node `crypto.randomBytes`, Python `secrets` / `os.urandom`, Go `crypto/rand`, Java `SecureRandom` | `Math.random()`, Python `random`, Mersenne Twister, `java.util.Random`, `System.Random` |

Then, per role, the decision rules:

- **Key.** Draw from the OS CSPRNG at the primitive's exact key length: 32 bytes for AES-256 or ChaCha20. Nothing else.
- **CTR/GCM nonce.** Prefer a 96-bit monotonic counter (uniqueness by construction). If you must draw randomly, cap usage at fewer than $2^{32}$ messages per key. Never reuse a nonce under one key. If uniqueness cannot be guaranteed across distributed senders, use AES-GCM-SIV [@src-rfc8452].
- **CBC IV.** A fresh *unpredictable* value per message from the CSPRNG, never a counter and never the previous ciphertext block, or better, migrate to authenticated encryption.
- **Salt.** At least 16 bytes from the CSPRNG, unique per password, stored in plaintext. Never a global salt.
- **Signature nonce (k).** Use RFC 6979 deterministic k, or a hedged scheme; never a bare draw whose bias or repetition leaks the private key [@src-rfc6979].
- **Fork or VM clone.** Rely on kernel reseed (VM Generation ID) plus library fork detection, and force a reseed after any clone or snapshot before emitting security-critical output [@src-ferguson2019win].

**Table 7: The Common Misuse catalog (the mirror of Table 1).**

| Misuse | Knob and place broken | Named break |
|--------|----------------------|-------------|
| Seeding from `time()` or a PID | predictable, generator | Netscape [@src-netscape1996] |
| Removing or starving entropy | predictable, generator | Debian, Ps and Qs [@src-debian2008] |
| Reusing a GCM nonce under one key | repeated, role | GCM in TLS 2016 [@src-cve2016] |
| Predictable or chained CBC IV | predictable, role | BEAST [@src-cve2011] |
| Static or biased signature k | repeated or predictable, role | PS3, PuTTY [@src-putty2024] |
| Secret, short, or global salt | repeated, role | shared precomputed tables |
| Hand-seeding a userspace PRNG | predictable, generator | interface misuse |
| Trusting `RDRAND` alone | predictable, generator | the Dual_EC lesson [@src-shumow2007] |
| Mersenne Twister or `Math.random` for tokens | predictable, generator | statistical PRNG used for crypto [@src-mersenne1998] |
| No reseed after `fork()` or snapshot | repeated, generator | VM-reset [@src-ristenpart2010] |
| UUIDv4 as a security token | predictable, role | guessable token [@src-rfc9562] |

The meta-lesson runs through every row: historically the *interface*, not the algorithm, is the bug. Blocking `/dev/random`, seeding a userspace PRNG, forgetting to reseed after a fork; these are misuses of how you *reach* randomness, not weaknesses in the math. That is exactly why `getrandom` and `getentropy` were designed to be un-misusable.

> **Note:** If you are managing entropy in application code, mixing your own sources, maintaining a userspace pool, reseeding on a timer, you have almost certainly introduced a bug the OS already solved. The correct amount of RNG plumbing in an application is zero. Call `getrandom`, `getentropy`, `BCryptGenRandom`, or your language's CSPRNG wrapper, and stop [@src-getrandom, @src-arc4random].

> **Note:** A version-4 UUID is 122 random bits formatted for uniqueness, not unpredictability, and its specification never promised it would be unguessable or drawn from a CSPRNG [@src-rfc9562]. Using one as a password-reset token, session ID, or API key is betting your security on an implementation detail the standard does not guarantee. For anything security-sensitive, draw CSPRNG bytes directly [@src-rfc4086].

<Spoiler kind="solution" label="The one-paragraph version you can paste into a code review">
Draw every random value from the OS CSPRNG (`getrandom` / `getentropy` / `BCryptGenRandom` / `secrets` / `crypto/rand`), never a language `random()` or Mersenne Twister. Keys: exact key length from the CSPRNG. GCM nonces: 96-bit counter, or random with a hard cap below $2^{32}$ per key, and if senders are distributed use AES-GCM-SIV. CBC IVs: fresh unpredictable per message, or move to AEAD. Salts: at least 16 bytes, unique per password, stored in the clear. Signature nonces: RFC 6979 deterministic. After any `fork()` or VM snapshot, force a reseed before emitting keys or nonces. Reject any hand-rolled entropy pool.
</Spoiler>

The rules are short because the thesis is short: for every value you draw, name the property the role owes, and confirm the bytes came from the OS CSPRNG. Answer both and you are out of the catalog. Before the closing, here are the misconceptions that send good engineers back into it.

## 11. Frequently Asked Questions

The most expensive randomness bugs start as reasonable-sounding beliefs. Here are eight, corrected.

<FAQ title="Frequently asked questions about cryptographic randomness">
<FAQItem question="Is /dev/urandom insecure, or weaker than /dev/random?">
No. After the CSPRNG is seeded once at first boot, both devices draw from the same ChaCha-based generator and are cryptographically identical in the quality of their output; the only remaining difference is that `/dev/random` blocks until that first seeding while `/dev/urandom` does not [@src-donenfeld2022]. The old advice to prefer `/dev/random` for "more secure" keys was based on an entropy-depletion model that does not hold for a modern CSPRNG: a properly seeded generator does not run out of unpredictability by producing output. Use `getrandom()` and stop worrying about which device node to open [@src-random4man].
</FAQItem>
<FAQItem question="Must I wait for entropy before every read?">
No, only once. `getrandom()` blocks a single time at first boot until the pool is initially seeded, and then never blocks again for the life of the system [@src-getrandom]. Code that polls an entropy estimate before every draw, or loops waiting for `/dev/random`, is solving a problem that the blocking-once design already solved at the interface.
</FAQItem>
<FAQItem question="Does a nonce have to be random or unpredictable?">
It depends on the mode, and this is the most common confusion in the field. A CTR or GCM nonce must only be *unique*; unpredictability buys nothing there, and a counter is a fine, often safer, choice [@src-sp80038d]. The role that must be *unpredictable* is the CBC IV, whose predictability is exactly what BEAST exploited [@src-cve2011]. Same-looking value, different requirement, set by the mode.
</FAQItem>
<FAQItem question="Must a salt be secret?">
No. A salt is unique and random but stored in plaintext right next to the password hash. Its only job is to make identical passwords hash differently so one precomputed table cannot attack many accounts. Treating it as a secret, or reusing one global salt, misunderstands the role: uniqueness is what matters, not secrecy.
</FAQItem>
<FAQItem question="Are random GCM nonces safe forever?">
No. Random 96-bit nonces are bounded by the birthday problem, so a single key should encrypt fewer than about $2^{32}$ messages to keep the repeat probability negligible [@src-sp80038d]. Beyond that, use a monotonic counter, which cannot collide, or AES-GCM-SIV, which tolerates a repeat without catastrophic failure [@src-rfc8452].
</FAQItem>
<FAQItem question="Is RDRAND a back door, or is it enough on its own?">
Neither extreme is right. Do not trust `RDRAND` *alone*, because it is opaque silicon you cannot audit, but do not refuse it either. The correct posture, the one modern kernels take, is to mix its output into a pool alongside other sources so that no single component is the whole answer [@src-intel-drng]. That is the discipline Dual_EC taught the field [@src-shumow2007].
</FAQItem>
<FAQItem question="Does a UUIDv4 make a secure token?">
No. A version-4 UUID is built for uniqueness, and its specification does not require the bits to be unguessable or drawn from a CSPRNG [@src-rfc9562]. For session IDs, reset tokens, and API keys, draw CSPRNG bytes directly instead of relying on a UUID's format [@src-rfc4086].
</FAQItem>
<FAQItem question="Do more statistical tests make a generator more secure?">
No. Statistical suites can falsify a generator but never certify one. The Mersenne Twister passes the standard batteries and is completely broken after 624 outputs [@src-mersenne1998]. Test suites catch gross defects; they say nothing about whether an adversary can predict the next bit, which is the only property that matters for cryptography [@src-sp80022].
</FAQItem>
</FAQ>

## 12. Two Knobs, Two Places

Go back to where we started. Netscape, 1996: the RC4 and RSA math was flawless, and the keys were guessable because the seed was the clock and a couple of process IDs [@src-netscape1996]. Debian, 2008: the algorithm was fine, and roughly two years of keys collapsed to about 32,768 possibilities because one entropy input was removed [@src-debian2008]. You can now name both on sight: *predictable*, in the *generator*. That is the whole diagnosis, and it is the same diagnosis for every break in this article.

By now the two knobs and two places should lock together into a single picture. Every value you draw owes one of two properties, unpredictability or uniqueness, and either can be squandered in one of two places: the *generator* that mints the bits, or the *role* that spends them (key, nonce, IV, salt). That product, two properties across two places, is the entire fault space, and nothing in this article fell outside it.

What makes randomness uniquely treacherous is that none of those faults trips an alarm. Hand a cipher the wrong key and it errors; forge a MAC and the receiver rejects it. A predictable or repeated value sets off nothing at all: it clears every statistical test and validates cleanly, yet it is already broken for anyone who thought to recompute it. Each disaster we catalogued was one of those four faults, found only in hindsight.

Snap the catalog into the grid one last time. Netscape and Debian: predictable, generator. The PS3's static nonce and the Bitcoin wallets' repeated nonce: repeated, role [@src-fail0verflow2010, @src-android2013]. Dual_EC: predictable, generator, by design [@src-shumow2007]. GCM reuse in TLS: repeated, role [@src-cve2016]. PuTTY's biased P-521 nonce: predictable, role [@src-putty2024]. Four boxes, and every disaster in four decades fits in one of them.

<PullQuote>
Mastering randomness is not about finding a better source of magic. It is about knowing, for every value you draw, which of the two properties it owes and which of the two places can betray it.
</PullQuote>

That is the discipline. For every value you draw, name the property the role owes (unique? unpredictable? secret?) and confirm the generator is the OS CSPRNG. The generator half is essentially solved engineering; the failures that remain live in sourcing, in trust, and above all in the roles, where a perfect byte stream still gets you owned if you pour it into the wrong one.

This was Part 2 of the field guide. Part 1 established what "secure" means and why the definitions demand randomized encryption in the first place; later parts take the disciplined bytes you now know how to produce and build the modes, key-encapsulation mechanisms, and signatures that consume them. For the platform-specific instance of everything here, how Windows assembles its CSPRNG and defends it against cloning, the [Windows CSPRNG companion article](/blog/a-key-is-only-as-unguessable-as-the-dice-that-made-it-inside/) is waiting. Every one of those constructions will hand you a value to generate. Now you know the only two questions to ask of it.

<StudyGuide slug="randomness-in-cryptography-csprngs-nonces-ivs-salts" keyTerms={[
  { term: "CSPRNG", definition: "A generator whose output no efficient adversary can distinguish from uniform, even after seeing part of it." },
  { term: "DRBG", definition: "The deterministic expansion core that stretches a seed. It never creates entropy, only stretches it." },
  { term: "Min-entropy", definition: "The worst-case unpredictability of a source, set by the attacker's single best guess." },
  { term: "Next-bit test", definition: "A sequence is strong if no efficient algorithm predicts its next bit with advantage over one-half." },
  { term: "Nonce", definition: "A number used once. Must be unique per key; need not be unpredictable or secret." },
  { term: "Initialization Vector", definition: "A per-message starting value. Unpredictable and unique in CBC; unique-only (a nonce) in CTR and GCM." },
  { term: "Salt", definition: "A unique, random, public value that makes each password hash distinct. Not a secret." },
  { term: "Forward secrecy", definition: "A later state compromise cannot recover earlier output. Achieved by fast key erasure." },
  { term: "Reseeding", definition: "Folding fresh entropy into the state on a schedule or after a fork or snapshot." },
  { term: "Birthday bound", definition: "Collisions in an m-bit value get likely near 2 to the m over 2 draws. A provable uniqueness ceiling." }
]} flashcards={[
  { front: "The two knobs", back: "Unpredictability and uniqueness." },
  { front: "The two places", back: "The generator that makes the bits, and the role that consumes them." },
  { front: "Why did BEAST work?", back: "TLS 1.0 used a predictable chained CBC IV, and a CBC IV must be unpredictable." },
  { front: "Why is a single GCM nonce reuse fatal?", back: "It leaks the XOR of the two plaintexts and yields the polynomial equation that recovers the GHASH subkey H, enabling forgery." },
  { front: "RDRAND in one rule", back: "The kernel mixes it into the pool; applications never call it directly." }
]} questions={[
  { q: "Why does passing every statistical test not make a generator secure?", a: "Statistical quality is distributional; cryptographic security is computational unpredictability. The Mersenne Twister passes the batteries yet is recovered from 624 outputs." },
  { q: "Which role must be unpredictable, and which must only be unique?", a: "A CBC IV must be unpredictable and unique. A CTR or GCM nonce must only be unique." },
  { q: "How did RFC 6979 fix repeated-nonce disasters?", a: "By deriving the signature nonce deterministically from the private key and message, removing the RNG from the signing path." },
  { q: "What did Dual_EC prove about detectability?", a: "A standardized generator can pass every test and still be predictable to whoever chose its constants. Back doors are undetectable from output." },
  { q: "What is the one operational rule for the generator?", a: "Never roll your own and never hand-seed. Always call the OS CSPRNG." }
]} />
