<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Parag Mali - tag: csprng</title><description>Posts tagged csprng.</description><link>https://paragmali.com/</link><language>en-US</language><lastBuildDate>Sat, 25 Jul 2026 08:57:43 GMT</lastBuildDate><atom:link href="https://paragmali.com/tags/csprng/rss.xml" rel="self" type="application/rss+xml"/><item><title>Predictable or Repeated: The Only Two Ways Cryptographic Randomness Betrays You</title><link>https://paragmali.com/blog/predictable-or-repeated-the-only-two-ways-cryptographic-rand/</link><guid isPermaLink="true">https://paragmali.com/blog/predictable-or-repeated-the-only-two-ways-cryptographic-rand/</guid><description>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.</description><pubDate>Wed, 08 Jul 2026 03:29:38 GMT</pubDate><content:encoded>
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.
&lt;h2&gt;1. Two Lines of Code&lt;/h2&gt;
&lt;p&gt;In 1996, two Berkeley graduate students needed only seconds to reconstruct the secret keys protecting Netscape&apos;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&apos;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].&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Look closely at Netscape first. Ian Goldberg and David Wagner reverse-engineered the Navigator&apos;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 &lt;em&gt;was&lt;/em&gt; the secret, and the secret was enumerable. Nothing about RC4 or RSA had to break; the attacker simply recomputed what the generator would produce.&lt;/p&gt;
&lt;p&gt;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.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]. 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.&lt;/p&gt;

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.
&lt;p&gt;Neither of these was a broken algorithm. Both were broken randomness, and the reason they feel like the same story is that they &lt;em&gt;are&lt;/em&gt; 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.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; 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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; There are only two ways a random value fails: it is &lt;em&gt;predictable&lt;/em&gt; when it needed to be &lt;em&gt;unpredictable&lt;/em&gt;, or &lt;em&gt;repeated&lt;/em&gt; when it needed to be &lt;em&gt;unique&lt;/em&gt;. And each failure happens in exactly one of two places: the &lt;em&gt;generator&lt;/em&gt; that manufactures the bits, or the &lt;em&gt;role&lt;/em&gt; 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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is Part 2 of a field guide for protocol designers. Part 1 argued that the standard security definition, IND-CPA, &lt;em&gt;mandates&lt;/em&gt; 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 &lt;em&gt;is&lt;/em&gt; 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;2. Why Randomness Became a Primitive&lt;/h2&gt;
&lt;p&gt;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 &lt;em&gt;key&lt;/em&gt;. That single move relocated all of a system&apos;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.&lt;/p&gt;
&lt;p&gt;Sixty-six years later, Claude Shannon made the requirement mathematical. In &lt;em&gt;Communication Theory of Secrecy Systems&lt;/em&gt; (1949), he proved that the one-time pad achieves &lt;em&gt;perfect&lt;/em&gt; secrecy: a ciphertext reveals nothing whatsoever about the plaintext, not even to an adversary with unlimited computing power [@src-shannon1949].&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;

*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&apos;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.
&lt;p&gt;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 &lt;em&gt;middle-square method&lt;/em&gt;, 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.&lt;/p&gt;

&quot;Any one who considers arithmetical methods of producing random digits is, of course, in a state of sin.&quot; -- John von Neumann, 1951 [@src-vonneumann1951]
&lt;p&gt;The point was not that deterministic recipes are useless, but that arithmetic alone only &lt;em&gt;simulates&lt;/em&gt; 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.&lt;/p&gt;
&lt;p&gt;So for a while, randomness was treated as a scarce physical resource you harvested and rationed. In 1955 the RAND Corporation published &lt;em&gt;A Million Random Digits with 100,000 Normal Deviates&lt;/em&gt;, 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].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]. 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.&lt;/p&gt;

Von Neumann&apos;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.
&lt;p&gt;The conceptual breakthrough that made a &lt;em&gt;deterministic&lt;/em&gt; generator respectable arrived in the early 1980s. Andrew Yao (1982) and Manuel Blum with Silvio Micali (1984) redefined what &quot;random enough&quot; should mean for cryptography [@src-yao1982, @src-blummicali1984]. Their answer was not statistical but &lt;em&gt;computational&lt;/em&gt;: 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.&lt;/p&gt;

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.
&lt;p&gt;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 &lt;em&gt;bounded&lt;/em&gt; 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 &lt;em&gt;looked&lt;/em&gt; random, and walked straight into the first disaster.&lt;/p&gt;
&lt;h2&gt;3. Statistical Randomness That Was Not Unpredictable&lt;/h2&gt;
&lt;p&gt;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 &lt;em&gt;completely predictable&lt;/em&gt;. 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 &quot;looks random&quot; and &quot;is unguessable,&quot; is the mistake that organizes the rest of this article.&lt;/p&gt;
&lt;p&gt;Start with the simplest offender. A &lt;em&gt;linear congruential generator&lt;/em&gt; (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.&lt;/p&gt;
&lt;p&gt;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 &lt;em&gt;linear&lt;/em&gt; 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.&lt;/p&gt;

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.
&lt;p&gt;The first genuinely &lt;em&gt;cryptographic&lt;/em&gt; 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.&lt;/p&gt;
&lt;p&gt;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 &lt;em&gt;hard-coded&lt;/em&gt; 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].&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;{`
// The cipher is never touched. Only the seed is guessed.
// We model a small seed space, like Netscape&apos;s time+PID+PPID
// or Debian&apos;s collapsed ~2^15..2^18 keyspace.&lt;/p&gt;
&lt;p&gt;function prng(seed) {                 // a toy stand-in for the real generator
  let s = seed &amp;gt;&amp;gt;&amp;gt; 0;
  s = (Math.imul(1103515245, s) + 12345) &amp;gt;&amp;gt;&amp;gt; 0;   // classic LCG constants
  return s;                            // the victim&apos;s &quot;key material&quot;
}&lt;/p&gt;
&lt;p&gt;const SEED_BITS = 18;                  // pretend this is all the entropy there was
const seedSpace = 1 &amp;lt;&amp;lt; SEED_BITS;      // 262144 possibilities
const secretSeed = Math.floor(Math.random() * seedSpace);
const victimKey = prng(secretSeed);    // a &quot;256-bit key&quot; -- but only 18 bits of real entropy&lt;/p&gt;
&lt;p&gt;// Attacker knows the seed came from a small space and enumerates it:
let tries = 0, recovered = -1;
for (let guess = 0; guess &amp;lt; seedSpace; guess++) {
  tries++;
  if (prng(guess) === victimKey) { recovered = guess; break; }
}&lt;/p&gt;
&lt;p&gt;console.log(&apos;seed space:&apos;, seedSpace, &apos;possibilities&apos;);
console.log(&apos;attacker tries needed:&apos;, tries);
console.log(&apos;key recovered:&apos;, recovered === secretSeed);
console.log(&apos;note: a 256-bit key with 18 bits of seed entropy is an 18-bit key&apos;);
`}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; 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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This is the first place your mental model has to shift. Statistical quality and cryptographic unpredictability are &lt;em&gt;different properties&lt;/em&gt;, 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 &lt;em&gt;computational&lt;/em&gt; claim about what an adversary can compute, not a &lt;em&gt;statistical&lt;/em&gt; 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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The theorists had, in fact, already named the generator that gets this right.&lt;/p&gt;

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.
&lt;p&gt;The Mersenne Twister is not a villain, incidentally.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. &quot;Wrong for crypto&quot; is not &quot;wrong for everything.&quot; 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.&lt;/p&gt;
&lt;h2&gt;4. CSPRNG Design, Generation by Generation&lt;/h2&gt;
&lt;p&gt;No single insight fixed cryptographic randomness. Instead there was a ratchet. Each generation of generator worked until a &lt;em&gt;named&lt;/em&gt; 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 &lt;em&gt;generator&lt;/em&gt; 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.&lt;/p&gt;

NIST&apos;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). &quot;DRBG&quot; 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.

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&apos;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.
&lt;p&gt;&lt;strong&gt;Generation 0, physical and tabular (1949 to 1957).&lt;/strong&gt; 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&apos;s middle-square degenerated to short cycles [@src-vonneumann1951].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generation 1, statistical PRNGs with ad-hoc seeding (1960s to 1998).&lt;/strong&gt; 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].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generation 2, block-cipher PRNGs (ANSI X9.17 and X9.31, 1985 to 1998).&lt;/strong&gt; 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].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generation 3, OS entropy pools (Linux /dev/random, Ts&apos;o 1994).&lt;/strong&gt; The operating system harvests timing from interrupts, keystrokes, and disk activity into a hashed pool, &lt;em&gt;estimates&lt;/em&gt; 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 &lt;code&gt;/dev/random&lt;/code&gt;-versus-&lt;code&gt;/dev/urandom&lt;/code&gt; confusion; and low boot entropy breaks real keys at scale. In 2012, &lt;em&gt;Mining Your Ps and Qs&lt;/em&gt; 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 &lt;em&gt;Ron was wrong, Whit is right&lt;/em&gt; 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&apos;s government-certified Citizen Digital Certificate smartcards, hardware that had passed FIPS and Common Criteria evaluation [@src-smartfacts, @src-coppersmith2013]. The Linux RNG&apos;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].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generation 4, standardized DRBGs (NIST SP 800-90A, 2006 onward).&lt;/strong&gt; 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 &lt;em&gt;same document&lt;/em&gt;, the field&apos;s most instructive cautionary tale: Dual_EC_DRBG. It had a documented output &lt;em&gt;bias&lt;/em&gt;, distinguishable from uniform independently of any back door (Schoenmakers and Sidorenko, 2006) [@src-schoenmakers2006], and, far worse, a &lt;em&gt;trapdoor&lt;/em&gt;: 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&apos;s ScreenOS in 2015 [@src-checkoway2016]. A generator can pass every statistical test and still be &lt;em&gt;owned&lt;/em&gt; by whoever chose its constants.&lt;/p&gt;

flowchart TD
    G0[&quot;Gen 0: physical and tabular (1949 to 1957)&quot;] --&amp;gt;|&quot;a table makes no fresh secret&quot;| G1[&quot;Gen 1: statistical PRNGs, ad-hoc seeds&quot;]
    G1 --&amp;gt;|&quot;MT 624 outputs, Netscape seed&quot;| G2[&quot;Gen 2: block-cipher PRNGs (X9.17, X9.31)&quot;]
    G2 --&amp;gt;|&quot;DUHK hard-coded key&quot;| G3[&quot;Gen 3: OS entropy pools (Linux, 1994)&quot;]
    G3 --&amp;gt;|&quot;boot starvation, Mining Ps and Qs&quot;| G4[&quot;Gen 4: standardized DRBGs (SP 800-90A)&quot;]
    G4 --&amp;gt;|&quot;Dual EC back door&quot;| G5[&quot;Gen 5: accumulation, ChaCha20, getrandom&quot;]
    G5 --&amp;gt;|&quot;fork or VM-snapshot clone&quot;| G6[&quot;Gen 6: converged validated architecture (90B, 90A, 90C)&quot;]
&lt;p&gt;&lt;strong&gt;Generation 5, entropy accumulation, stream-cipher cores, and non-blocking syscalls (1999 to 2022).&lt;/strong&gt; Yarrow (1999) [@src-yarrow1999] led to Fortuna (2003), whose 32 staggered pools &lt;em&gt;abolish entropy estimation&lt;/em&gt; altogether [@src-fortuna2003, @src-freebsd-random]. The output core moved to ChaCha20, Bernstein&apos;s refinement of his earlier Salsa20 cipher [@src-chacha, @src-snuffle]: OpenBSD re-cored &lt;code&gt;arc4random&lt;/code&gt; 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].Pin this milestone at Linux 4.8 (October 2016), commit e192be9d9a30555. A &quot;5.6 / 2020&quot; figure sometimes cited refers to an unrelated change in how &lt;code&gt;/dev/random&lt;/code&gt; handles &lt;code&gt;O_NONBLOCK&lt;/code&gt;, not the ChaCha20 output core [@src-linux48]. Jason Donenfeld&apos;s 5.17 to 5.18 rewrite in 2022 added BLAKE2s extraction, per-CPU fast key erasure, and unified &lt;code&gt;/dev/random&lt;/code&gt; with &lt;code&gt;/dev/urandom&lt;/code&gt; [@src-donenfeld2022]. The &lt;code&gt;getrandom(2)&lt;/code&gt; syscall (2014) blocks &lt;em&gt;once&lt;/em&gt; at boot until the pool is seeded, then never again [@src-getrandom]. One hazard the generator alone cannot fix remained: &lt;code&gt;fork()&lt;/code&gt; and VM-snapshot cloning duplicate the entire generator state and re-emit identical values, a &lt;em&gt;uniqueness&lt;/em&gt; failure measured in the wild by Ristenpart and Yilek (2010) and Everspaugh et al. (2014) [@src-ristenpart2010, @src-everspaugh2014].OpenBSD kept the name &lt;code&gt;arc4random&lt;/code&gt; for source compatibility even though the &quot;RC4&quot; it originally stood for is long gone; since 2014 the bytes come from ChaCha20 [@src-arc4random].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generation 6, the converged validated architecture (2018 to 2026, current state of the art).&lt;/strong&gt; A &lt;em&gt;validated&lt;/em&gt; entropy source (SP 800-90B) [@src-sp80090b] seeds a &lt;em&gt;computational&lt;/em&gt; 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.&lt;/p&gt;

flowchart TD
    K[&quot;State: Key and counter block V&quot;] --&amp;gt; G[&quot;Generate: AES-CTR keystream from Key and V&quot;]
    G --&amp;gt; O[&quot;Hand the requested bytes to the caller&quot;]
    G --&amp;gt; U[&quot;Update: re-derive a fresh Key and V&quot;]
    U --&amp;gt; K2[&quot;New state replaces the old one, so prior output cannot be recomputed (backtracking resistance)&quot;]
    K2 --&amp;gt; R[&quot;After up to 2 to the 48 requests, reseed from the entropy source&quot;]
    R --&amp;gt; K
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Table 1: The Failure Catalog.&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Break&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Knob broken&lt;/th&gt;
&lt;th&gt;Place&lt;/th&gt;
&lt;th&gt;Root cause&lt;/th&gt;
&lt;th&gt;Lesson&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Netscape SSL [@src-netscape1996]&lt;/td&gt;
&lt;td&gt;1996&lt;/td&gt;
&lt;td&gt;predictable&lt;/td&gt;
&lt;td&gt;generator&lt;/td&gt;
&lt;td&gt;Seed was time + PID + PPID&lt;/td&gt;
&lt;td&gt;Never seed from the clock or a PID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debian OpenSSL [@src-debian2008]&lt;/td&gt;
&lt;td&gt;2008&lt;/td&gt;
&lt;td&gt;predictable&lt;/td&gt;
&lt;td&gt;generator&lt;/td&gt;
&lt;td&gt;Entropy input removed, keyspace ~2^15&lt;/td&gt;
&lt;td&gt;Never delete an entropy source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PS3 signing key [@src-fail0verflow2010]&lt;/td&gt;
&lt;td&gt;2010&lt;/td&gt;
&lt;td&gt;repeated&lt;/td&gt;
&lt;td&gt;role (nonce)&lt;/td&gt;
&lt;td&gt;Same ECDSA nonce k on every signature&lt;/td&gt;
&lt;td&gt;A signature nonce must be unique&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android Bitcoin [@src-android2013]&lt;/td&gt;
&lt;td&gt;2013&lt;/td&gt;
&lt;td&gt;repeated&lt;/td&gt;
&lt;td&gt;role (nonce)&lt;/td&gt;
&lt;td&gt;SecureRandom mis-init repeated k&lt;/td&gt;
&lt;td&gt;Seed the RNG before drawing a nonce&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mining Ps and Qs [@src-heninger2012]&lt;/td&gt;
&lt;td&gt;2012&lt;/td&gt;
&lt;td&gt;predictable&lt;/td&gt;
&lt;td&gt;generator&lt;/td&gt;
&lt;td&gt;Keys generated before pool seeded&lt;/td&gt;
&lt;td&gt;Do not make keys at cold boot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dual_EC_DRBG [@src-shumow2007]&lt;/td&gt;
&lt;td&gt;2007-15&lt;/td&gt;
&lt;td&gt;predictable&lt;/td&gt;
&lt;td&gt;generator&lt;/td&gt;
&lt;td&gt;Trapdoor in chosen points P, Q&lt;/td&gt;
&lt;td&gt;Choose constants verifiably; never trust one source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VM-snapshot reset [@src-ristenpart2010]&lt;/td&gt;
&lt;td&gt;2010-14&lt;/td&gt;
&lt;td&gt;repeated&lt;/td&gt;
&lt;td&gt;generator&lt;/td&gt;
&lt;td&gt;Snapshot clones generator state&lt;/td&gt;
&lt;td&gt;Force a reseed after any clone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DUHK [@src-duhk2018]&lt;/td&gt;
&lt;td&gt;2017&lt;/td&gt;
&lt;td&gt;predictable&lt;/td&gt;
&lt;td&gt;generator&lt;/td&gt;
&lt;td&gt;Hard-coded X9.31 seed key&lt;/td&gt;
&lt;td&gt;Never hard-code the seed key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BEAST [@src-cve2011]&lt;/td&gt;
&lt;td&gt;2011&lt;/td&gt;
&lt;td&gt;predictable&lt;/td&gt;
&lt;td&gt;role (CBC IV)&lt;/td&gt;
&lt;td&gt;TLS 1.0 chained the previous ciphertext block as IV&lt;/td&gt;
&lt;td&gt;A CBC IV must be unpredictable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GCM nonce reuse in TLS [@src-cve2016]&lt;/td&gt;
&lt;td&gt;2016&lt;/td&gt;
&lt;td&gt;repeated&lt;/td&gt;
&lt;td&gt;role (nonce)&lt;/td&gt;
&lt;td&gt;Repeated 96-bit nonce under one key&lt;/td&gt;
&lt;td&gt;Never reuse a GCM nonce per key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CTR_DRBG cache attack [@src-cohney2020]&lt;/td&gt;
&lt;td&gt;2020&lt;/td&gt;
&lt;td&gt;predictable&lt;/td&gt;
&lt;td&gt;generator&lt;/td&gt;
&lt;td&gt;AES-table cache timing leaks state&lt;/td&gt;
&lt;td&gt;Prefer a constant-time core&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PuTTY P-521 [@src-putty2024]&lt;/td&gt;
&lt;td&gt;2024&lt;/td&gt;
&lt;td&gt;predictable&lt;/td&gt;
&lt;td&gt;role (nonce)&lt;/td&gt;
&lt;td&gt;Biased k, first 9 bits always zero [@src-putty-oss]&lt;/td&gt;
&lt;td&gt;A nonce must be uniform or derived&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;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&apos;s chair.&lt;/p&gt;

Dual_EC did more than embarrass a standard; it changed how the field designs generators. Before it, the working assumption was &quot;trust the standardized construction.&quot; After it, the assumption became &quot;mix multiple sources and trust no single one, and choose your constants so anyone can verify there is no hidden structure.&quot; That is why modern kernels fold hardware RNG output *into* a pool rather than using it raw, why NIST&apos;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].&lt;p&gt;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.
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The most surprising fix of all runs the other direction. After the PlayStation 3, the Bitcoin wallets, and eventually PuTTY, all showed that a &lt;em&gt;random&lt;/em&gt; per-signature nonce is catastrophic when it repeats or leaks,Recovery differs by failure mode. A &lt;em&gt;repeated&lt;/em&gt; nonce means two signatures share $k$, so simple algebra recovers the private key, as on the PlayStation 3. A &lt;em&gt;biased&lt;/em&gt; nonce, like PuTTY&apos;s nine always-zero leading bits, falls to a lattice attack on the &lt;em&gt;Hidden Number Problem&lt;/em&gt;: each signature leaks a few key bits, and lattice reduction solves for the key once about 60 accumulate [@src-putty-oss]. RFC 6979 (2013) responded by &lt;em&gt;removing&lt;/em&gt; the randomness: it derives the ECDSA nonce deterministically from the private key and the message, so that signers, in the RFC&apos;s own words,&lt;/p&gt;

&quot;...do not need access to a source of high-quality randomness.&quot; -- RFC 6979, on deterministic ECDSA nonces [@src-rfc6979]
&lt;p&gt;The fix for a randomness disaster was &lt;em&gt;less&lt;/em&gt; 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.&lt;/p&gt;
&lt;h2&gt;5. The Modern Architecture, and the Two Layers Underneath It&lt;/h2&gt;
&lt;p&gt;Every fix in Section 4 was pushing toward one shape, and here it is. A &lt;em&gt;true entropy source&lt;/em&gt; seeds a &lt;em&gt;computational DRBG&lt;/em&gt; 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.&lt;/p&gt;

flowchart LR
    E[&quot;Entropy sources: interrupts, RDSEED, TPM&quot;] --&amp;gt; A[&quot;Accumulate and condition&quot;]
    A --&amp;gt; D[&quot;DRBG expansion: AES-CTR or ChaCha20&quot;]
    D --&amp;gt; I[&quot;OS interface: getrandom, arc4random, BCryptGenRandom&quot;]
    I --&amp;gt; R[&quot;Consumer roles: key, nonce, IV, salt&quot;]
    D -.-&amp;gt;|&quot;reseed and fast key erasure&quot;| A
&lt;p&gt;Read it left to right. Physical noise (interrupt timing, the &lt;code&gt;RDSEED&lt;/code&gt; 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.&lt;/p&gt;
&lt;p&gt;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 &lt;em&gt;create&lt;/em&gt; entropy; it stretches it. Garbage seed in, garbage out, which is why the entire left side of the diagram exists.&lt;/p&gt;

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&apos;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.

Periodically folding fresh entropy into the generator&apos;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.

sequenceDiagram
    participant S as Generator state
    participant O as Output
    S-&amp;gt;&amp;gt;O: emit one block of keystream
    S-&amp;gt;&amp;gt;S: overwrite the key with fresh keystream
    Note over S: the old key is gone, so earlier output cannot be recomputed
&lt;p&gt;Now the correction that clears away half the folklore in this field: the difference between an &lt;em&gt;OS interface&lt;/em&gt; and a &lt;em&gt;CPU instruction&lt;/em&gt;. These are two different layers, and conflating them is the source of endless bad advice.&lt;/p&gt;
&lt;p&gt;An &lt;strong&gt;OS-provided interface&lt;/strong&gt; is what your code should call: &lt;code&gt;getrandom(2)&lt;/code&gt; or &lt;code&gt;/dev/urandom&lt;/code&gt; on Linux, &lt;code&gt;getentropy(3)&lt;/code&gt; or &lt;code&gt;arc4random(3)&lt;/code&gt; on BSD and macOS, &lt;code&gt;BCryptGenRandom&lt;/code&gt; or &lt;code&gt;ProcessPrng&lt;/code&gt; on Windows [@src-getrandom, @src-arc4random]. On Linux, &lt;code&gt;getrandom&lt;/code&gt; blocks &lt;em&gt;once&lt;/em&gt; until the pool is first seeded and then never again, which is what finally ended the &lt;code&gt;/dev/random&lt;/code&gt;-versus-&lt;code&gt;/dev/urandom&lt;/code&gt; dilemma at the interface level [@src-getrandom, @src-lwn2014].&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;CPU instruction&lt;/strong&gt; is what the operating system &lt;em&gt;consumes&lt;/em&gt;, not what you call. Intel&apos;s &lt;code&gt;RDRAND&lt;/code&gt; returns the output of an on-die hardware DRBG; &lt;code&gt;RDSEED&lt;/code&gt; 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 &lt;em&gt;mixes&lt;/em&gt; them into its pool alongside other sources, precisely the post-Dual_EC discipline: never let one opaque component be the whole answer [@src-donenfeld2022].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The rule is a single sentence: applications call the OS interface; only the kernel touches the CPU instruction. If you find yourself calling &lt;code&gt;RDRAND&lt;/code&gt; 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 &lt;code&gt;getrandom&lt;/code&gt;, &lt;code&gt;getentropy&lt;/code&gt;, or &lt;code&gt;BCryptGenRandom&lt;/code&gt;, and let the kernel decide how much to trust the hardware [@src-getrandom, @src-donenfeld2022].&lt;/p&gt;
&lt;/blockquote&gt;

flowchart TD
    APP[&quot;Your application&quot;] --&amp;gt;|&quot;calls this&quot;| OSI[&quot;OS interface: getrandom, arc4random, BCryptGenRandom&quot;]
    OSI --&amp;gt; POOL[&quot;Kernel entropy pool and DRBG&quot;]
    POOL --&amp;gt; OSI
    RDRAND[&quot;CPU RDRAND: on-die DRBG output&quot;] -.-&amp;gt;|&quot;mixed in, never trusted alone&quot;| POOL
    RDSEED[&quot;CPU RDSEED: seed-grade entropy&quot;] -.-&amp;gt;|&quot;mixed in, never trusted alone&quot;| POOL

So why not skip the pool and read `RDRAND` directly? Because mixing is what turns &quot;trust no single source&quot; 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.
&lt;p&gt;Seen this way, the generator&apos;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.&lt;/p&gt;
&lt;p&gt;Windows implements this same pipeline with a CTR_DRBG tree behind &lt;code&gt;BCryptGenRandom&lt;/code&gt; and &lt;code&gt;ProcessPrng&lt;/code&gt;; the platform-specific internals, including how the kernel seeds per-processor generators and defends against VM cloning, are the subject of a dedicated &lt;a href=&quot;https://paragmali.com/blog/a-key-is-only-as-unguessable-as-the-dice-that-made-it-inside/&quot; rel=&quot;noopener&quot;&gt;companion article on the Windows CSPRNG&lt;/a&gt;, and the &lt;a href=&quot;https://paragmali.com/blog/cng-architecture-bcrypt-ncrypt-ksps/&quot; rel=&quot;noopener&quot;&gt;CNG API surface&lt;/a&gt; itself lives in a companion post. We will not re-derive them here.&lt;/p&gt;
&lt;p&gt;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?&lt;/p&gt;
&lt;h2&gt;6. What Ships in 2026, and the Four Roles&lt;/h2&gt;
&lt;p&gt;By 2026 the generator architecture is settled engineering, and most real-world breaks happen &lt;em&gt;after&lt;/em&gt; 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&apos;s heart of the matter: the four roles and the exact property mix each one demands.&lt;/p&gt;
&lt;p&gt;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 &lt;code&gt;getrandom&lt;/code&gt; and OpenBSD or macOS &lt;code&gt;arc4random&lt;/code&gt; [@src-donenfeld2022, @src-arc4random].&lt;/p&gt;
&lt;p&gt;The 2025 capstone is SP 800-90C, which stops treating &quot;the entropy source&quot; and &quot;the DRBG&quot; 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].&lt;/p&gt;
&lt;p&gt;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].&lt;/p&gt;
&lt;p&gt;Post-quantum cryptography makes randomness &lt;em&gt;more&lt;/em&gt; 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 &lt;a href=&quot;https://paragmali.com/blog/post-quantum-cryptography-on-windows-the-thirty-year-migrati/&quot; rel=&quot;noopener&quot;&gt;post-quantum migration guides&lt;/a&gt; for these algorithms treat the CSPRNG as a first-class dependency.&lt;/p&gt;
&lt;p&gt;Now the centerpiece. There are exactly four roles a random value plays in cryptography, and each demands a &lt;em&gt;different&lt;/em&gt; combination of three properties: &lt;em&gt;uniqueness&lt;/em&gt;, &lt;em&gt;unpredictability&lt;/em&gt;, and &lt;em&gt;secrecy&lt;/em&gt;. Getting the combination wrong is how a perfect generator still gets you owned.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Table 2: The four roles and what each one owes.&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;Unique?&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;Unpredictable?&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;Secret?&lt;/th&gt;
&lt;th&gt;Canonical failure if violated&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Key&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;yes&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;yes&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Guessable key: Netscape 1996, Debian 2008 [@src-debian2008]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CBC IV&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;yes&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;yes&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;no&lt;/td&gt;
&lt;td&gt;Predictable IV enabled BEAST [@src-cve2011]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CTR/GCM nonce&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;yes&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;no&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;no&lt;/td&gt;
&lt;td&gt;Repeated nonce leaks the auth key: GCM in TLS 2016 [@src-cve2016]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Salt&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;yes&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;no (but random)&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;strong&gt;no&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reused or global salt enables shared precomputed tables&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Read the differences, because they are the whole point.&lt;/p&gt;

A *nonce* is a &quot;number used once.&quot; 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.

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 &quot;IV&quot; is really a *nonce*, and needs only to be *unique*. The word &quot;IV&quot; therefore does not name a fixed requirement; the *mode* names the requirement. This is the single most common source of confusion in applied randomness.
&lt;p&gt;The nonce-versus-IV correction deserves stating precisely, because folklore gets it backwards. The &lt;em&gt;same value slot&lt;/em&gt; is called an &quot;IV&quot; or a &quot;nonce&quot; depending on the mode, and the &lt;em&gt;mode&lt;/em&gt; dictates the requirement.&lt;/p&gt;
&lt;p&gt;A CBC IV must be unpredictable &lt;em&gt;and&lt;/em&gt; unique. TLS 1.0 chained CBC records by using the previous ciphertext block as the next IV, which is &lt;em&gt;predictable&lt;/em&gt;, 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].&lt;/p&gt;
&lt;p&gt;A CTR or GCM nonce, by contrast, needs &lt;em&gt;only&lt;/em&gt; 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&apos;s forbidden attack), enabling universal forgery. That failure was measured across live TLS servers in 2016 under the name &quot;Nonce-Disrespecting Adversaries&quot; and captured for IBM Domino as CVE-2016-0270 [@src-cve2016].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; GCM&apos;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 &lt;em&gt;and&lt;/em&gt;, 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).&lt;/p&gt;
&lt;/blockquote&gt;

flowchart TD
    V[&quot;A value in the IV or nonce slot&quot;] --&amp;gt; Q{&quot;Which mode?&quot;}
    Q --&amp;gt;|&quot;CBC&quot;| CBC[&quot;Must be unpredictable AND unique. A predictable IV enabled BEAST&quot;]
    Q --&amp;gt;|&quot;CTR or GCM&quot;| CTR[&quot;Must be unique only. Reuse leaks the authentication key&quot;]
&lt;p&gt;The last role, the salt, is where a different myth does the damage.&lt;/p&gt;

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.
&lt;p&gt;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].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 &lt;a href=&quot;https://paragmali.com/blog/rotating-every-cipher-schannel-and-the-twenty-year-algorithm/&quot; rel=&quot;noopener&quot;&gt;cipher agility&lt;/a&gt; and is told in the SChannel post.&lt;/p&gt;
&lt;p&gt;{`
const crypto = require(&apos;crypto&apos;);&lt;/p&gt;
&lt;p&gt;// KEY: secret + unpredictable + high-entropy. Straight from the OS CSPRNG.
const key = crypto.randomBytes(32);          // 256 bits, for AES-256 or ChaCha20&lt;/p&gt;
&lt;p&gt;// 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;
}&lt;/p&gt;
&lt;p&gt;// SALT: unique + random, stored in the clear next to the password hash.
const salt = crypto.randomBytes(16);
console.log(&apos;salt is PUBLIC, stored with the hash:&apos;, salt.toString(&apos;hex&apos;));&lt;/p&gt;
&lt;p&gt;// The WRONG lines, for contrast:
//   const nonce = Buffer.alloc(12);                 // reused -&amp;gt; repeated knob, in a role
//   const salt  = Buffer.from(String(Math.random())); // Math.random is not a CSPRNG -&amp;gt; predictable&lt;/p&gt;
&lt;p&gt;console.log(&apos;key bytes:&apos;, key.length, &apos;nonce bytes:&apos;, nextNonce().length);
console.log(&apos;same generator, three different disciplines&apos;);
`}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; 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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;7. CTR_DRBG, Fortuna, and ChaCha, and Hardware Versus Software&lt;/h2&gt;
&lt;p&gt;&quot;Which cipher makes the best RNG?&quot; is the wrong question. The real axis is &lt;em&gt;architecture&lt;/em&gt;: how you &lt;em&gt;source&lt;/em&gt; entropy and how you &lt;em&gt;expand&lt;/em&gt; it. Get that framing right and the &quot;competitors&quot; turn out to be solving different halves of the same problem.&lt;/p&gt;
&lt;p&gt;Start with a category correction. Fortuna is largely an &lt;em&gt;accumulator&lt;/em&gt;, the sourcing half: its contribution is how it collects and schedules entropy. CTR_DRBG and ChaCha20 are &lt;em&gt;expanders&lt;/em&gt;, the expansion half: their contribution is how they stretch a seed. A real system runs an accumulator front-end feeding an expander core, so &quot;CTR_DRBG versus Fortuna&quot; is partly a comparison between a fuel tank and an engine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Table 3: Deployed generators, head to head.&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;CTR_DRBG&lt;/th&gt;
&lt;th&gt;HMAC_DRBG&lt;/th&gt;
&lt;th&gt;Fortuna&lt;/th&gt;
&lt;th&gt;ChaCha20&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Output cost&lt;/td&gt;
&lt;td&gt;~1 AES per 128 bits&lt;/td&gt;
&lt;td&gt;~1 HMAC per block&lt;/td&gt;
&lt;td&gt;cipher core per block&lt;/td&gt;
&lt;td&gt;1 ChaCha permutation per 512 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State size&lt;/td&gt;
&lt;td&gt;Key + 128-bit V&lt;/td&gt;
&lt;td&gt;two hash-length values&lt;/td&gt;
&lt;td&gt;32 pools + generator key&lt;/td&gt;
&lt;td&gt;256-bit key, per CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forward secrecy&lt;/td&gt;
&lt;td&gt;Update re-derives (Key, V)&lt;/td&gt;
&lt;td&gt;Update re-derives (Key, V)&lt;/td&gt;
&lt;td&gt;frequent generator rekey&lt;/td&gt;
&lt;td&gt;fast key erasure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Side channel&lt;/td&gt;
&lt;td&gt;AES-table lookups can leak [@src-cohney2020]&lt;/td&gt;
&lt;td&gt;HMAC, low risk&lt;/td&gt;
&lt;td&gt;depends on core&lt;/td&gt;
&lt;td&gt;constant-time by design [@src-chacha]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entropy philosophy&lt;/td&gt;
&lt;td&gt;assumes a 90B seed&lt;/td&gt;
&lt;td&gt;assumes a 90B seed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;accumulates&lt;/strong&gt;, no estimate&lt;/td&gt;
&lt;td&gt;assumes a pool seed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reseed&lt;/td&gt;
&lt;td&gt;up to 2^48 requests&lt;/td&gt;
&lt;td&gt;up to 2^48 requests&lt;/td&gt;
&lt;td&gt;32-pool staggered&lt;/td&gt;
&lt;td&gt;continuous, per CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FIPS 140&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;yes, validated&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;not yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrency&lt;/td&gt;
&lt;td&gt;per instance&lt;/td&gt;
&lt;td&gt;per instance&lt;/td&gt;
&lt;td&gt;single system RNG&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;per CPU&lt;/strong&gt; since Linux 2022&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best suited for&lt;/td&gt;
&lt;td&gt;FIPS boundaries, AES-NI&lt;/td&gt;
&lt;td&gt;deterministic signatures&lt;/td&gt;
&lt;td&gt;accumulator front-end&lt;/td&gt;
&lt;td&gt;modern software default&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Two 2026 differences decide most real choices. First, ChaCha20 is &lt;em&gt;constant-time&lt;/em&gt; by design, with no secret-dependent branches or table lookups,Constant-time means the code&apos;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. 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 &lt;em&gt;FIPS 140 validation&lt;/em&gt;, which is exactly why it still dominates inside compliance boundaries and on AES-NI hardware despite the side-channel result [@src-sp80090a]. Linux&apos;s 2022 move to per-CPU ChaCha instances closed the historic concurrency gap that once favored other designs [@src-donenfeld2022].&lt;/p&gt;

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.

flowchart TD
    EV[&quot;Entropy events arrive&quot;] --&amp;gt; RR[&quot;Round-robin into pools P0 through P31&quot;]
    RR --&amp;gt; P0[&quot;P0 drains at every reseed&quot;]
    RR --&amp;gt; PI[&quot;Pi drains only when 2 to the i divides the reseed number&quot;]
    RR --&amp;gt; P31[&quot;P31 fills rarely but very deeply&quot;]
    P0 --&amp;gt; RK[&quot;Reseed the generator key&quot;]
    PI --&amp;gt; RK
    P31 --&amp;gt; RK
&lt;p&gt;The hardware-versus-software debate resolves the same way: not by choosing, but by mixing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Table 4: Hardware RNG versus software DRBG.&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Hardware (RDRAND/RDSEED, TPM)&lt;/th&gt;
&lt;th&gt;Software DRBG (CTR/ChaCha)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Source of unpredictability&lt;/td&gt;
&lt;td&gt;physical noise on the die&lt;/td&gt;
&lt;td&gt;deterministic expansion of a seed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auditability&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;opaque&lt;/strong&gt;, you cannot inspect silicon&lt;/td&gt;
&lt;td&gt;open, testable, formally analyzable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure mode&lt;/td&gt;
&lt;td&gt;silent bias or back door (the Dual_EC lesson) [@src-shumow2007]&lt;/td&gt;
&lt;td&gt;bad seed, state clone, side channel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forward secrecy&lt;/td&gt;
&lt;td&gt;vendor-dependent&lt;/td&gt;
&lt;td&gt;explicit (key erasure or Update)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verdict&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;seed-grade input only&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;auditable expansion + forward secrecy&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;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&apos;s resolution is to fold hardware output &lt;em&gt;into&lt;/em&gt; the pool rather than trust it directly [@src-donenfeld2022]. &quot;Trust &lt;code&gt;RDRAND&lt;/code&gt; alone&quot; fell out of favor precisely because Dual_EC proved silicon and standards can both be compromised [@src-shumow2007].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This article cross-references &lt;a href=&quot;https://paragmali.com/blog/a-key-is-only-as-unguessable-as-the-dice-that-made-it-inside/&quot; rel=&quot;noopener&quot;&gt;the Windows CSPRNG&lt;/a&gt; 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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On the consumer side, the same &quot;which construction&quot; question has its own honest table, this time about how much uniqueness burden you are willing to carry.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Table 5: Plain GCM versus GCM-SIV versus deterministic.&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;AES-GCM (counter nonce)&lt;/th&gt;
&lt;th&gt;AES-GCM (random nonce)&lt;/th&gt;
&lt;th&gt;AES-GCM-SIV&lt;/th&gt;
&lt;th&gt;RFC 6979 (signatures)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Uniqueness burden&lt;/td&gt;
&lt;td&gt;on the caller (counter)&lt;/td&gt;
&lt;td&gt;birthday cap ~2^32 per key&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;tolerates repeats&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;none (deterministic)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost vs GCM&lt;/td&gt;
&lt;td&gt;1x&lt;/td&gt;
&lt;td&gt;1x&lt;/td&gt;
&lt;td&gt;~2x&lt;/td&gt;
&lt;td&gt;not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Catastrophic on reuse?&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;no&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use when&lt;/td&gt;
&lt;td&gt;you control a counter&lt;/td&gt;
&lt;td&gt;low message volume&lt;/td&gt;
&lt;td&gt;uniqueness not guaranteed&lt;/td&gt;
&lt;td&gt;ECDSA/DSA nonces&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;AES-GCM-SIV (RFC 8452) derives its internal inputs from a synthetic value computed over the nonce &lt;em&gt;and&lt;/em&gt; the plaintext, so a repeated nonce with different messages no longer leaks the authentication subkey [@src-rfc8452]. In the RFC&apos;s own words, such schemes are built so that they&lt;/p&gt;

&quot;...do not fail catastrophically if a nonce is repeated.&quot; -- RFC 8452, on AES-GCM-SIV [@src-rfc8452]
&lt;p&gt;It costs roughly twice a plain GCM encryption and is the right answer when you cannot &lt;em&gt;guarantee&lt;/em&gt; 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].&lt;/p&gt;

It is tempting to declare a winner between NIST&apos;s estimation and Fortuna&apos;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.
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;8. What You Cannot Know or Prove&lt;/h2&gt;
&lt;p&gt;Everything so far was engineering, and engineering can be improved. This section is about the floor beneath it: things you fundamentally &lt;em&gt;cannot&lt;/em&gt; do, no matter how good your code is. This is where confidence turns to humility.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Min-entropy is unmeasurable.&lt;/strong&gt; You cannot &lt;em&gt;measure&lt;/em&gt; the min-entropy of an unknown physical source from its output; you can only &lt;em&gt;estimate&lt;/em&gt; it under a model of how the source behaves. SP 800-90B&apos;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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Statistical tests can falsify, never certify.&lt;/strong&gt; SP 800-22 is explicitly hypothesis testing with P-values: a suite can &lt;em&gt;reject&lt;/em&gt; a generator, but it can never &lt;em&gt;prove&lt;/em&gt; 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].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. &quot;More tests&quot; is not &quot;more secure.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Every deployed computational CSPRNG rests on an unproven assumption.&lt;/strong&gt; The best a deterministic generator achieves is the next-bit guarantee, but that guarantee is conditional: cryptographically secure generators exist &lt;em&gt;if and only if&lt;/em&gt; 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 &lt;em&gt;pseudorandom&lt;/em&gt; generator. Every CSPRNG you have ever used is secure only relative to a hardness assumption no one has proved.&lt;/p&gt;

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].
&lt;p&gt;&lt;strong&gt;The birthday bound is a hard uniqueness ceiling.&lt;/strong&gt; Randomness cannot buy its way past its own birthday bound. The only escapes are to &lt;em&gt;stop drawing randomly&lt;/em&gt;, using a counter whose uniqueness is structural, or to &lt;em&gt;tolerate&lt;/em&gt; repeats with a nonce-misuse-resistant mode. There is no third option in which random draws stay unique forever.&lt;/p&gt;
&lt;p&gt;{`
// Birthday bound: for an m-bit value, collisions get likely near 2^(m/2).
const m = 96;                              // GCM nonce size in bits&lt;/p&gt;
&lt;p&gt;const fiftyPercentPoint = Math.pow(2, m / 2);   // ~2^48 draws for 96 bits
console.log(&apos;nonce size:&apos;, m, &apos;bits&apos;);
console.log(&apos;~50% collision near 2^&apos; + (m / 2) + &apos; =&apos;, fiftyPercentPoint.toExponential(3), &apos;draws&apos;);&lt;/p&gt;
&lt;p&gt;// 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));
}&lt;/p&gt;
&lt;p&gt;// 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(&apos;at 2^32 draws, P(collision) =&apos;, collisionProb(96, cap).toExponential(3));
console.log(&apos;that is the safety margin SP 800-38D buys by capping at 2^32&apos;);
`}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Back doors are undetectable by construction.&lt;/strong&gt; 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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clone defense is mitigation, not elimination.&lt;/strong&gt; &lt;code&gt;fork()&lt;/code&gt; and VM-snapshot cloning duplicate a &lt;em&gt;good&lt;/em&gt; generator [@src-ristenpart2010, @src-everspaugh2014]. VM Generation ID counters, library fork detection, and forced reseeds shrink the window but do not close it; Ferguson&apos;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].&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;

flowchart TD
    START[&quot;A randomness bug&quot;] --&amp;gt; K{&quot;Which property failed?&quot;}
    K --&amp;gt;|&quot;predictable&quot;| P{&quot;Where did it fail?&quot;}
    K --&amp;gt;|&quot;repeated&quot;| R{&quot;Where did it fail?&quot;}
    P --&amp;gt;|&quot;generator&quot;| PG[&quot;Netscape, Debian, Dual EC, DUHK, CTR_DRBG cache&quot;]
    P --&amp;gt;|&quot;role&quot;| PR[&quot;BEAST (CBC IV), PuTTY (biased nonce)&quot;]
    R --&amp;gt;|&quot;generator&quot;| RG[&quot;VM-snapshot clone&quot;]
    R --&amp;gt;|&quot;role&quot;| RR[&quot;PS3, Bitcoin, GCM nonce reuse&quot;]
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; 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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If entropy is unmeasurable and unpredictability uncertifiable, where is randomness still actively failing today?&lt;/p&gt;
&lt;h2&gt;9. Where Randomness Is Still Failing&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Early-boot entropy on embedded devices, IoT, and VMs.&lt;/strong&gt; A device that must generate a key seconds after first power-on, before its pool has filled, is the exact condition &lt;em&gt;Mining Your Ps and Qs&lt;/em&gt; measured in 2012, and it still recurs in modern IoT field scans [@src-factorable]. Partial fixes exist: &lt;code&gt;getrandom&lt;/code&gt; blocking once until seeded [@src-getrandom], saved-seed files such as FreeBSD&apos;s &lt;code&gt;/boot/entropy&lt;/code&gt; [@src-freebsd-random], and mixing in &lt;code&gt;RDSEED&lt;/code&gt; or jitter-based entropy at boot. None fully solves a cold device with no stored seed and no timing history.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Validating entropy sources at fleet scale.&lt;/strong&gt; SP 800-90B health tests run per device, but proving that a noise-source &lt;em&gt;model&lt;/em&gt; holds across millions of heterogeneous units, over years, across temperature swings and silicon aging, is unsolved [@src-sp80090b]. Fortuna&apos;s accumulation sidesteps &lt;em&gt;estimation&lt;/em&gt; but not &lt;em&gt;validation&lt;/em&gt;: you still have to argue the events carry entropy at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Making nonce-misuse resistance the default.&lt;/strong&gt; 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&apos;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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Post-quantum&apos;s larger draws and wider side-channel surface.&lt;/strong&gt; 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 &lt;a href=&quot;https://paragmali.com/blog/the-thirty-year-migration-ships-in-a-pip-install-how-post-qu/&quot; rel=&quot;noopener&quot;&gt;post-quantum migration&lt;/a&gt; is an opportunity to get seeding right and a risk if it is not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trusting hardware RNGs after Dual_EC.&lt;/strong&gt; &quot;Never trust a single hardware source&quot; is settled &lt;em&gt;practice&lt;/em&gt;, but a general, auditable way to &lt;em&gt;earn&lt;/em&gt; trust in opaque silicon is open [@src-shumow2007]. Cohney&apos;s cache attack showed that even a correct-on-paper CTR_DRBG can leak through microarchitectural side channels, so &quot;the math is right&quot; is not the end of the story [@src-cohney2020].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Formally verifying RNG code end to end.&lt;/strong&gt; Proofs typically assume &quot;the implementation does not leak,&quot; 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.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Notice the pattern across all six. The &lt;em&gt;expansion&lt;/em&gt; 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 &lt;em&gt;sourcing&lt;/em&gt; (getting and validating real entropy, especially at boot and at fleet scale) and &lt;em&gt;trust&lt;/em&gt; (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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Physical noise is quietly back in fashion, incidentally.&lt;code&gt;RDSEED&lt;/code&gt;, 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 &lt;em&gt;mixed in&lt;/em&gt;, never trusted raw. The physics returned; the blind trust did not. These are the field&apos;s problems. But you have to ship code tomorrow. What are the rules, concretely, per role, that keep you out of the Failure Catalog?&lt;/p&gt;
&lt;h2&gt;10. The Field Guide, Made Operational&lt;/h2&gt;
&lt;p&gt;Everything collapses to a short set of rules of the form &quot;use X with these parameters in case Y.&quot; 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&apos;s chair.&lt;/p&gt;
&lt;p&gt;Rule zero for the generator: never roll your own, and never hand-seed. Always call the operating system&apos;s CSPRNG.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Table 6: Generator, call this, not this.&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Call this&lt;/th&gt;
&lt;th&gt;Never this&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;&lt;code&gt;getrandom(2)&lt;/code&gt;, &lt;code&gt;/dev/urandom&lt;/code&gt; [@src-getrandom]&lt;/td&gt;
&lt;td&gt;a hand-seeded userspace PRNG, blocking on &lt;code&gt;/dev/random&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BSD, macOS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;getentropy(3)&lt;/code&gt;, &lt;code&gt;arc4random(3)&lt;/code&gt; [@src-arc4random]&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rand()&lt;/code&gt;, &lt;code&gt;random()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BCryptGenRandom&lt;/code&gt;, &lt;code&gt;ProcessPrng&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;anything seeded by hand (see the CNG post)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language stdlib&lt;/td&gt;
&lt;td&gt;Node &lt;code&gt;crypto.randomBytes&lt;/code&gt;, Python &lt;code&gt;secrets&lt;/code&gt; / &lt;code&gt;os.urandom&lt;/code&gt;, Go &lt;code&gt;crypto/rand&lt;/code&gt;, Java &lt;code&gt;SecureRandom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Math.random()&lt;/code&gt;, Python &lt;code&gt;random&lt;/code&gt;, Mersenne Twister, &lt;code&gt;java.util.Random&lt;/code&gt;, &lt;code&gt;System.Random&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Then, per role, the decision rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Key.&lt;/strong&gt; Draw from the OS CSPRNG at the primitive&apos;s exact key length: 32 bytes for AES-256 or ChaCha20. Nothing else.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CTR/GCM nonce.&lt;/strong&gt; 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].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CBC IV.&lt;/strong&gt; A fresh &lt;em&gt;unpredictable&lt;/em&gt; value per message from the CSPRNG, never a counter and never the previous ciphertext block, or better, migrate to authenticated encryption.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Salt.&lt;/strong&gt; At least 16 bytes from the CSPRNG, unique per password, stored in plaintext. Never a global salt.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Signature nonce (k).&lt;/strong&gt; Use RFC 6979 deterministic k, or a hedged scheme; never a bare draw whose bias or repetition leaks the private key [@src-rfc6979].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fork or VM clone.&lt;/strong&gt; 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].&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Table 7: The Common Misuse catalog (the mirror of Table 1).&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Misuse&lt;/th&gt;
&lt;th&gt;Knob and place broken&lt;/th&gt;
&lt;th&gt;Named break&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Seeding from &lt;code&gt;time()&lt;/code&gt; or a PID&lt;/td&gt;
&lt;td&gt;predictable, generator&lt;/td&gt;
&lt;td&gt;Netscape [@src-netscape1996]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Removing or starving entropy&lt;/td&gt;
&lt;td&gt;predictable, generator&lt;/td&gt;
&lt;td&gt;Debian, Ps and Qs [@src-debian2008]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reusing a GCM nonce under one key&lt;/td&gt;
&lt;td&gt;repeated, role&lt;/td&gt;
&lt;td&gt;GCM in TLS 2016 [@src-cve2016]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Predictable or chained CBC IV&lt;/td&gt;
&lt;td&gt;predictable, role&lt;/td&gt;
&lt;td&gt;BEAST [@src-cve2011]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static or biased signature k&lt;/td&gt;
&lt;td&gt;repeated or predictable, role&lt;/td&gt;
&lt;td&gt;PS3, PuTTY [@src-putty2024]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secret, short, or global salt&lt;/td&gt;
&lt;td&gt;repeated, role&lt;/td&gt;
&lt;td&gt;shared precomputed tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hand-seeding a userspace PRNG&lt;/td&gt;
&lt;td&gt;predictable, generator&lt;/td&gt;
&lt;td&gt;interface misuse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trusting &lt;code&gt;RDRAND&lt;/code&gt; alone&lt;/td&gt;
&lt;td&gt;predictable, generator&lt;/td&gt;
&lt;td&gt;the Dual_EC lesson [@src-shumow2007]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mersenne Twister or &lt;code&gt;Math.random&lt;/code&gt; for tokens&lt;/td&gt;
&lt;td&gt;predictable, generator&lt;/td&gt;
&lt;td&gt;statistical PRNG used for crypto [@src-mersenne1998]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No reseed after &lt;code&gt;fork()&lt;/code&gt; or snapshot&lt;/td&gt;
&lt;td&gt;repeated, generator&lt;/td&gt;
&lt;td&gt;VM-reset [@src-ristenpart2010]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UUIDv4 as a security token&lt;/td&gt;
&lt;td&gt;predictable, role&lt;/td&gt;
&lt;td&gt;guessable token [@src-rfc9562]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The meta-lesson runs through every row: historically the &lt;em&gt;interface&lt;/em&gt;, not the algorithm, is the bug. Blocking &lt;code&gt;/dev/random&lt;/code&gt;, seeding a userspace PRNG, forgetting to reseed after a fork; these are misuses of how you &lt;em&gt;reach&lt;/em&gt; randomness, not weaknesses in the math. That is exactly why &lt;code&gt;getrandom&lt;/code&gt; and &lt;code&gt;getentropy&lt;/code&gt; were designed to be un-misusable.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; 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 &lt;code&gt;getrandom&lt;/code&gt;, &lt;code&gt;getentropy&lt;/code&gt;, &lt;code&gt;BCryptGenRandom&lt;/code&gt;, or your language&apos;s CSPRNG wrapper, and stop [@src-getrandom, @src-arc4random].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; 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].&lt;/p&gt;
&lt;/blockquote&gt;

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.
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;11. Frequently Asked Questions&lt;/h2&gt;
&lt;p&gt;The most expensive randomness bugs start as reasonable-sounding beliefs. Here are eight, corrected.&lt;/p&gt;


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 &quot;more secure&quot; 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].


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.


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.


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.


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].


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].


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&apos;s format [@src-rfc4086].


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].

&lt;h2&gt;12. Two Knobs, Two Places&lt;/h2&gt;
&lt;p&gt;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: &lt;em&gt;predictable&lt;/em&gt;, in the &lt;em&gt;generator&lt;/em&gt;. That is the whole diagnosis, and it is the same diagnosis for every break in this article.&lt;/p&gt;
&lt;p&gt;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 &lt;em&gt;generator&lt;/em&gt; that mints the bits, or the &lt;em&gt;role&lt;/em&gt; 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Snap the catalog into the grid one last time. Netscape and Debian: predictable, generator. The PS3&apos;s static nonce and the Bitcoin wallets&apos; 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&apos;s biased P-521 nonce: predictable, role [@src-putty2024]. Four boxes, and every disaster in four decades fits in one of them.&lt;/p&gt;

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.
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;This was Part 2 of the field guide. Part 1 established what &quot;secure&quot; 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 &lt;a href=&quot;https://paragmali.com/blog/a-key-is-only-as-unguessable-as-the-dice-that-made-it-inside/&quot; rel=&quot;noopener&quot;&gt;Windows CSPRNG companion article&lt;/a&gt; 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.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;randomness-in-cryptography-csprngs-nonces-ivs-salts&quot; keyTerms={[
  { term: &quot;CSPRNG&quot;, definition: &quot;A generator whose output no efficient adversary can distinguish from uniform, even after seeing part of it.&quot; },
  { term: &quot;DRBG&quot;, definition: &quot;The deterministic expansion core that stretches a seed. It never creates entropy, only stretches it.&quot; },
  { term: &quot;Min-entropy&quot;, definition: &quot;The worst-case unpredictability of a source, set by the attacker&apos;s single best guess.&quot; },
  { term: &quot;Next-bit test&quot;, definition: &quot;A sequence is strong if no efficient algorithm predicts its next bit with advantage over one-half.&quot; },
  { term: &quot;Nonce&quot;, definition: &quot;A number used once. Must be unique per key; need not be unpredictable or secret.&quot; },
  { term: &quot;Initialization Vector&quot;, definition: &quot;A per-message starting value. Unpredictable and unique in CBC; unique-only (a nonce) in CTR and GCM.&quot; },
  { term: &quot;Salt&quot;, definition: &quot;A unique, random, public value that makes each password hash distinct. Not a secret.&quot; },
  { term: &quot;Forward secrecy&quot;, definition: &quot;A later state compromise cannot recover earlier output. Achieved by fast key erasure.&quot; },
  { term: &quot;Reseeding&quot;, definition: &quot;Folding fresh entropy into the state on a schedule or after a fork or snapshot.&quot; },
  { term: &quot;Birthday bound&quot;, definition: &quot;Collisions in an m-bit value get likely near 2 to the m over 2 draws. A provable uniqueness ceiling.&quot; }
]} flashcards={[
  { front: &quot;The two knobs&quot;, back: &quot;Unpredictability and uniqueness.&quot; },
  { front: &quot;The two places&quot;, back: &quot;The generator that makes the bits, and the role that consumes them.&quot; },
  { front: &quot;Why did BEAST work?&quot;, back: &quot;TLS 1.0 used a predictable chained CBC IV, and a CBC IV must be unpredictable.&quot; },
  { front: &quot;Why is a single GCM nonce reuse fatal?&quot;, back: &quot;It leaks the XOR of the two plaintexts and yields the polynomial equation that recovers the GHASH subkey H, enabling forgery.&quot; },
  { front: &quot;RDRAND in one rule&quot;, back: &quot;The kernel mixes it into the pool; applications never call it directly.&quot; }
]} questions={[
  { q: &quot;Why does passing every statistical test not make a generator secure?&quot;, a: &quot;Statistical quality is distributional; cryptographic security is computational unpredictability. The Mersenne Twister passes the batteries yet is recovered from 624 outputs.&quot; },
  { q: &quot;Which role must be unpredictable, and which must only be unique?&quot;, a: &quot;A CBC IV must be unpredictable and unique. A CTR or GCM nonce must only be unique.&quot; },
  { q: &quot;How did RFC 6979 fix repeated-nonce disasters?&quot;, a: &quot;By deriving the signature nonce deterministically from the private key and message, removing the RNG from the signing path.&quot; },
  { q: &quot;What did Dual_EC prove about detectability?&quot;, a: &quot;A standardized generator can pass every test and still be predictable to whoever chose its constants. Back doors are undetectable from output.&quot; },
  { q: &quot;What is the one operational rule for the generator?&quot;, a: &quot;Never roll your own and never hand-seed. Always call the OS CSPRNG.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>cryptography</category><category>randomness</category><category>csprng</category><category>entropy</category><category>nonces</category><category>applied-cryptography</category><category>security</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>A Key Is Only as Unguessable as the Dice That Made It: Inside the Windows CSPRNG</title><link>https://paragmali.com/blog/a-key-is-only-as-unguessable-as-the-dice-that-made-it-inside/</link><guid isPermaLink="true">https://paragmali.com/blog/a-key-is-only-as-unguessable-as-the-dice-that-made-it-inside/</guid><description>Every software secret Windows makes is drawn from one CSPRNG. Trace the entropy, the SP 800-90A CTR_DRBG, ProcessPrng, and where the OS stops rolling the dice.</description><pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate><content:encoded>
Every software secret Windows generates -- DPAPI master keys, BitLocker volume keys, TLS session keys, machine-account passwords -- is drawn from one cryptographic random number generator: a NIST SP 800-90A AES `CTR_DRBG` seeded from many mixed entropy sources and reseeded on a clock schedule [@src-win10rng-pdf]. It is not &quot;true&quot; randomness. It is a deterministic generator whose output is only as unguessable as the entropy behind its seed. Windows learned this the hard way -- its first generator, `CryptGenRandom`, was broken for forward secrecy in 2007 [@src-cryptgenrandom2007], and the industry&apos;s `Dual_EC_DRBG` scare proved that an algorithm&apos;s own constants can be a back door [@src-shumowferguson2007]. The modern design answers both failures: a kernel-resident root `CTR_DRBG` seeds a strict chain of per-processor generators, mixing enough sources that no single one -- not even Intel&apos;s `RDRAND` -- can own the pool, delivered to your process through `ProcessPrng` and `BCryptGenRandom` [@src-processprng; @src-bcryptgenrandom]. The one boundary: keys born inside a TPM or a smart card come from the device&apos;s own hardware RNG, not this software CSPRNG.
&lt;h2&gt;1. Two Lines of Code That Never Touched the Cipher&lt;/h2&gt;
&lt;p&gt;In 1996, two Berkeley graduate students broke the encryption on every Netscape SSL session without laying a finger on the cipher. Ian Goldberg and David Wagner never factored a modulus or attacked RC4. They reverse-engineered the browser and found that the &quot;random&quot; seed feeding each session key depended on just three quantities: the time of day, the process ID, and the parent process ID [@src-netscape1996]. Guess those three numbers -- and on a shared Unix host you very nearly could -- and you reconstruct the key directly.&lt;/p&gt;
&lt;p&gt;Twelve years later, a change to Debian&apos;s OpenSSL did the same thing to a large slice of the internet at once. A well-meaning patch commented out the code that mixed extra data into the key generator&apos;s entropy pool, so the private keys a machine produced were drawn from little more than its process ID [@src-debian2008]. Every SSH host key, TLS certificate, and OpenVPN key generated on an affected system fell into a tiny, enumerable set, and the flaw sat in shipping code from September 2006 until its disclosure in May 2008 [@src-cve20080166].&lt;/p&gt;
&lt;p&gt;Notice what did &lt;em&gt;not&lt;/em&gt; fail in either case. RSA was fine. RC4 was fine. The math was flawless and the keys were still guessable, because a key is only as unguessable as the randomness underneath it. That is the single idea this article is built on, and it is not a story about Netscape or Debian. It is the story of every secret your operating system makes.&lt;/p&gt;
&lt;p&gt;On Windows, that surface is enormous. DPAPI master keys, BitLocker volume keys, TLS session keys, and machine-account passwords are all drawn -- when they are generated in software -- from a single cryptographic random number generator buried in the kernel. Their security bottoms out on how well Windows rolls the dice. Get that generator right and a thousand higher-level protocols inherit unpredictability for free. Get it wrong, once, and every one of them silently weakens at the same time.&lt;/p&gt;
&lt;p&gt;Goldberg and Wagner&apos;s 1996 write-up derives the figure directly: with only the wall-clock second known (from a packet sniffer) but the microseconds, process ID, and parent process ID unknown, the seed retains &quot;at most, 47 bits of randomness in the secret key,&quot; and in practice often far fewer [@src-netscape1996].&lt;/p&gt;
&lt;p&gt;The Debian break is usually summarized as collapsing keys to &quot;about 32,768 possibilities.&quot; That number is a derived consequence, not text from the advisory: the change commented out two &lt;code&gt;MD_Update&lt;/code&gt; calls that fed the pool, leaving process-ID as effectively the only varying input, and a 15-bit PID space is $2^{15}$, or 32,768 values [@src-debian2008; @src-cve20080166].&lt;/p&gt;
&lt;p&gt;So the questions that organize the rest of this piece almost ask themselves. What would it actually take to build randomness an attacker cannot guess? Has Windows ever failed at it? And when it rebuilt, what design did it land on -- and where does that design stop?&lt;/p&gt;

timeline
    title From &quot;looks random&quot; to &quot;is unpredictable&quot;
    1982 : Blum and Micali introduce the unpredictability criterion and first provable PRG
    1982 : Yao proves unpredictability = indistinguishability
    1996 : Netscape SSL seed reconstructed
    2006 to 2008 : Debian OpenSSL entropy stripped
    2007 : CryptGenRandom forward-security break
    2007 : Dual_EC_DRBG back door demonstrated
    2012 : VM Generation ID ships for clone detection
    2014 : NIST removes Dual_EC_DRBG
    2019 : Windows CTR_DRBG tree documented
    2025 : NIST finalizes SP 800-90C
&lt;h2&gt;2. What &quot;Random&quot; Has to Mean&lt;/h2&gt;
&lt;p&gt;Here is a claim that sounds wrong the first time you hear it: passing every statistical randomness test in existence is not good enough for keys. A sequence can be perfectly balanced, survive every chi-squared and spectral test you throw at it, and still be catastrophic to build a secret on.&lt;/p&gt;
&lt;p&gt;The Mersenne Twister makes the point. It is a superb general-purpose generator with a period of $2^{19937}-1$ and excellent statistical properties, and it is the default &lt;code&gt;rand&lt;/code&gt;-style source in countless languages. Its own authors are blunt about the limit: &quot;Mersenne Twister is basically for Monte-Carlo simulations. It is not cryptographically secure as is&quot; [@src-mt]. The reason is that its internal state is a linear function of its output, held in a working area of just 624 words [@src-mtpaper]. Observe 624 consecutive 32-bit outputs and you can solve for the entire state, then predict every future value. It looks random to a statistician and is transparent to a cryptanalyst.&lt;/p&gt;
&lt;p&gt;That gap forces a three-way distinction that the rest of this article leans on constantly.&lt;/p&gt;

A physical process that produces genuine unpredictability -- thermal noise, ring-oscillator jitter, the timing of hardware interrupts. A true random number generator (TRNG) samples such a process. It is slow, sometimes biased, and cannot be reproduced from a formula, which is exactly why it is trusted as a source of fresh unpredictability.

An algorithm that stretches a short seed into a long stream of output bits. Given the same seed it always produces the same stream, so it creates no new unpredictability; it spreads out the unpredictability already present in the seed. NIST SP 800-90A defines the DRBG mechanisms Windows uses [@src-sp80090a].

A DRBG whose output no efficient adversary can distinguish from uniformly random bits, even after seeing any feasible, polynomially bounded amount of prior output. The Mersenne Twister is a PRNG but not a CSPRNG; a `CTR_DRBG` is designed to be one.
&lt;p&gt;A real system needs both halves: an entropy source to supply unpredictability, and a DRBG to turn a trickle of it into all the fast, uniform bytes applications demand. Confusing the two is how you ship a generator that benchmarks beautifully and leaks keys.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Entropy source (TRNG)&lt;/th&gt;
&lt;th&gt;CSPRNG (secure DRBG)&lt;/th&gt;
&lt;th&gt;Non-crypto PRNG&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Origin of bits&lt;/td&gt;
&lt;td&gt;physical noise&lt;/td&gt;
&lt;td&gt;a seed plus an algorithm&lt;/td&gt;
&lt;td&gt;a seed plus an algorithm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reproducible from state?&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed&lt;/td&gt;
&lt;td&gt;slow&lt;/td&gt;
&lt;td&gt;fast&lt;/td&gt;
&lt;td&gt;fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safe for keys?&lt;/td&gt;
&lt;td&gt;yes, but rate-limited&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Example&lt;/td&gt;
&lt;td&gt;RDSEED, interrupt jitter&lt;/td&gt;
&lt;td&gt;AES &lt;code&gt;CTR_DRBG&lt;/code&gt;, ChaCha20&lt;/td&gt;
&lt;td&gt;Mersenne Twister, C &lt;code&gt;rand()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;rand()&lt;/code&gt;, &lt;code&gt;random()&lt;/code&gt;, and the Mersenne Twister are built for simulations and games, not keys. Their state is recoverable from their output [@src-mt]. Anything that must be unpredictable to an adversary needs a CSPRNG, never a general-purpose PRNG.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;The bar that replaced &quot;looks random&quot;&lt;/h3&gt;
&lt;p&gt;What separates a CSPRNG from a Mersenne Twister was pinned down theoretically before either Windows generator existed. In 1982, Manuel Blum and Silvio Micali introduced the &lt;strong&gt;next-bit unpredictability criterion&lt;/strong&gt;: a generator is cryptographically strong if no efficient adversary, given any prefix of the output, can predict the next bit with advantage better than negligible. They did more than state the bar -- they showed it was reachable, constructing a PRG whose output meets it as long as a specific hard problem (the discrete logarithm) stays hard, via a hardcore predicate that is provably as difficult to guess as inverting a one-way function [@src-blummicali1984]. It was the first pseudorandom generator with a security &lt;em&gt;proof&lt;/em&gt; rather than a hope.&lt;/p&gt;
&lt;p&gt;That same year, Andrew Yao proved why the next-bit test is the right bar: a generator passes it if and only if its output is computationally indistinguishable from a uniform random string -- no efficient statistical test of any kind can tell them apart [@src-yao1982]. Unpredictability and indistinguishability are the same property. Yao&apos;s theorem generalized the Blum-Micali criterion into the definition of &quot;cryptographically random,&quot; replacing the vague &quot;looks random&quot; with something you can reason about. Every DRBG since, including Windows&apos; &lt;code&gt;CTR_DRBG&lt;/code&gt;, is a descendant of that idea.&lt;/p&gt;

The worst-case measure of unpredictability. For a source $X$, the min-entropy is $H_\infty(X) = -\log_2 \max_x \Pr[X = x]$ -- it counts only the single most likely outcome. If the likeliest value has probability one in a thousand, the source has just under 10 bits of min-entropy, no matter how the rest of the distribution looks. Min-entropy upper-bounds the security of everything drawn from a seed.
&lt;p&gt;Two derived properties matter so much that the entire modern design is organized around them, and every generator in this article is graded on both.&lt;/p&gt;

If an attacker compromises the generator&apos;s state right now, they still cannot recover the output it produced in the past. The state update must be a one-way step. In NIST&apos;s vocabulary this is &quot;backtracking resistance&quot;; the academic literature usually calls the same property &quot;forward security.&quot;

If an attacker learns the state now, fresh entropy mixed in afterward makes future output unpredictable again. Prediction resistance is what lets a generator *recover* from a compromise, and it is the entire reason a generator reseeds rather than running forever on its first seed.
&lt;p&gt;You can watch why a weak seed is fatal in a few lines. When the seed comes from a small space, an attacker does not attack the cipher; they enumerate the seeds and derive the key directly, exactly as in the Debian case.&lt;/p&gt;
&lt;p&gt;{`
// A toy key derivation. Pretend the hex output is a real 256-bit key.
function keyFromSeed(seed) {
  let h = seed &amp;gt;&amp;gt;&amp;gt; 0;
  h = Math.imul(h, 2654435761) &amp;gt;&amp;gt;&amp;gt; 0;   // a deterministic mix
  h = (h ^ (h &amp;gt;&amp;gt;&amp;gt; 15)) &amp;gt;&amp;gt;&amp;gt; 0;
  return h.toString(16).padStart(8, &quot;0&quot;);
}&lt;/p&gt;
&lt;p&gt;// The Debian OpenSSL bug effectively drew the seed from the PID space:
// 2^15 = 32768 possibilities per key type, not 2^256.
const PID_SPACE = 32768;
const victimSeed = 20443;                 // the victim&apos;s unknown PID
const targetKey  = keyFromSeed(victimSeed);&lt;/p&gt;
&lt;p&gt;let guesses = 0, recovered = null;
for (let pid = 0; pid &amp;lt; PID_SPACE &amp;amp;&amp;amp; recovered === null; pid++) {
  guesses++;
  if (keyFromSeed(pid) === targetKey) recovered = pid;
}&lt;/p&gt;
&lt;p&gt;console.log(&quot;target key:&quot;, targetKey);
console.log(&quot;recovered the seed in&quot;, guesses, &quot;guesses&quot;);
console.log(&quot;keyspace searched: 2^15 =&quot;, PID_SPACE, &quot;-- trivially enumerable&quot;);
`}&lt;/p&gt;
&lt;p&gt;Windows built a generator on exactly these terms, &lt;code&gt;CryptGenRandom&lt;/code&gt;, the generator it shipped in the CryptoAPI. Three researchers later took the Windows 2000 build of it apart -- and found it failed the first property outright.&lt;/p&gt;
&lt;h2&gt;3. The First Windows Generator, and How It Broke&lt;/h2&gt;
&lt;p&gt;In 2007, Leo Dorrendorf, Zvi Gutterman, and Benny Pinkas did something Microsoft had never published: they reconstructed the algorithm behind Windows&apos; random number generator by reverse-engineering a Windows 2000 binary, with no help from the vendor. Then they broke it [@src-cryptgenrandom2007]. The target was &lt;code&gt;CryptGenRandom&lt;/code&gt;, the generator that at the time sat under essentially every Windows secret, and the flaws they found were structural, not arithmetic.&lt;/p&gt;
&lt;p&gt;Three design choices did the damage. First, the generator ran &lt;strong&gt;entirely in user mode&lt;/strong&gt;, with a separate copy of its state living inside each process&apos;s own address space. Second, part of that state at startup was simply whatever happened to be sitting on the stack. Third, and most damaging, the generator pulled fresh system entropy into its state &lt;strong&gt;only after it had produced 128 KB of output&lt;/strong&gt;. Between those refreshes, the output was a pure deterministic function of a state that never changed its unpredictability.&lt;/p&gt;
&lt;p&gt;With the algorithm in hand, the attack followed. Given the generator&apos;s current internal state, the researchers showed the &lt;em&gt;previous&lt;/em&gt; state could be recovered in about $O(2^{23})$ work -- a break of forward security -- while running the generator &lt;em&gt;forward&lt;/em&gt; to predict future output was trivial, $O(1)$ [@src-cryptgenrandom2007]. Because state was per-process and reseeded so rarely, a single state leak was not a local event. It reached backward and forward across the whole 128 KB window.&lt;/p&gt;

&quot;Learning a single state may reveal 128 Kbytes of the past and future output of the generator.&quot; -- Dorrendorf, Gutterman, and Pinkas, 2007 [@src-cryptgenrandom2007]
&lt;p&gt;For a process terminating a TLS connection, 128 KB of predictable output is more than enough to expose the session keys it just generated, or is about to. The cipher suite did not matter. The break was in the dice.&lt;/p&gt;

flowchart TD
    A[&quot;CryptGenRandom runs in user mode, one state per process&quot;] --&amp;gt; B[&quot;Generator state lives in the process address space&quot;]
    B --&amp;gt; C[&quot;System entropy mixed in only after 128 KB of output&quot;]
    C --&amp;gt; D[&quot;Attacker leaks a single internal state&quot;]
    D --&amp;gt; E[&quot;Recover the previous state in about 2 to the 23 work&quot;]
    D --&amp;gt; F[&quot;Run forward to predict future output in constant work&quot;]
    E --&amp;gt; G[&quot;Reconstruct the process&apos;s SSL and session keys&quot;]
    F --&amp;gt; G
&lt;p&gt;The asymmetry is worth savoring: predicting &lt;em&gt;future&lt;/em&gt; output from a known state was $O(1)$, because that is just running the generator, while recovering &lt;em&gt;past&lt;/em&gt; output cost $O(2^{23})$ -- the work of inverting one step. A truly forward-secure design makes the second number astronomically large. &lt;code&gt;CryptGenRandom&lt;/code&gt; made it a rounding error [@src-cryptgenrandom2007].&lt;/p&gt;
&lt;p&gt;Two points of discipline before we move on. This break is strictly a property of the &lt;strong&gt;legacy&lt;/strong&gt; &lt;code&gt;CryptGenRandom&lt;/code&gt;, the generator Windows shipped in its CryptoAPI, analyzed here in its Windows 2000 build. It is not the modern &lt;code&gt;CTR_DRBG&lt;/code&gt; design that replaced it, and nothing here should be read as a live vulnerability in a current Windows system.&lt;/p&gt;
&lt;p&gt;The value of the story is that it names, concretely, the four things a redesign had to fix: get the state out of user-mode process memory, reseed far more often than every 128 KB, stop seeding from uninitialized stack data, and make the state update genuinely one-way so a leak cannot reach into the past.&lt;/p&gt;
&lt;p&gt;The fix looked obvious on paper. Move the state into the kernel, reseed on a schedule, make it forward-secure. But at the very moment Microsoft was redesigning, a deeper and more unsettling fear surfaced across the whole field. What if the weak point was not the code at all -- what if the &lt;em&gt;algorithm itself&lt;/em&gt;, blessed by a standards body, was the trap?&lt;/p&gt;
&lt;h2&gt;4. The Dual_EC_DRBG Shadow&lt;/h2&gt;
&lt;p&gt;In 2006, NIST standardized four DRBG mechanisms in Special Publication 800-90: &lt;code&gt;Hash_DRBG&lt;/code&gt;, &lt;code&gt;HMAC_DRBG&lt;/code&gt;, &lt;code&gt;CTR_DRBG&lt;/code&gt;, and a fourth built on elliptic curves called &lt;code&gt;Dual_EC_DRBG&lt;/code&gt; [@src-sp80090-2006; @src-nistremoval2014]. Three of them were ordinary constructions built from hash functions and block ciphers. The fourth carried a landmine, and in August 2007 two Microsoft cryptographers walked up to the CRYPTO rump session and pointed at it.&lt;/p&gt;
&lt;p&gt;Dan Shumow and Niels Ferguson showed that &lt;code&gt;Dual_EC_DRBG&lt;/code&gt; is defined by two elliptic-curve points, $P$ and $Q$, whose relationship is a secret. If anyone knows a scalar $d$ with $d\cdot P = Q$, that number is a skeleton key. In their experiments, &quot;32 bytes of output was sufficient to uniquely identify the internal state&quot; -- after which every future output is predictable [@src-shumowferguson2007]. The constants shipped in the standard, and no one outside their authors could say how $P$ and $Q$ had been chosen. The generator could be perfect for everyone except the one party holding $d$.&lt;/p&gt;
&lt;p&gt;They were scrupulous about what they were and were not claiming, and their exact wording is a model of how to raise an alarm honestly.&lt;/p&gt;

&quot;WHAT WE ARE NOT SAYING: NIST intentionally put a back door in this PRNG. WHAT WE ARE SAYING: The prediction resistance of this PRNG (...) is dependent on solving one instance of the elliptic curve discrete log problem.&quot; -- Shumow and Ferguson, 2007 [@src-shumowferguson2007]
&lt;p&gt;For years this stayed a theoretical worry. Then in 2013, reporting by Joseph Menn at Reuters, drawing on the Snowden documents, alleged that RSA Security had accepted a secret $10 million contract that set &lt;code&gt;Dual_EC_DRBG&lt;/code&gt; as the &lt;strong&gt;default&lt;/strong&gt; generator in its widely used BSAFE toolkit [@src-reuters2013]. The theoretical trapdoor now had a deployment path into real products.&lt;/p&gt;
&lt;p&gt;NIST reopened the standard for comment in September 2013 and, in 2014, formally removed &lt;code&gt;Dual_EC_DRBG&lt;/code&gt;, keeping &quot;three of the four previously available options&quot; and urging users to &quot;transition (...) as quickly as possible&quot; in response to &quot;the lack of public confidence&quot; [@src-nistremoval2014]. A standards body had withdrawn one of its own algorithms because it could no longer vouch for it.&lt;/p&gt;
&lt;p&gt;Where did Windows sit in this? The precise, defensible statement is this: Windows shipped &lt;code&gt;Dual_EC_DRBG&lt;/code&gt; as a &lt;strong&gt;non-default&lt;/strong&gt; SP 800-90 option -- available if an application explicitly asked for it, but never the system default -- and later removed it [@src-msdualec]. That is a materially different posture from BSAFE, where the reporting alleges it was the default. Naming this accurately matters more than scoring a point: the lesson of Dual_EC is not &quot;vendor X was careless,&quot; it is that trust in a generator bottoms out on its algorithm &lt;em&gt;and its constants&lt;/em&gt;, not merely on whether the code has bugs.&lt;/p&gt;

Niels Ferguson is not a bystander in this story; he is the thread that ties it together. He co-presented the Dual_EC back-door result in 2007. Years earlier he co-designed Fortuna, the multi-pool reseeding generator whose pattern Windows would adopt [@src-fortuna]. And in 2019 he authored the whitepaper documenting the modern Windows generator [@src-win10rng-pdf]. The person who showed most vividly that a standardized DRBG could betray you is the same person who later wrote down how Windows arranges never to trust any single one. That is not a coincidence -- it is the design philosophy made personal.
&lt;p&gt;So the field learned the same lesson twice in the span of a few years, from two directions. &lt;code&gt;CryptGenRandom&lt;/code&gt; proved that a generator&apos;s &lt;em&gt;structure&lt;/em&gt; could betray it. &lt;code&gt;Dual_EC&lt;/code&gt; proved that a generator&apos;s &lt;em&gt;algorithm and constants&lt;/em&gt; could betray it. When Microsoft rebuilt from scratch, it had to answer both at once: which mechanism do you pick, and how do you arrange the whole system so that no single component -- no algorithm, no constant, no hardware chip -- can quietly own the result?&lt;/p&gt;
&lt;h2&gt;5. The Modern Architecture: One Root, a Strict Chain of Generators&lt;/h2&gt;
&lt;p&gt;The redesign that answers both failures fits in a single sentence, and it is worth stating before the diagram. Ferguson&apos;s 2019 whitepaper on the Windows generator [@src-win10rng] puts it flatly:&lt;/p&gt;

&quot;All PRNGs in the system are SP800-90 AES_CTR_DRBG with 256-bit security strength using the df() function.&quot; -- Niels Ferguson, The Windows 10 random number generation infrastructure, 2019 [@src-win10rng-pdf]
&lt;p&gt;Every generator in Windows is the same mechanism, an AES &lt;code&gt;CTR_DRBG&lt;/code&gt; at the maximum 256-bit strength. What differs is how they are wired together. Before the wiring, here is the road that led to it.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Generation&lt;/th&gt;
&lt;th&gt;Representative design&lt;/th&gt;
&lt;th&gt;Why it was superseded&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;G0 -- non-crypto PRNG&lt;/td&gt;
&lt;td&gt;C &lt;code&gt;rand()&lt;/code&gt;, Mersenne Twister&lt;/td&gt;
&lt;td&gt;Linear state recoverable from output [@src-mt]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G1 -- ad-hoc app CSPRNG&lt;/td&gt;
&lt;td&gt;Netscape SSL seed from clock and PID&lt;/td&gt;
&lt;td&gt;Too little entropy; seeds enumerable [@src-netscape1996]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G2 -- legacy &lt;code&gt;CryptGenRandom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;User-mode, per-process DRBG&lt;/td&gt;
&lt;td&gt;Forward-security break; reseed only per 128 KB [@src-cryptgenrandom2007]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G3 -- SP 800-90 DRBG menu&lt;/td&gt;
&lt;td&gt;Standardized &lt;code&gt;CTR_DRBG&lt;/code&gt; and siblings&lt;/td&gt;
&lt;td&gt;A menu is not an architecture; Dual_EC withdrawn [@src-nistremoval2014]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G4 -- modern Windows tree&lt;/td&gt;
&lt;td&gt;Root &lt;code&gt;CTR_DRBG&lt;/code&gt; seeding a strict chain of per-processor DRBGs&lt;/td&gt;
&lt;td&gt;Current design [@src-win10rng-pdf]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The wiring is a strict, one-way chain. At the top sits a single &lt;strong&gt;root generator that lives in kernel mode&lt;/strong&gt;, maintained by the &lt;code&gt;CNG.SYS&lt;/code&gt; driver, whose primary job is to seed other generators -- in the rare case a per-processor state cannot be allocated, a kernel-mode request is served directly from the root [@src-win10rng-pdf]; it never hands bytes to an application. Ferguson&apos;s whitepaper is unambiguous about its reach: &quot;All random bytes in the system are derived in some way from this PRNG&quot; [@src-win10rng-pdf]. Below the root are the &lt;strong&gt;kernel per-processor generators&lt;/strong&gt;, also in &lt;code&gt;CNG.SYS&lt;/code&gt;, allocated on demand and seeded &lt;em&gt;from the root&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Each user-mode process then gets a &lt;strong&gt;process-base generator&lt;/strong&gt;, maintained by &lt;code&gt;BCryptPrimitives.dll&lt;/code&gt;; when that DLL loads it &quot;requests a random seed from kernel mode, where it is produced by the per-CPU states&quot; [@src-win10rng-pdf], so it is seeded from the kernel per-processor generators, not directly from the root. Finally, the &lt;strong&gt;user-mode per-processor generators&lt;/strong&gt; that actually service your calls are created on demand and seeded from the process-base generator.&lt;/p&gt;
&lt;p&gt;So the seeding lineage runs in exactly one direction, top to bottom, and only that last layer hands bytes to your code. A request for randomness draws from the bottom of the chain; fresh seed material only ever flows downward from the top. There is no diamond and no shortcut -- a user-mode generator can never read the root or the kernel state directly.&lt;/p&gt;

flowchart TD
    ES[&quot;Mixed entropy sources&quot;] --&amp;gt; ROOT[&quot;Root CTR_DRBG in CNG.SYS, seeds the generators below it&quot;]
    ROOT --&amp;gt; KP[&quot;Kernel per-processor CTR_DRBGs, also in CNG.SYS&quot;]
    KP --&amp;gt; PB[&quot;Process-base CTR_DRBG in BCryptPrimitives.dll&quot;]
    PB --&amp;gt; UP[&quot;User-mode per-processor CTR_DRBGs&quot;]
    UP --&amp;gt; APP[&quot;ProcessPrng hands bytes to your process&quot;]
&lt;p&gt;Why per-processor, and why a root that only seeds? Three reasons, each a direct answer to a past failure. A per-processor generator needs no lock on the hot path, so thousands of threads can draw bytes at once without contending. Each processor&apos;s state is isolated from the others, so leaking one tells you nothing about the rest. And because each layer is seeded &lt;em&gt;from&lt;/em&gt; the one above rather than sharing its state, a compromise of a user-mode generator on one core cannot walk back up to the kernel generator or across to another core.&lt;/p&gt;
&lt;p&gt;The reachability that let one &lt;code&gt;CryptGenRandom&lt;/code&gt; state expose 128 KB of a process&apos;s keys is designed out.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The two structural moves -- a root generator whose main job is to seed the generators below it, and a separate &lt;code&gt;CTR_DRBG&lt;/code&gt; per processor -- are not performance tricks that happen to help security. They are the security. They ensure no single leaked state, and no single hot lock, sits on the path between your key and everyone else&apos;s [@src-win10rng-pdf].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;How a CTR_DRBG actually works&lt;/h3&gt;

The SP 800-90A DRBG built from a block cipher run in counter mode. Its state is a cipher key $K$ and a counter $V$. To produce output it encrypts successive counter values under $K$; after each request it runs an update step that replaces both $K$ and $V$. Windows uses AES with a 256-bit key, giving a combined seed length of 384 bits [@src-sp80090a-pdf].
&lt;p&gt;Mechanically it is simpler than its reputation. The state is a key $K$ and a counter block $V$. To generate output, the DRBG &lt;strong&gt;increments the counter before each block&lt;/strong&gt; and encrypts the result under AES with key $K$: the first output block is $\text{AES}(K, V+1)$, the next is $\text{AES}(K, V+2)$, and so on, exactly like running AES in counter mode. Because the increment comes first, the block for the current $V$ is never emitted. For AES-256 the seed length -- the size of $K$ and $V$ together -- is 384 bits: a 256-bit key plus a 128-bit counter block [@src-sp80090a-pdf].&lt;/p&gt;
&lt;p&gt;The step that earns the &quot;cryptographically secure&quot; label comes after the bytes are handed back. The DRBG runs an &lt;strong&gt;update&lt;/strong&gt; that derives fresh values for $K$ and $V$ and overwrites the old ones. Once that happens, the state that produced your bytes no longer exists anywhere. Recovering it would mean inverting AES, which is exactly the work the cipher is built to make infeasible. That single overwrite is what gives the whole chain its backtracking resistance -- the property &lt;code&gt;CryptGenRandom&lt;/code&gt; lacked.&lt;/p&gt;

sequenceDiagram
    participant App as Caller
    participant D as CTR_DRBG state K and V
    App-&amp;gt;&amp;gt;D: Request random bytes
    D-&amp;gt;&amp;gt;D: Increment the counter, then encrypt under AES to produce each output block
    D--&amp;gt;&amp;gt;App: Return the output bytes
    D-&amp;gt;&amp;gt;D: Update step derives and overwrites a fresh K and V
    Note over D: The old K and V are destroyed, so past output cannot be recomputed
&lt;p&gt;Before those raw entropy bytes ever become a key $K$, they pass through a conditioning step the standard calls the derivation function.&lt;/p&gt;

The conditioning step that turns raw entropy of uneven quality into a seed of exactly the length and distribution the DRBG needs. It absorbs more input bits than it emits, spreading whatever unpredictability arrived across every output bit, so no structure in the raw source survives into the seed [@src-sp80090a].
&lt;p&gt;You can feel the forward-secrecy property in a few lines. The key move is that the update overwrites the state with the output of a one-way step, so a later leak cannot walk backward.&lt;/p&gt;
&lt;p&gt;{`
// A toy generator with a one-way update, mirroring the CTR_DRBG idea.
// Real Windows uses AES; here a simple one-way mix stands in for it.
function mix(x) {
  let h = x &amp;gt;&amp;gt;&amp;gt; 0;
  h = Math.imul(h ^ 0x9e3779b9, 2246822519) &amp;gt;&amp;gt;&amp;gt; 0;
  h = (h ^ (h &amp;gt;&amp;gt;&amp;gt; 13)) &amp;gt;&amp;gt;&amp;gt; 0;
  return h &amp;gt;&amp;gt;&amp;gt; 0;
}&lt;/p&gt;
&lt;p&gt;let state = 123456789;                 // the secret internal state&lt;/p&gt;
&lt;p&gt;function generate() {
  const output = mix(state ^ 0x0000abcd); // the bytes we hand out
  state = mix(state);                      // update: overwrite the state
  return output &amp;gt;&amp;gt;&amp;gt; 0;
}&lt;/p&gt;
&lt;p&gt;const out1 = generate();
const out2 = generate();
const leakedNow = state;               // attacker compromises the state here&lt;/p&gt;
&lt;p&gt;console.log(&quot;output 1:&quot;, out1.toString(16));
console.log(&quot;output 2:&quot;, out2.toString(16));
console.log(&quot;state leaked after two draws:&quot;, leakedNow.toString(16));
console.log(&quot;to recover out1 the attacker must invert the update -- that is forward secrecy&quot;);
`}&lt;/p&gt;
&lt;p&gt;The implementation behind all of this is &lt;strong&gt;SymCrypt&lt;/strong&gt;, which the project describes as &quot;the primary crypto library for all algorithms&quot; in Windows since the Windows 10 1703 release, engineered to &quot;support FIPS 140 certification&quot; and to &quot;provide high assurance&quot; [@src-symcrypt]. The generator is not a bespoke one-off; it is part of the same audited library that implements the rest of Windows cryptography.&lt;/p&gt;
&lt;p&gt;This chain-of-generators shape now has a standardized name. NIST&apos;s SP 800-90C, finalized on 25 September 2025, calls a chain of RBGs on one platform -- each an RBG (a DRBG mechanism plus its seeding interface), one feeding the next -- an &lt;strong&gt;RBGC&lt;/strong&gt; construction, the standardized shape that the Windows root-to-per-processor arrangement instantiates as &lt;code&gt;CTR_DRBG&lt;/code&gt;s [@src-sp80090c; @src-nist90cnews2025]. That standard was finalized six years after Ferguson&apos;s 2019 whitepaper documented the Windows tree, so the correspondence is a retrospective analogy rather than a certified conformance to SP 800-90C. We return to the full SP 800-90 construction family, and what it can and cannot promise, in Section 10.&lt;/p&gt;
&lt;p&gt;The architecture facts in this article -- the chain, the 256-bit &lt;code&gt;CTR_DRBG&lt;/code&gt;, the &lt;code&gt;df()&lt;/code&gt; seeding, and the reseed numbers in the next section -- are quoted from Ferguson&apos;s whitepaper, which is a &lt;em&gt;Windows 10&lt;/em&gt; document published in October 2019. They are accurate as documented in 2019; Microsoft has not published a newer primary of comparable detail, so treat the exact figures as of that vintage [@src-win10rng-pdf].&lt;/p&gt;
&lt;p&gt;Line the redesign up against the two failures. &lt;code&gt;CryptGenRandom&lt;/code&gt; ran in user mode; the modern root and kernel generators live in the kernel, out of any process&apos;s reach. &lt;code&gt;CryptGenRandom&lt;/code&gt; reseeded only every 128 KB; the modern chain reseeds on a clock schedule, which the next section unpacks. &lt;code&gt;CryptGenRandom&lt;/code&gt; let a leaked state expose the past; the &lt;code&gt;CTR_DRBG&lt;/code&gt; update overwrites $K$ and $V$ so the past is gone. And against Dual_EC, the mechanism itself is AES in counter mode, whose security rests on the block cipher rather than on two mysterious curve points nobody can audit.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; A DRBG cannot create unpredictability. It can only stretch a seed. So a Windows key is only as unguessable as the entropy behind that seed, which is why the entire architecture is built to protect and diversify the seed rather than to strengthen the cipher.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A &lt;code&gt;CTR_DRBG&lt;/code&gt; is deterministic: feed it the same seed and it produces the same &quot;random&quot; stream forever. Everything, then, rests on one question the whole design has been circling. Where does the unpredictability in that seed actually come from?&lt;/p&gt;
&lt;h2&gt;6. Where the Entropy Comes From&lt;/h2&gt;
&lt;p&gt;A deterministic generator cannot manufacture unpredictability. It can only spend what it is given. So the design problem is not &quot;how do we make randomness&quot; -- the &lt;code&gt;CTR_DRBG&lt;/code&gt; does the stretching -- it is &quot;how do we collect genuine unpredictability, from sources diverse enough that no one of them can quietly poison the well.&quot; Windows answers by gathering entropy from everywhere it can reach and trusting no single source in particular.&lt;/p&gt;
&lt;p&gt;The whitepaper enumerates the sources, and the list is deliberately not exhaustive [@src-win10rng-pdf]. The &lt;strong&gt;primary&lt;/strong&gt; source is the timing jitter of hardware interrupts, sampled with the CPU&apos;s cycle counter via the &lt;code&gt;RDTSC&lt;/code&gt; instruction: the exact cycle at which each interrupt fires is influenced by physical processes an outside attacker cannot observe or steer.&lt;/p&gt;
&lt;p&gt;Alongside it, Windows pulls bytes from the &lt;strong&gt;&lt;a href=&quot;https://paragmali.com/blog/the-tpm-in-windows-one-primitive-twenty-five-years-and-the-c/&quot; rel=&quot;noopener&quot;&gt;TPM&lt;/a&gt;&lt;/strong&gt; (documented as 40 bytes at boot and 64 bytes per reseed, at most once every 40 minutes or so), from Intel&apos;s on-die &lt;strong&gt;&lt;code&gt;RDRAND&lt;/code&gt; and &lt;code&gt;RDSEED&lt;/code&gt;&lt;/strong&gt; instructions, from a &lt;strong&gt;seed file&lt;/strong&gt; kept in the registry and rewritten on each boot to carry entropy forward across restarts, from &lt;strong&gt;UEFI and firmware&lt;/strong&gt; seeds, and on virtual machines from the Hyper-V &lt;strong&gt;ACPI OEM0&lt;/strong&gt; table [@src-win10rng-pdf].&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Cadence (as documented in 2019)&lt;/th&gt;
&lt;th&gt;Trust note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Interrupt timing via &lt;code&gt;RDTSC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timing jitter&lt;/td&gt;
&lt;td&gt;continuous; the primary source&lt;/td&gt;
&lt;td&gt;Hard to observe or steer remotely [@src-win10rng-pdf]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TPM&lt;/td&gt;
&lt;td&gt;hardware RNG&lt;/td&gt;
&lt;td&gt;40 bytes at boot, 64 bytes per reseed, at most once per ~40 min&lt;/td&gt;
&lt;td&gt;Separate chip, independent of the CPU [@src-win10rng-pdf]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RDRAND&lt;/code&gt; / &lt;code&gt;RDSEED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;on-die hardware RNG&lt;/td&gt;
&lt;td&gt;on demand&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RDRAND&lt;/code&gt; capped at 128-bit security; &lt;code&gt;RDSEED&lt;/code&gt; preferred; never trusted alone [@src-win10rng-pdf]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Registry seed file&lt;/td&gt;
&lt;td&gt;stored entropy&lt;/td&gt;
&lt;td&gt;read at boot, rewritten for next boot&lt;/td&gt;
&lt;td&gt;Carries entropy across reboots [@src-win10rng-pdf]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UEFI / firmware seed&lt;/td&gt;
&lt;td&gt;firmware-provided&lt;/td&gt;
&lt;td&gt;at boot&lt;/td&gt;
&lt;td&gt;Platform-dependent [@src-win10rng-pdf]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hyper-V ACPI OEM0 table&lt;/td&gt;
&lt;td&gt;hypervisor-provided&lt;/td&gt;
&lt;td&gt;at boot on guests&lt;/td&gt;
&lt;td&gt;Present for virtualized machines [@src-win10rng-pdf]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Mix many, trust none&lt;/h3&gt;
&lt;p&gt;This is the direct, engineered answer to the Dual_EC fear. Consider Intel&apos;s &lt;code&gt;RDRAND&lt;/code&gt;: it is fast, on-die, and convenient, and a lazier design would simply use it as the system&apos;s randomness. Windows does not. The whitepaper notes that &quot;the &lt;code&gt;RDRAND&lt;/code&gt; instruction only provides random numbers with a 128-bit security level,&quot; so Windows uses &lt;code&gt;RDSEED&lt;/code&gt; &quot;in preference to &lt;code&gt;RDRAND&lt;/code&gt;&quot; and, more importantly, folds it in as one input among many rather than relying on it alone [@src-win10rng-pdf].&lt;/p&gt;
&lt;p&gt;The underlying Intel design is itself an entropy source feeding a conditioner and a &lt;code&gt;CTR_DRBG&lt;/code&gt; [@src-inteldrng] -- a whole pipeline sealed inside a chip you cannot audit. Mixing it with five other independent sources means that even if &lt;code&gt;RDRAND&lt;/code&gt; were compromised or backdoored, it could not by itself determine the seed.&lt;/p&gt;
&lt;p&gt;The 128-bit ceiling is the tell. Windows targets 256-bit security throughout, so a source that tops out at 128 bits can never be the &lt;em&gt;whole&lt;/em&gt; seed without halving the system&apos;s strength. That is a concrete, non-conspiratorial reason to prefer &lt;code&gt;RDSEED&lt;/code&gt; and to always blend -- the mathematics forces diversification before trust ever enters the picture [@src-win10rng-pdf].&lt;/p&gt;

flowchart LR
    A[&quot;Interrupt and RDTSC timing jitter (primary)&quot;] --&amp;gt; P[&quot;SHA-512 entropy pools, up to 8&quot;]
    B[&quot;TPM random bytes&quot;] --&amp;gt; P
    C[&quot;RDRAND and RDSEED&quot;] --&amp;gt; P
    D[&quot;Boot seed file from the registry&quot;] --&amp;gt; P
    E[&quot;UEFI and firmware seed&quot;] --&amp;gt; P
    F[&quot;Hyper-V ACPI OEM0 table&quot;] --&amp;gt; P
    P --&amp;gt; R[&quot;Seed for the root CTR_DRBG&quot;]
&lt;p&gt;The mixing itself is a hash. Sources are folded with &lt;strong&gt;SHA-512&lt;/strong&gt; into as many as &lt;strong&gt;8 entropy pools&lt;/strong&gt;, and the very first seeding happens early, in the boot loader. During &lt;strong&gt;Winload&lt;/strong&gt;, Windows hashes the available sources with SHA-512, uses the result to seed an AES &lt;code&gt;CTR_DRBG&lt;/code&gt;, and hands 48 bytes to CNG so the kernel generator is already seeded before any user code runs [@src-win10rng-pdf].&lt;/p&gt;
&lt;p&gt;There is no window in which Windows serves keys from an unseeded generator -- the boot-time hole that broke so many Unix devices. The design of these entropy sources, their health tests and min-entropy estimates, is governed by NIST SP 800-90B, the companion standard to the DRBG document [@src-sp80090b].&lt;/p&gt;
&lt;p&gt;That completes the second shift in how to think about this machine: security does not come from finding one perfect source of randomness. It comes from combining many imperfect ones so that trusting any single component is never necessary. Distrust is the feature.&lt;/p&gt;
&lt;p&gt;Collecting entropy once, though, is not enough. A generator that seeds at boot and never refreshes slowly drifts back toward the failure that killed &lt;code&gt;CryptGenRandom&lt;/code&gt; -- a long-lived state that, once leaked, stays leaked. So when, and how often, does Windows reseed? And what happens the moment two machines start life from the exact same state?&lt;/p&gt;
&lt;h2&gt;7. Reseeding, Forward Secrecy, and the VM-Clone Problem&lt;/h2&gt;
&lt;p&gt;Reseeding is easy to misunderstand as &quot;topping up&quot; a tank of randomness that slowly drains. It is not. A &lt;code&gt;CTR_DRBG&lt;/code&gt; never runs out of output. Reseeding exists for a different reason: it is how a generator &lt;em&gt;recovers&lt;/em&gt; from a compromise it may never know occurred. If an attacker somehow learns the state, mixing in fresh entropy afterward restores unpredictability going forward. That is prediction resistance, and it is why reseeding is on a schedule rather than an afterthought.&lt;/p&gt;
&lt;p&gt;The schedule, as documented in the 2019 whitepaper, is descended from Fortuna -- the multi-pool accumulator Ferguson co-designed as a successor to Yarrow [@src-yarrow], which itself grew out of a 1998 taxonomy of how real pseudorandom generators get broken in the field [@src-prngattacks1998]. The first reseed is scheduled about &lt;strong&gt;1 second&lt;/strong&gt; after boot. Each subsequent interval is &lt;strong&gt;tripled&lt;/strong&gt;, up to a cap of &lt;strong&gt;3600 seconds&lt;/strong&gt; -- one hour -- with roughly &lt;strong&gt;33% jitter&lt;/strong&gt; so the timing is not perfectly regular [@src-win10rng-pdf].&lt;/p&gt;
&lt;p&gt;Underneath sits a bank of up to &lt;strong&gt;8 SHA-512 entropy pools&lt;/strong&gt;, and pool $k$ is drained into a reseed only every $3^k$-th time. Pool 0 contributes to every reseed; pool 1 to every third; pool 7 only very rarely. The effect of that geometric schedule is subtle and clever: a fast, low-rate attacker who is quietly injecting predictable &quot;entropy&quot; cannot keep all pools poisoned, because the high-numbered pools accumulate real entropy over long stretches before they are ever used [@src-win10rng-pdf].&lt;/p&gt;
&lt;p&gt;Map this onto the two properties from Section 2. The &lt;code&gt;CTR_DRBG&lt;/code&gt; update gives &lt;strong&gt;backtracking resistance&lt;/strong&gt;: a leaked state cannot reveal past output. Reseeding gives &lt;strong&gt;prediction resistance&lt;/strong&gt;: a leaked state cannot predict output past the next reseed. Together they bound the blast radius of a state compromise in both directions of time. The standard also caps a single &lt;code&gt;CTR_DRBG&lt;/code&gt; at $2^{48}$ generate requests before a reseed is mandatory, though Windows&apos; clock schedule reseeds far more often than that ceiling would ever require [@src-sp80090a-pdf].&lt;/p&gt;
&lt;p&gt;That $2^{48}$ figure is a hard limit in SP 800-90A, not a Windows tuning choice. At any realistic request rate the one-hour clock reseed fires enormously sooner, so the ceiling is a safety backstop rather than an operational parameter [@src-sp80090a-pdf].&lt;/p&gt;
&lt;h3&gt;The failure reseeding cannot fully fix&lt;/h3&gt;
&lt;p&gt;There is one deployment scenario that defeats even a perfect reseed schedule, and it is the sharpest residual edge in the whole design. If you &lt;strong&gt;clone a virtual machine, or revert one to a snapshot&lt;/strong&gt;, you duplicate its memory -- including the generator&apos;s state. Two machines that believe they are independent now hold the identical $K$ and $V$, and they will produce the identical &quot;random&quot; stream. They roll the same dice.&lt;/p&gt;
&lt;p&gt;This is not hypothetical. Thomas Ristenpart and Scott Yilek demonstrated in 2010 that resuming a VM from a snapshot replays RNG state and causes servers to emit the same &quot;random&quot; TLS and DSA values twice, leaking keys [@src-ristenpart2010; @src-ristenpart2010-pdf]. Everspaugh and colleagues extended it to the Linux &lt;em&gt;system&lt;/em&gt; RNG in 2014, measuring generators on Xen, VMware, and EC2 that would &quot;output the exact same sequence of bits each time it is resumed from the same snapshot&quot; [@src-whirlwind2014].&lt;/p&gt;

A 128-bit value supplied by the hypervisor that changes whenever a virtual machine experiences a time-shift event -- a snapshot restore, a clone, or a backup restore. Guest software reads it, and a change signals that the machine may be a duplicate that must reseed. Introduced with Windows 8 and Windows Server 2012 [@src-vmgenid].
&lt;p&gt;Windows&apos; defense is to listen for exactly that event. The VM Generation ID is a 128-bit identifier the hypervisor changes on any time-shift [@src-vmgenid]. The whitepaper&apos;s &quot;virtual machine rewind&quot; logic detects the change, retrieves &quot;a unique (not random) value from the hypervisor,&quot; and forces the root generator to reseed so the two instances diverge [@src-win10rng-pdf]. Crucially, Ferguson is candid that this &quot;does not eliminate the vulnerability&quot; [@src-win10rng-pdf] -- it narrows it.&lt;/p&gt;

flowchart TD
    A[&quot;VM is cloned or reverted to a snapshot&quot;] --&amp;gt; B[&quot;Both instances resume with identical generator state&quot;]
    B --&amp;gt; C{&quot;Did the VM Generation ID change?&quot;}
    C --&amp;gt;|Yes| D[&quot;Root generator reseeds, the two states diverge&quot;]
    C --&amp;gt;|No| E[&quot;Both instances roll identical dice&quot;]
    E --&amp;gt; F[&quot;Residual window: any secret made here may repeat on the twin&quot;]
&lt;p&gt;Where does the residual risk live? In two gaps. First, in the sliver of time &lt;em&gt;after&lt;/em&gt; a clone but &lt;em&gt;before&lt;/em&gt; the reseed fires, any secret generated draws on duplicated state. Second, on hypervisors that expose no Generation ID at all, the signal never arrives and the duplication is silent. Precisely quantifying that window for current Windows is not something any primary source pins down -- this is a reasoned consequence of the reseed-on-rewind mechanism and the reset results above, not a measured figure. The honest summary is that the VM-clone problem is mitigated, not solved.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A virtual machine that is cloned or rolled back on a hypervisor that does not expose a VM Generation ID can emit the same &quot;random&quot; values more than once -- a direct route to duplicated TLS, SSH, or signing keys. If you snapshot and clone Windows VMs, confirm your hypervisor surfaces a Generation ID [@src-whirlwind2014; @src-win10rng-pdf].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;All of this machinery lives in the kernel and the boot loader, far below anything an application sees. So how does ordinary code -- your code -- actually reach down and pull bytes out of it?&lt;/p&gt;
&lt;h2&gt;8. The User-Mode Path: ProcessPrng and BCryptGenRandom&lt;/h2&gt;
&lt;p&gt;Everything so far has been under the floorboards. Here is the single line of code that touches it -- and, just as important, the three that you must never write.&lt;/p&gt;
&lt;p&gt;The modern entry point is &lt;code&gt;ProcessPrng&lt;/code&gt;, exported from &lt;code&gt;bcryptprimitives.dll&lt;/code&gt; through the &lt;code&gt;CngRngExt&lt;/code&gt; API set. Microsoft&apos;s documentation is refreshingly terse: it &quot;retrieves a specified number of random bytes from the user-mode per-processor random number generator,&quot; and it &quot;always returns TRUE&quot; [@src-processprng]. There is no failure path to check and no entropy to supply, because the generator it reads from is one of the user-mode per-processor &lt;code&gt;CTR_DRBG&lt;/code&gt;s from Section 5, seeded during your process&apos;s startup from the kernel chain (the process-base generator requests a kernel seed when &lt;code&gt;bcryptprimitives.dll&lt;/code&gt; loads, and the kernel chain itself was seeded at boot, before any process ran). You ask for bytes; you get unpredictable bytes.&lt;/p&gt;
&lt;p&gt;Most code should call &lt;code&gt;BCryptGenRandom&lt;/code&gt;, the recommended CNG randomness API. Two flags shape its behavior, and one of them is a trap. Passing &lt;code&gt;BCRYPT_USE_SYSTEM_PREFERRED_RNG&lt;/code&gt; selects the system&apos;s preferred generator, and when you use it the algorithm handle must be &lt;code&gt;NULL&lt;/code&gt; [@src-bcryptgenrandom]. The trap is &lt;code&gt;BCRYPT_RNG_USE_ENTROPY_IN_BUFFER&lt;/code&gt;, which once let a caller stir its own bytes into the request: the documentation states plainly that &quot;this flag is ignored in Windows 8 and later&quot; [@src-bcryptgenrandom]. Your hand-supplied entropy does nothing, and the API quietly stopped pretending otherwise.&lt;/p&gt;
&lt;p&gt;The older names still work, because they were rerouted rather than reinvented. &lt;code&gt;RtlGenRandom&lt;/code&gt; -- exported from &lt;code&gt;advapi32.dll&lt;/code&gt; as &lt;code&gt;SystemFunction036&lt;/code&gt; -- now carries a Microsoft note telling callers to &quot;use the &lt;code&gt;BCryptGenRandom&lt;/code&gt; or &lt;code&gt;ProcessPrng&lt;/code&gt; functions&quot; instead [@src-rtlgenrandom], and Ferguson&apos;s whitepaper states outright that &quot;RtlGenRandom uses the ProcessPrng function&quot; [@src-win10rng-pdf]. The legacy &lt;code&gt;CryptGenRandom&lt;/code&gt; and the C runtime&apos;s &lt;code&gt;rand()&lt;/code&gt; are deprecated for anything security-sensitive -- the first for its history, the second because it was never a CSPRNG at all.&lt;/p&gt;
&lt;p&gt;On .NET, &lt;code&gt;System.Security.Cryptography.RandomNumberGenerator&lt;/code&gt; wraps this same machinery: on Windows its implementation calls &lt;code&gt;BCryptGenRandom(IntPtr.Zero, ..., BCRYPT_USE_SYSTEM_PREFERRED_RNG)&lt;/code&gt;, so managed code inherits the identical guarantees without touching a P/Invoke [@src-dotnet-rng; @src-bcryptgenrandom].&lt;/p&gt;

Windows is not alone in converging on a two-layer, forward-secure system RNG. Linux exposes `getrandom(2)`, which draws from the kernel urandom source and blocks until the pool is initialized unless `GRND_NONBLOCK` is set [@src-getrandom]. OpenBSD&apos;s `arc4random` family &quot;was replaced with the ChaCha20 cipher&quot; in OpenBSD 5.5 and is &quot;re-seeded from the kernel random(4) subsystem using getentropy(2) on a regular basis, and also upon fork(2)&quot;; its `arc4random_uniform` avoids the modulo bias that trips up naive `% n` reductions [@src-arc4random]. Both lean on the fast-key-erasure construction Daniel J. Bernstein describes, which overwrites the key immediately after use -- &quot;in NIST terminology, it is a failure of backtracking resistance&quot; that erasure prevents [@src-djbrandom]. Different cipher, same shape: collect entropy, key a fast generator, erase forward.
&lt;p&gt;The head-to-head is worth seeing in one place, because it shows that the interesting differences are architectural, not about who has the better cipher.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Windows CNG chain&lt;/th&gt;
&lt;th&gt;Linux &lt;code&gt;getrandom&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;OpenBSD/FreeBSD &lt;code&gt;arc4random&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Fortuna (pattern)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Output core&lt;/td&gt;
&lt;td&gt;AES &lt;code&gt;CTR_DRBG&lt;/code&gt;, 256-bit&lt;/td&gt;
&lt;td&gt;ChaCha20&lt;/td&gt;
&lt;td&gt;ChaCha20&lt;/td&gt;
&lt;td&gt;block cipher&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forward secrecy&lt;/td&gt;
&lt;td&gt;DRBG update step&lt;/td&gt;
&lt;td&gt;fast-key-erasure&lt;/td&gt;
&lt;td&gt;fast-key-erasure&lt;/td&gt;
&lt;td&gt;DRBG update&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrency&lt;/td&gt;
&lt;td&gt;per-processor state&lt;/td&gt;
&lt;td&gt;per-CPU state&lt;/td&gt;
&lt;td&gt;per-process pool&lt;/td&gt;
&lt;td&gt;not specified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VM-reset defense&lt;/td&gt;
&lt;td&gt;VM Generation ID reseed&lt;/td&gt;
&lt;td&gt;improving over time&lt;/td&gt;
&lt;td&gt;reseed on &lt;code&gt;fork()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;not specified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary API&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BCryptGenRandom&lt;/code&gt; / &lt;code&gt;ProcessPrng&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;getrandom(2)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;arc4random_buf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Sources for the table: the Windows column from Ferguson&apos;s whitepaper and the CNG docs [@src-win10rng-pdf; @src-processprng]; Linux from &lt;code&gt;getrandom(2)&lt;/code&gt; [@src-getrandom]; OpenBSD/FreeBSD from &lt;code&gt;arc4random(3)&lt;/code&gt; [@src-arc4random; @src-arc4random-freebsd]; Fortuna from its design page [@src-fortuna]. The standout row is VM-reset defense: Windows is unusual in having an explicit hypervisor-signaled reseed, a direct consequence of the clone problem in the last section.&lt;/p&gt;
&lt;p&gt;One API, one per-processor &lt;code&gt;CTR_DRBG&lt;/code&gt;, one shared foundation. Which means we can finally close the loop: every secret Windows makes in software is drawing from the same well. Let us follow the pipes and see exactly which secrets those are -- and the one place the pipes do not reach.&lt;/p&gt;
&lt;h2&gt;9. How Every Windows Secret Draws on This -- and Where It Doesn&apos;t&lt;/h2&gt;
&lt;p&gt;Now the loop closes. Trace almost any Windows secret back far enough and you arrive at the same per-processor &lt;code&gt;CTR_DRBG&lt;/code&gt; from Section 5. The generator is not one feature among many; it is the shared root of the whole key hierarchy.&lt;/p&gt;
&lt;p&gt;Here is the claim stated precisely, because precision is what makes it defensible rather than a slogan. &lt;code&gt;BCryptGenRandom&lt;/code&gt; and &lt;code&gt;ProcessPrng&lt;/code&gt; &lt;em&gt;are&lt;/em&gt; the CNG software CSPRNG -- Cryptography API: Next Generation, the subject of the companion post on &lt;a href=&quot;https://paragmali.com/blog/cng-architecture-bcrypt-ncrypt-ksps/&quot; rel=&quot;noopener&quot;&gt;CNG architecture&lt;/a&gt;, has no second random source hiding behind them. And the whitepaper is explicit that the root generator&apos;s reach is total: &quot;All random bytes in the system are derived in some way from this PRNG&quot; [@src-win10rng-pdf; @src-processprng].&lt;/p&gt;
&lt;p&gt;So the linkage from any subsystem to the Section 5 chain is &lt;strong&gt;architectural&lt;/strong&gt;, not a claim that each component calls &lt;code&gt;BCryptGenRandom&lt;/code&gt; at some named line: on modern Windows, CNG is the sole software CSPRNG, so any key or nonce generated &lt;em&gt;in software&lt;/em&gt; bottoms out in that chain by construction.&lt;/p&gt;
&lt;p&gt;With that framing, the dependencies fan out, and each one has a primary source confirming its key material is randomly generated.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://paragmali.com/blog/dpapi-and-dpapi-ng-the-credential-vault-under-everything/&quot; rel=&quot;noopener&quot;&gt;DPAPI and DPAPI-NG&lt;/a&gt;&lt;/strong&gt;, the credential vault under nearly every stored Windows secret: classic DPAPI builds on a MasterKey that Microsoft documents as &quot;512 bits of random data,&quot; and each protection operation derives a session key from that MasterKey plus &quot;16 bytes of random data&quot; [@src-dpapi]. DPAPI-NG&apos;s key hierarchy differs, but it likewise draws its key material from the same CNG CSPRNG. Either way, that randomness is CNG&apos;s.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://paragmali.com/blog/bitlocker-on-windows-architecture-attacks-and-the-limits-of-/&quot; rel=&quot;noopener&quot;&gt;BitLocker&lt;/a&gt;&lt;/strong&gt; generates its Volume Master Key and Full Volume Encryption Key when a volume is first encrypted in software, wrapping the FVEK under the VMK in the key hierarchy Microsoft documents [@src-bitlocker]; that key material, like all software randomness on the system, is drawn from the CNG CSPRNG [@src-win10rng-pdf].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://paragmali.com/blog/rotating-every-cipher-schannel-and-the-twenty-year-algorithm/&quot; rel=&quot;noopener&quot;&gt;Schannel&lt;/a&gt;&lt;/strong&gt;, the Windows TLS stack [@src-schannel], pulls per-handshake randomness and ephemeral (EC)DHE secrets on every connection through the same CryptoAPI/CNG plumbing [@src-win10rng-pdf].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Machine-account passwords and computer-object secrets&lt;/strong&gt; bottom out in the same place. Randomly-generated (version-4) GUIDs, when produced through CNG, inherit its randomness, but GUIDs are not in general guaranteed to be unpredictable and should not be relied on as security tokens [@src-rfc4122].&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Break the generator and you do not break one of these -- you weaken all of them at once, which is precisely why the design invests so heavily in protecting the seed. The DPAPI, BitLocker, and Schannel companion posts trace each of these key hierarchies in detail.&lt;/p&gt;
&lt;p&gt;That is the thesis, and it would be an overstatement if it stopped there. So here is the boundary that keeps it honest, and naming it is what makes the universal claim true rather than sloppy.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Every &lt;em&gt;software&lt;/em&gt; secret Windows generates draws on one CSPRNG. But keys born &lt;em&gt;inside&lt;/em&gt; a TPM or a smart card are generated by that device&apos;s own hardware random number generator -- the CNG software &lt;code&gt;CTR_DRBG&lt;/code&gt; is not in that path. That boundary is exactly where &quot;the OS rolls the dice&quot; stops and the hardware rolls its own.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When you generate a key through the Platform Crypto Provider -- the TPM key storage provider -- or through a smart-card provider, the private key can be created and kept on the device and never exist as software-accessible bytes. The randomness for that key comes from the TPM&apos;s or the card&apos;s on-chip RNG, governed by its own certification, not from the Windows &lt;code&gt;CTR_DRBG&lt;/code&gt;; the software chain the whitepaper describes simply is not on that path [@src-win10rng-pdf]. The companion post on the TPM in Windows follows it in detail.&lt;/p&gt;
&lt;p&gt;The distinction is not academic: a TPM-resident key does not inherit the VM-clone caveat from Section 7 the way a software key does, because its generation never touched the cloned software state. Conversely, it inherits whatever assurance -- or opacity -- the device&apos;s own RNG carries.&lt;/p&gt;
&lt;p&gt;This is why the thesis is scoped to &lt;em&gt;software&lt;/em&gt; secrets. It is not a hedge; it is a map. The set of things that draw on the CNG generator is enormous and includes essentially everything an application, a protocol stack, or the OS itself produces in memory. The set that does not is small, specific, and deliberate: the keys you asked the hardware to make and keep. A precise claim tells you both where to look when you reason about randomness and where that reasoning hands off to a different chip.&lt;/p&gt;
&lt;p&gt;So the OS rolls the dice for everything it makes in software, and -- as the last several sections argued -- it rolls them well: kernel-resident, forward-secure, diversely seeded, clock-reseeded, clone-aware. That is a genuinely strong position. But there is a limit no amount of good engineering can cross, and it is not a bug to be patched. It is a fact about information itself.&lt;/p&gt;
&lt;h2&gt;10. Theoretical Limits: You Cannot Conjure Entropy&lt;/h2&gt;
&lt;p&gt;Here is the uncomfortable truth beneath all of it. A &lt;code&gt;CTR_DRBG&lt;/code&gt; cannot create a single bit of unpredictability. It can only spread out what it was seeded with. Run AES a billion times and the output is exactly as unpredictable as the entropy in its seed, and never beyond its 256-bit security strength -- no more. The generator is a magnificent amplifier of entropy and a producer of none.&lt;/p&gt;
&lt;p&gt;That has a hard consequence. The &lt;strong&gt;min-entropy of the seed is an upper bound on the security of everything drawn from it&lt;/strong&gt;. A 256-bit key generated from a seed carrying only 30 bits of real min-entropy has 30 bits of security, not 256, no matter how flawless the cipher is. The attacker does not fight AES; they guess among $2^{30}$ possible seeds. This is the same failure as Netscape and Debian, stated as a theorem rather than a bug: unpredictability out cannot exceed unpredictability in.&lt;/p&gt;
&lt;p&gt;The theory takes this seriously by splitting a secure generator into two phases with different characters. The first, entropy extraction, is &lt;strong&gt;information-theoretic&lt;/strong&gt; -- it is about genuinely accumulating unpredictability. The second, output generation, is &lt;strong&gt;computational&lt;/strong&gt; -- it stretches the accumulated seed using a cipher. Barak and Halevi made that separation rigorous in 2005, showing the extraction step &quot;is information-theoretic in nature&quot; while the generation step can be built from any standard cryptographic PRG [@src-barakhalevi2005].&lt;/p&gt;

The modern security goal for a generator with input. A generator meeting this goal must keep output unpredictable, recover its security after a state compromise once enough fresh entropy is absorbed (prediction resistance), and protect past output (backtracking resistance) -- all at once, even against an adversary who partly controls the entropy source. Formalized by Dodis and colleagues in 2013 [@src-dodis2013].
&lt;p&gt;That model, &lt;strong&gt;robustness&lt;/strong&gt;, is now &quot;the standard security goal for PRNGs&quot; [@src-hoangshen2020]. It folds resilience, prediction resistance, and backtracking resistance into one game an adversary plays against the whole generator, entropy source included. And the good news is real: the mechanisms Windows relies on have been analyzed against it.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Built from&lt;/th&gt;
&lt;th&gt;Meets the robustness goal?&lt;/th&gt;
&lt;th&gt;Known footgun&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CTR_DRBG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AES in counter mode&lt;/td&gt;
&lt;td&gt;Yes, patched (Hoang and Shen, 2020) [@src-hoangshen2020]&lt;/td&gt;
&lt;td&gt;Implementation flexibility enables cache side-channels [@src-cohney2019]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HMAC_DRBG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HMAC over a hash&lt;/td&gt;
&lt;td&gt;In the random-oracle model, with a caveat [@src-woodageshumow2018]&lt;/td&gt;
&lt;td&gt;No forward security if the optional input is omitted [@src-woodageshumow2018]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Hash_DRBG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a hash function&lt;/td&gt;
&lt;td&gt;In the random-oracle model, with a caveat [@src-woodageshumow2018]&lt;/td&gt;
&lt;td&gt;An over-flexible standard&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Read the table honestly, though, and the caveats are the point. Hoang and Shen proved the &lt;strong&gt;patched&lt;/strong&gt; &lt;code&gt;CTR_DRBG&lt;/code&gt; satisfies robustness, and only under the assumption that AES behaves as an ideal cipher [@src-hoangshen2020]. Woodage and Shumow proved &lt;code&gt;Hash_DRBG&lt;/code&gt; and &lt;code&gt;HMAC_DRBG&lt;/code&gt; satisfy robustness in the random-oracle model, and found that &lt;code&gt;HMAC_DRBG&lt;/code&gt; loses forward security entirely if an optional input the standard treats as discretionary is omitted [@src-woodageshumow2018]. Every one of these results holds &lt;em&gt;up to an assumption&lt;/em&gt;: an idealized primitive and a leak-free, constant-time implementation.&lt;/p&gt;
&lt;p&gt;Reality violates that assumption. Cohney and colleagues turned &lt;code&gt;CTR_DRBG&lt;/code&gt;&apos;s implementation flexibility into practical &lt;strong&gt;cache side-channel attacks&lt;/strong&gt; on deployed code, recovering state that the security proofs assumed was hidden [@src-cohney2019]. The proof says &quot;secure if the code does not leak.&quot; The attack says &quot;the code leaks.&quot; Both are true, and the gap between them is where real vulnerabilities live.&lt;/p&gt;

It is tempting to think you can validate a generator by running its output through Dieharder or the NIST statistical test suite. You cannot, at least not for the property that matters. Those suites detect gross bias and obvious structure -- a generator that is broken in a way a statistician would notice. They say nothing about cryptographic unpredictability. `Dual_EC_DRBG` would sail through every one of them, because its output passes standard statistical test batteries; the *catastrophic* weakness is a trapdoor, not the small, known output bias [@src-dualecbias]. Passing the tests is necessary and nowhere near sufficient.
&lt;p&gt;The construction-level standard meant to tie entropy sources to DRBGs, SP 800-90C, is now finished. NIST &lt;strong&gt;finalized it on 25 September 2025&lt;/strong&gt;, calling it &quot;the final document in the SP 800-90 series,&quot; and it specifies four RBG constructions: RBG1, RBG2, RBG3, and the chained &lt;strong&gt;RBGC&lt;/strong&gt; [@src-sp80090c; @src-sp80090c-doi; @src-nist90cnews2025].&lt;/p&gt;
&lt;p&gt;RBG3 constructions target full-entropy output, the continuous-fresh-entropy ideal; &lt;strong&gt;RBGC&lt;/strong&gt; names the chain-of-RBGs shape -- each RBG (a DRBG mechanism plus its seeding interface) on one platform seeding the next -- that the Windows root-to-per-processor arrangement instantiates as &lt;code&gt;CTR_DRBG&lt;/code&gt;s. Beneath it, SP 800-90B governs the design and health testing of the entropy sources feeding the whole construction [@src-sp80090b]. The framework for reasoning about the full stack, in other words, is now complete on paper -- which throws the remaining gap into sharper relief.&lt;/p&gt;
&lt;p&gt;So the ceiling is the seed&apos;s min-entropy, and there is no way to run a tape measure over a live source and read off its true entropy. Every security proof in this article is conditioned on a &quot;sufficiently seeded&quot; premise that can be assumed but never fully verified. That is not a flaw in Windows; it is the shape of the problem. Which raises the obvious question: if you cannot measure entropy and cannot prove code leak-free, what exactly is still open -- and how much of it should keep you up at night?&lt;/p&gt;
&lt;h2&gt;11. Open Problems and Live Concerns&lt;/h2&gt;
&lt;p&gt;The generator is, for practical purposes, solved. The things around it are not. Five honest frontiers remain, and knowing them tells you exactly where to be careful.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trusting hardware entropy you cannot audit.&lt;/strong&gt; &lt;code&gt;RDRAND&lt;/code&gt; and &lt;code&gt;RDSEED&lt;/code&gt; are sealed pipelines inside a chip: an entropy source, a conditioner, and a &lt;code&gt;CTR_DRBG&lt;/code&gt;, none of which you can inspect [@src-inteldrng]. Windows&apos; mix-many-trust-none design means a single compromised hardware source cannot own the seed [@src-win10rng-pdf], which is the right defensive posture. But &quot;cannot dominate&quot; is not &quot;proven honest.&quot; No amount of mixing can &lt;em&gt;demonstrate&lt;/em&gt; that a hardware source is behaving; it can only ensure that its misbehavior is survivable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Entropy estimation is essentially unsolvable.&lt;/strong&gt; There is no sound, general way to measure the true min-entropy of a running source online -- to look at a stream and certify &quot;this carries 200 bits.&quot; Fortuna&apos;s design philosophy is to &lt;em&gt;avoid&lt;/em&gt; estimation entirely, reseeding on a schedule rather than when an estimator declares &quot;enough&quot; [@src-fortuna]. Dodis and colleagues showed that flawed estimators are themselves an attack surface, adding that &quot;it remains unclear if these attacks lead to actual exploitable vulnerabilities in practice&quot; [@src-dodis2013]. SP 800-90B specifies health tests, but a health test detects failure; it does not measure abundance [@src-sp80090b; @src-sp80090b-pdf].&lt;/p&gt;
&lt;p&gt;This is why &quot;add your own entropy&quot; is bad advice: you would be trusting an entropy estimate no one can validate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The boot, headless, container, and VM residual window.&lt;/strong&gt; The most damaging real-world randomness failures have all happened when a system generated keys before it had gathered enough entropy. &quot;Mining Your Ps and Qs&quot; scanned the internet and found tens of thousands of hosts sharing or exposing factorable keys, tracing the cause to a &quot;boot-time entropy hole&quot; on headless and embedded devices that generated keys at first boot [@src-heninger2012; @src-factorable].&lt;/p&gt;
&lt;p&gt;The mitigations -- seed files carried across reboots, Linux&apos;s &lt;code&gt;getrandom()&lt;/code&gt; blocking until initialized (its &lt;code&gt;random(7)&lt;/code&gt; manual warns that early urandom output &quot;may be low entropy and unsuitable&quot;) [@src-random7], and Windows&apos; Winload seeding plus VM Generation ID -- have narrowed this dramatically. They have not closed it. A freshly provisioned container or a cloned VM in the wrong instant is still the scenario to watch.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Constant-time, side-channel-free implementations.&lt;/strong&gt; The Cohney cache attacks from the last section were not breaks of the mathematics; they were breaks of the &lt;em&gt;code&lt;/em&gt; [@src-cohney2019]. Keeping every step of a generator constant-time and free of state-dependent memory access is an ongoing engineering discipline, not a solved problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;End-to-end formal verification.&lt;/strong&gt; SymCrypt is explicitly engineered to &quot;provide high assurance&quot; and to support FIPS 140 certification [@src-symcrypt], and pieces of the cryptographic stack have been formally analyzed. But a single machine-checked proof covering the entire path -- from interrupt-timing collection through the SHA-512 pools to the per-processor &lt;code&gt;CTR_DRBG&lt;/code&gt; and out through &lt;code&gt;ProcessPrng&lt;/code&gt; -- is not yet reality.&lt;/p&gt;

Enabling FIPS mode does not swap in a different generator. Windows already uses an SP 800-90A `CTR_DRBG` implemented in SymCrypt, which is built for FIPS 140 validation [@src-symcrypt]. What FIPS mode does is constrain the system to the validated set of mechanisms and enforce the associated self-tests and boundaries. The architecture -- the chain, the mixing, the reseed schedule -- is the same with FIPS mode on or off. It is a compliance and configuration control, not a security upgrade to the RNG itself.
&lt;p&gt;A final clarification, because it comes up constantly. The arrival of quantum computing does &lt;strong&gt;not&lt;/strong&gt; threaten the quality of Windows&apos; randomness. Shor&apos;s algorithm attacks the hard problems behind RSA and elliptic curves [@src-shor1997]; it does not make a well-seeded &lt;code&gt;CTR_DRBG&lt;/code&gt; predictable, and a good CSPRNG needs no &lt;a href=&quot;https://paragmali.com/blog/post-quantum-cryptography-on-windows-the-thirty-year-migrati/&quot; rel=&quot;noopener&quot;&gt;post-quantum&lt;/a&gt; redesign.&lt;/p&gt;
&lt;p&gt;If anything, randomness becomes &lt;em&gt;more&lt;/em&gt; foundational in a post-quantum world, because most of the new key-generation algorithms draw more fresh randomness per key than an elliptic-curve key does: ML-KEM consumes 64 bytes of RBG output [@src-fips203] and SLH-DSA between 48 and 96 bytes [@src-fips205], against roughly the 32-byte scalar an elliptic-curve or EdDSA key needs, while ML-DSA draws about the same, a 32-byte seed [@src-fips204].&lt;/p&gt;
&lt;p&gt;This is not a blanket claim against &lt;em&gt;all&lt;/em&gt; classical schemes -- RSA key generation already rejection-samples large primes and consumes substantial randomness of its own -- but for the elliptic-curve keys these algorithms replace, the fixed draw is as large or larger. The generator does not need to change; it just gets asked for more, more often. And with SP 800-90C now final as of 2025, the remaining standards work is not the constructions themselves but validating the entropy sources that feed them under SP 800-90B [@src-sp80090b].&lt;/p&gt;
&lt;p&gt;None of this should scare you off the platform. It should do the opposite: tell you precisely what the OS handles for you, and what small set of things are yours to get right.&lt;/p&gt;
&lt;h2&gt;12. A Practical Guide&lt;/h2&gt;
&lt;p&gt;The whole argument reduces to a short list of rules, and most of them are about what &lt;em&gt;not&lt;/em&gt; to do. The generator is the OS&apos;s job; your job is to call it correctly and stay out of its way.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Call this&lt;/th&gt;
&lt;th&gt;Never for secrets&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Windows (native)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BCryptGenRandom&lt;/code&gt; with &lt;code&gt;BCRYPT_USE_SYSTEM_PREFERRED_RNG&lt;/code&gt;, or &lt;code&gt;ProcessPrng&lt;/code&gt; [@src-processprng; @src-bcryptgenrandom]&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rand()&lt;/code&gt;, legacy &lt;code&gt;CryptGenRandom&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.NET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;System.Security.Cryptography.RandomNumberGenerator&lt;/code&gt; [@src-dotnet-rng]&lt;/td&gt;
&lt;td&gt;&lt;code&gt;System.Random&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;&lt;code&gt;getrandom()&lt;/code&gt; or &lt;code&gt;getentropy()&lt;/code&gt; [@src-getrandom]&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/dev/urandom&lt;/code&gt; read before init, &lt;code&gt;rand()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BSD / macOS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;arc4random_buf&lt;/code&gt;, &lt;code&gt;arc4random_uniform&lt;/code&gt; [@src-arc4random]&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rand()&lt;/code&gt;, &lt;code&gt;random()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The DO side is short because the right answer is almost always &quot;ask the OS.&quot; The DON&apos;T side is where the real bugs live. Never use &lt;code&gt;rand()&lt;/code&gt; or the Mersenne Twister for anything an adversary should not predict [@src-mt]. Never reach for legacy &lt;code&gt;CryptGenRandom&lt;/code&gt;. Never hand-seed the generator with your own entropy (see the callout below).&lt;/p&gt;
&lt;p&gt;And watch three subtler traps: modulo bias when reducing random bytes into a range, generator state inherited across a &lt;code&gt;fork()&lt;/code&gt; or a VM clone, and key generation that happens too early in boot.&lt;/p&gt;
&lt;p&gt;That first trap -- modulo bias -- is the one people rediscover constantly, so it is worth seeing fixed. Reducing a uniform byte with &lt;code&gt;% n&lt;/code&gt; skews the result whenever &lt;code&gt;n&lt;/code&gt; does not divide the range evenly. Rejection sampling fixes it by discarding the few values that would skew things.&lt;/p&gt;
&lt;p&gt;{`
// In real code the bytes come from the OS CSPRNG:
//   Windows: BCryptGenRandom / ProcessPrng
//   .NET:    RandomNumberGenerator.Fill
//   Linux:   getrandom()   BSD/macOS: arc4random_buf
// Here we simulate a source of uniform bytes.
function secureRandomByte() {
  return Math.floor(Math.random() * 256);   // stand-in for the OS CSPRNG
}&lt;/p&gt;
&lt;p&gt;// WRONG: &apos;byte % 6&apos; is biased, because 256 is not a multiple of 6.
function rollBiased() {
  return (secureRandomByte() % 6) + 1;
}&lt;/p&gt;
&lt;p&gt;// RIGHT: rejection sampling discards the few skewing values.
function rollFair() {
  const limit = 256 - (256 % 6);            // 252, the largest multiple of 6 below 256
  let b;
  do { b = secureRandomByte(); } while (b &amp;gt;= limit);
  return (b % 6) + 1;
}&lt;/p&gt;
&lt;p&gt;const biased = [0,0,0,0,0,0,0];
const fair   = [0,0,0,0,0,0,0];
for (let i = 0; i &amp;lt; 600000; i++) {
  biased[rollBiased()]++;
  fair[rollFair()]++;
}
console.log(&quot;biased % 6 :&quot;, biased.slice(1));
console.log(&quot;fair reject:&quot;, fair.slice(1));
console.log(&quot;low faces are over-represented in the biased row&quot;);
`}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The instinct to &quot;strengthen&quot; a key by stirring in your own randomness is almost always counterproductive. The OS pool is conditioned from many independent sources; app-supplied entropy is usually worse and can only dilute. On Windows 8 and later, the flag that once allowed it is ignored outright [@src-bcryptgenrandom]. Call the system generator and trust the architecture the last eleven sections described.&lt;/p&gt;
&lt;/blockquote&gt;

On Windows, in PowerShell, `[System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32)` returns 32 cryptographically secure bytes through the CNG path [@src-dotnet-rng]. On Linux, a program calling `getrandom(buf, 32, 0)` blocks only if the pool is not yet initialized, then fills the buffer [@src-getrandom]. On OpenBSD or macOS, `arc4random_buf(buf, 32)` never blocks and never fails [@src-arc4random]. In every case you are reaching the same two-layer design: an entropy-fed pool keying a fast, forward-secure generator.


No. Windows shipped `Dual_EC_DRBG` only as a non-default SP 800-90 option -- available if an application explicitly requested it, never the system default -- and later removed it [@src-msdualec]. The case where it was reportedly the default is RSA&apos;s BSAFE toolkit, per Reuters reporting of a \$10 million contract, and NIST withdrew the algorithm in 2014 citing a lack of public confidence [@src-nistremoval2014; @src-reuters2013].


No. Windows treats `RDRAND` as one input among many, notes that it provides only a 128-bit security level, prefers `RDSEED`, and never relies on any single hardware source alone. Everything is mixed through SHA-512 pools so no one source can determine the seed [@src-win10rng-pdf].


No. Keys generated and held inside a TPM or a smart card come from that device&apos;s own hardware random number generator. The CNG software `CTR_DRBG` is not in that path. This is the explicit boundary of the claim that the OS rolls the dice for software secrets [@src-win10rng-pdf].


On native Windows, `BCryptGenRandom` (with `BCRYPT_USE_SYSTEM_PREFERRED_RNG`) or `ProcessPrng`. On .NET, `RandomNumberGenerator`, which wraps `BCryptGenRandom` on Windows. Never `rand()` or legacy `CryptGenRandom` for anything secret [@src-processprng; @src-bcryptgenrandom; @src-dotnet-rng].


You can detect gross failure, but statistical suites cannot prove cryptographic security. `Dual_EC_DRBG` would pass Dieharder and the NIST test suite while remaining predictable to whoever holds its trapdoor. Statistical quality is necessary, not sufficient [@src-mt].


No. Windows seeds its generator in the Winload boot loader before any user code runs, so there is no blocking wait at the API. Contrast Linux `getrandom()`, which blocks until the kernel pool is initialized unless you pass a non-blocking flag [@src-win10rng-pdf; @src-getrandom].


No. See the &quot;Do not add your own entropy&quot; callout above.

&lt;p&gt;Which brings the argument back to where it started, now with the evidence behind it. Netscape and Debian did not lose to better cryptanalysis; they lost because their keys were drawn from dice an attacker could read. Windows lost the same way once, with &lt;code&gt;CryptGenRandom&lt;/code&gt;, and the industry nearly lost a third time to a standardized algorithm whose own constants were the weapon.&lt;/p&gt;
&lt;p&gt;The answer Windows built is not a better cipher -- AES was never the problem -- but a better way to roll: kernel-resident, forward-secure &lt;code&gt;CTR_DRBG&lt;/code&gt;s in a per-processor chain, seeded from many sources so no single one can cheat, reseeded on a clock, and watching for the clone that would make two machines roll alike.&lt;/p&gt;
&lt;p&gt;Every software secret the OS makes inherits that. The one place it hands the dice to someone else is the TPM and the smart card, which roll their own. Know where that line is, call the system generator, and the unguessability of your keys takes care of itself.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;windows-rng-cng-csprng&quot; keyTerms={[
  { term: &quot;Min-entropy&quot;, definition: &quot;The worst-case measure of unpredictability of a source; it upper-bounds the security of every key drawn from a seed.&quot; },
  { term: &quot;DRBG&quot;, definition: &quot;Deterministic Random Bit Generator: an algorithm that stretches a seed into many output bits but creates no new unpredictability.&quot; },
  { term: &quot;CSPRNG&quot;, definition: &quot;A DRBG whose output no efficient adversary can distinguish from uniform random bits, even given prior output.&quot; },
  { term: &quot;CTR_DRBG&quot;, definition: &quot;The SP 800-90A DRBG built from AES in counter mode with a state (key K and counter V) that is overwritten after every request. Windows uses it at 256-bit strength.&quot; },
  { term: &quot;Backtracking resistance&quot;, definition: &quot;Forward secrecy: a state compromise cannot reveal past output, because the state update is one-way.&quot; },
  { term: &quot;Prediction resistance&quot;, definition: &quot;After fresh entropy is mixed in, output becomes unpredictable again even if the prior state leaked; this is why generators reseed.&quot; },
  { term: &quot;Entropy source (TRNG)&quot;, definition: &quot;A physical process producing genuine unpredictability, such as interrupt-timing jitter, RDSEED, or a TPM, as opposed to an algorithm.&quot; },
  { term: &quot;VM Generation ID&quot;, definition: &quot;A 128-bit hypervisor value that changes on a snapshot or clone, signaling the guest RNG to reseed so cloned machines do not roll identical dice.&quot; },
  { term: &quot;ProcessPrng&quot;, definition: &quot;The user-mode CNG entry point that returns bytes from the user-mode per-processor CTR_DRBG, seeded from the kernel chain.&quot; },
  { term: &quot;BCryptGenRandom&quot;, definition: &quot;The recommended CNG randomness API; with BCRYPT_USE_SYSTEM_PREFERRED_RNG it draws from the system generator.&quot; }
]} questions={[
  { q: &quot;Why is passing every statistical randomness test not enough for a key generator?&quot;, a: &quot;Statistical tests detect bias and structure but not predictability. A generator like the Mersenne Twister or Dual_EC_DRBG can produce statistically excellent output whose state or future values are recoverable, so it fails as a CSPRNG despite passing the tests.&quot; },
  { q: &quot;What exactly was the 2007 CryptGenRandom break?&quot;, a: &quot;Dorrendorf, Gutterman, and Pinkas reverse-engineered the legacy generator and showed it ran in user mode with per-process state reseeded only every 128 KB. Given one state, the previous state was recoverable in about 2^23 work, so a single leak exposed roughly 128 KB of past and future output.&quot; },
  { q: &quot;Why does Windows mix many entropy sources instead of trusting one good one?&quot;, a: &quot;Because trust bottoms out on both the algorithm and its sources. Mixing many independent sources through SHA-512 pools means that even a compromised or capped source, such as RDRAND, cannot by itself determine the seed. Distrust is designed in.&quot; },
  { q: &quot;Where does the claim &apos;the OS rolls the dice for every secret&apos; stop being true?&quot;, a: &quot;At hardware-resident keys. Keys generated inside a TPM or a smart card come from the device&apos;s own hardware RNG, not the CNG software CTR_DRBG, so the software generator is not in that path.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows</category><category>cryptography</category><category>csprng</category><category>ctr-drbg</category><category>entropy</category><category>cng</category><category>randomness</category><category>security</category><author>noreply@paragmali.com (Parag Mali)</author></item></channel></rss>