Skip to content
woza.ink
← Blog
13 min read

Where Randomness Actually Comes From

True RNG vs pseudorandom, how your machine actually collects entropy, why 100 dice rolls make a Bitcoin seed, and the three different things people mean when they say 'random'.

On this page
  1. The three properties
  2. True random vs pseudorandom
  3. What your computer actually does
  4. The urandom question, settled
  5. Physical entropy theater
  6. Dice: the case for doing it by hand
  7. The arithmetic
  8. Converting rolls to bits without introducing bias
  9. Details that actually matter
  10. Randomness other people have to trust
  11. When it goes wrong
  12. Practical summary
  13. Further reading

Ask for a random number and you'll get one. Ask where it came from and things get interesting fast.

Most randomness failures aren't failures of the algorithm. They're failures of definition — someone needed one kind of randomness and reached for a tool that provides a different kind. The number looked random. It was even statistically random. It just wasn't the property that was actually needed.

There are three properties, and almost nothing provides all of them.1


The three properties

Unpredictability. Nobody can guess the next value, even knowing every previous one. This is what cryptographic keys need. A nonce that an attacker can predict is not a nonce.

Unbiasability. No participant can influence the outcome, even a little, even at the cost of giving up their turn. This is what lotteries, leader election, and any adversarial draw need. A value can be completely unpredictable to outsiders and still be quietly steerable by whoever generates it.

Verifiability. Anyone can confirm after the fact that the value was generated honestly. This is what public draws need — where you don't want participants to trust you, because trusting you is the thing they'd rather not do.

/dev/urandom gives you unpredictability. It gives you no verifiability whatsoever — you cannot prove to anyone that you didn't just pick your favorite number. A blockchain commit-reveal scheme gives you verifiability and can be made unbiasable, and is often entirely predictable to the first participant. These are different tools.

Pick the wrong one and the failure is usually silent, which is what makes this category of bug so nasty.


True random vs pseudorandom

A TRNG (true random number generator) harvests entropy from a physical process believed to be fundamentally unpredictable: thermal noise, shot noise, radioactive decay, quantum measurement, oscillator jitter. Its output is non-deterministic. It is also slow, sometimes biased, and hard to verify — a serious problem because a broken hardware RNG and a working one produce output that looks identical.

A PRNG is a deterministic algorithm. Feed it a seed, get a long stream that passes statistical tests. Same seed, same stream, always. Fast and reproducible — exactly what you want for simulations and games. Math.random() and rand() live here, and neither belongs anywhere near a key.

A CSPRNG is a PRNG with an extra guarantee: given any amount of output, you cannot compute the next value or recover the seed in practical time. ChaCha20-based generators, HMAC-DRBG, and the Linux kernel's RNG are in this family.

The practical architecture nearly everything uses is TRNG seeds a CSPRNG2. Collect a few hundred bits of real physical entropy — slow, awkward, once — then stretch it into gigabytes of unpredictable output with fast symmetric cryptography. You need true randomness exactly once, at the start. Everything after that is arithmetic.

This is why the seed is where the bodies are buried.


What your computer actually does

The Linux kernel maintains an entropy pool fed from several sources: interrupt timing, disk and network jitter, keyboard and mouse events where available, and dedicated hardware.

RDRAND is Intel's on-die generator — a circuit whose output depends on thermal noise fluctuations inside the CPU, conditioned and exposed as a single instruction. It's fast and it works. It is also completely unauditable: you cannot inspect it, and it's a single silicon block from a single vendor. After 2013 made state-level RNG sabotage a documented concern rather than a hypothetical, the kernel community settled on mixing RDRAND into the pool rather than using it directly3. That way a backdoored RDRAND degrades your entropy instead of owning it outright.

Jitter entropy exploits something less obvious: on a modern out-of-order CPU with caches, branch prediction, and DRAM refresh cycles, the exact cycle count of a fixed instruction sequence varies unpredictably4. Measure that variance with RDTSC and you have an entropy source requiring no special hardware. The kernel's jitterentropy module does this, and it's the reason headless VMs with no keyboard and no disk activity can still boot with a properly seeded pool.

The urandom question, settled

The old advice — /dev/random for keys, /dev/urandom for everything else — was wrong for years and is now obsolete.5

Since Linux 5.6, /dev/random blocks only before the pool is first initialized. After that, both devices draw from the same CSPRNG and are cryptographically identical. The "entropy depletion" mental model behind the old advice was never correct: a CSPRNG seeded with 256 bits doesn't run out of randomness, any more than AES runs out of encryption.

Use getrandom(2). It blocks until the pool is initialized, then never blocks again, and it can't fail due to file descriptor exhaustion the way opening /dev/urandom can. In userland: getrandom() on Linux, arc4random_buf() on BSD and macOS, BCryptGenRandom on Windows, crypto.randomBytes() in Node, secrets in Python. Never the language's default random module.

The dangerous window is early boot, especially on embedded devices and freshly cloned VM images — no disk history, no user input, no accumulated jitter. Devices have shipped SSH host keys generated seconds after first boot6, and researchers have found thousands of them sharing keys across the internet.


Physical entropy theater

Some setups are famous mostly for being fun to look at.

Cloudflare's San Francisco lobby has a wall of lava lamps filmed by a camera whose frames feed an entropy pool. Their London office uses a chaotic pendulum; Singapore uses a radioactive source measuring uranium decay.

It's real entropy. It's also somewhat theatrical, and Cloudflare is upfront that it's one input among many rather than the main source. Chaotic fluid in wax is hard to predict, but the camera sensor's own thermal noise probably contributes more entropy than the lamps do7.

The ANU Quantum Random Numbers Server is a more rigorous version: it measures vacuum fluctuations of the electromagnetic field — quantum noise, unpredictable as a matter of physics rather than practical difficulty — and streams the output publicly.

Which surfaces the obvious problem with all remote randomness: you're trusting a third party and a network path. Randomness delivered over the wire is randomness someone else generated and could have recorded. For anything that matters, remote entropy is something you mix in, never something you rely on.


Dice: the case for doing it by hand

Which brings us to the most paranoid and most practical technique in the seed-generation toolkit.

When you generate a Bitcoin wallet, the hardware device picks 128 or 256 bits from its internal RNG. You have no way to verify that RNG. If it's backdoored — or merely broken — your seed is predictable and everything derived from it is gone. This isn't hypothetical: multiple wallets have shipped with entropy bugs, and Android's SecureRandom flaw in 2013 drained real Bitcoin wallets through repeated ECDSA nonces.

Dice fix this by moving the entropy source somewhere you can see it.

The arithmetic

A fair six-sided die produces log₂(6) ≈ 2.585 bits per roll.8

  • 128 bits (12-word seed): 128 / 2.585 ≈ 50 rolls
  • 256 bits (24-word seed): 256 / 2.585 ≈ 99.04 → 100 rolls

Rolling beyond the target adds nothing. Once you've supplied 256 bits into a 256-bit seed, additional rolls are discarded.

Converting rolls to bits without introducing bias

This is where people go wrong, and the failure is invisible.

The trap. Mapping a die to binary by taking the value mod 2, or mapping 1–6 onto 0–5 and then reducing mod 4, introduces modulo bias9 — some outputs become more likely than others. It still looks random. It still passes a casual eyeball test. You've quietly lost entropy in a way that's hard to detect and impossible to fix after the fact.

Three correct approaches:

Rejection sampling. Use only rolls of 1–4, mapping them to 2 bits each; reject 5s and 6s entirely. Perfectly uniform, easy to do by hand, wasteful — you'll discard a third of your rolls, needing about 192 throws to collect 128 usable ones for 256 bits.

Binary reduction. 1–3 → 0, 4–6 → 1. Exactly one unbiased bit per roll. Dead simple, verifiable in your head, and needs 256 rolls for a 24-word seed. That's a long evening.

Hash the transcript. Roll 100 times, write down the digits as a string, and SHA-256 the result. The hash acts as a randomness extractor: feed in ≥256 bits of entropy in any format and get a uniform 256-bit output. This is what Coldcard and most offline seed tools do, and it's the right default — the arithmetic is done by a function you can audit rather than by you at 1am.

Details that actually matter

Your dice are probably biased. Cheap dice have rounded corners, hollowed-out pips, and uneven density. Casino dice are precision-machined with flush pips and sharp edges10 precisely because ordinary dice measurably favor certain faces. The bias is small — but you're at 2.585 bits per roll with no margin to spare.

Fortunately, the hash-the-transcript method is fairly forgiving here. A slightly biased die reduces entropy per roll rather than destroying it, so rolling 120 times with mediocre dice comfortably clears 256 bits. Roll extra. It's cheap.

Verify the tool offline. Generating the seed on an airgapped machine and then typing it into a website defeats the entire exercise. The whole point was avoiding a black box.

Consider mixing rather than replacing. Some wallets let you XOR dice entropy with the device's internal RNG. That's strictly better than either alone: you're protected if the hardware is backdoored and if your dice are loaded, because compromising the result requires compromising both.


Randomness other people have to trust

Everything so far assumed you generate randomness for yourself. The problem changes completely when the output has to convince a skeptic.

Commit-reveal is the foundational trick. Publish hash(secret) first. Reveal secret later. Anyone can check the hash matches, so you're bound to a value you chose before seeing anything else. This is what "provably fair" online gambling runs on: a hashed server seed published up front, a player-supplied client seed, and a nonce incrementing per round, run through HMAC-SHA256. The player contributes entropy the operator can't control, and after the server seed is revealed every past round can be recomputed and checked.

It's an elegant construction with a narrow guarantee: the operator did not change the outcome after your bet. It says nothing about whether the game's odds are what the operator claims, and it doesn't satisfy any major gaming regulator. Licensed markets require certification from labs like GLI or eCOGRA, which test the generator's statistical behavior over time rather than the integrity of individual rounds. The two mechanisms answer different questions, and crypto casinos advertising "provably fair" as equivalent to a license are eliding that.

Blockchain randomness has the same shape with adversarial validators added.

Ethereum's PREVRANDAO exposes the beacon chain's RANDAO accumulator — each proposer mixes in a BLS signature they can't forge. This works fine for consensus-internal purposes like committee assignment. It is not safe for high-value application randomness, because the last proposer in an epoch can see the result and choose to skip their slot11, discarding an unfavorable outcome. One bit of bias, available for the price of one missed block reward. If your lottery pays more than that, someone will buy it.

VRFs solve this differently. A verifiable random function produces a value plus a proof that the value was derived correctly from a specific input and a specific private key. Chainlink VRF publishes both on-chain, so the contract verifies the proof before accepting the number. Nobody — not the oracle, not the developer, not a validator — can substitute a different value. You are still trusting that the oracle answers at all.

drand takes the threshold approach: a league of independent operators produces a jointly-generated random beacon on a fixed schedule, where no minority can predict or bias the output and no single operator can withhold it. No other option here distributes control as widely. Its values are public the instant they're produced, so you can't use them for anything requiring privacy.


When it goes wrong

The failure cases are worth knowing because they're all the same failure.

Debian OpenSSL (2006–2008). A developer removed two lines that fed uninitialized memory into the entropy pool — they made Valgrind complain. The seed collapsed to the process ID: 15 bits of entropy, about 32,768 possibilities12. Every SSH and SSL key generated on affected Debian systems for nearly two years was brute-forceable in minutes. The code was correct in every other respect.

Sony PlayStation 3 (2010). ECDSA requires a fresh random nonce for every signature. Sony used a constant.13 With two signatures under the same nonce, the private key falls out by simple algebra. fail0verflow demonstrated it on stage. Sony's console signing key was recovered not through a cryptographic weakness but through a missing call to a random number generator.

Android SecureRandom (2013). An initialization flaw caused repeated nonces in ECDSA signatures, and Bitcoin wallets on Android leaked their private keys on-chain to anyone watching for the pattern.

The common thread: in every case the cryptography was sound, correctly implemented, and standards-compliant. The randomness underneath it was not, and nothing in the layer above noticed. There is no test that distinguishes a good key from a predictable one by looking at it. The entropy is either there at generation time or it never was.


Practical summary

For keys and nonces. Use the OS. getrandom(), arc4random_buf(), BCryptGenRandom, crypto.randomBytes(). Never a language's default random. Never roll your own generator. Watch out for early boot on embedded hardware and cloned VM images.

For simulations and games. A fast PRNG is correct and a CSPRNG is a waste. Seed it explicitly and log the seed so your runs reproduce.

For a wallet seed you're serious about. Dice, hashed with SHA-256, on an airgapped device, ideally XORed with the hardware RNG rather than replacing it. Roll 100 times for 256 bits, or 120 if your dice came from a board game. Pair it with multisig if the amount justifies the operational overhead.

For anything a stranger must verify. Commit-reveal at minimum. A VRF if a contract consumes it. drand if you need a continuous public beacon. Never PREVRANDAO or a block hash for anything valuable.

The recurring lesson across all of it: randomness is the one primitive whose failure is undetectable by inspection14. A weak key looks exactly like a strong one. You cannot test your way to confidence after the fact — you can only be careful about where the entropy came from in the first place.


Further reading

Notes

  1. 1Context

    A fourth property matters in some designs and is easy to forget: liveness. A beacon that can't be biased but can be withheld is still exploitable — if the party producing the number can decline to publish an unfavorable one, refusing to answer becomes the attack. This is why drand's threshold construction cares as much about no minority being able to stop the beacon as about no minority being able to steer it.

  2. 2Note

    This architecture is why "but is it truly random?" is usually the wrong question about a running system. Your TLS session keys are pure arithmetic — deterministic output from a ChaCha20 stream. The physical randomness happened once, at seeding. Provided that seed had 256 real bits, the derived stream is unpredictable for reasons that have nothing to do with physics and everything to do with the cost of breaking a cipher.

  3. 3Context

    This was a genuine, public fight. In 2013 a petition asked Linus Torvalds to remove RDRAND support entirely over sabotage concerns; he refused in characteristically direct terms, pointing out that RDRAND was already only one input being mixed into the pool rather than the pool itself. The resolution is a nice piece of defensive design — you get the throughput benefit of hardware entropy while a compromised source degrades the result instead of determining it.

  4. 4Source

    Jitter entropy is a strange and somewhat uncomfortable source: it works because modern CPUs are too complex to predict cycle-accurately, which is an argument from engineering difficulty rather than physics. Researchers periodically probe how much genuine entropy it yields, and NIST SP 800-90B assessments generally support the conservative estimates the kernel uses. It's the reason a headless cloud VM with no keyboard, no mouse, and no disk history can still seed itself at boot.

    source →
  5. 5Counter

    This advice persists in a remarkable number of textbooks, StackOverflow answers, and internal security wikis, and following it actively causes harm — blocking reads on /dev/random have hung production services and stalled boots waiting for "entropy" that was never actually needed. Since Linux 5.6 the two devices differ only in behavior before the pool is first initialized. If you learned the old rule, unlearn it.

    source →
  6. 6Source

    The canonical study is "Mining Your Ps and Qs" (Heninger et al., USENIX Security 2012), which scanned the public internet and found that roughly 0.75% of TLS certificates shared keys due to insufficient boot-time entropy, and recovered private keys for around 0.5% of TLS hosts and 1% of SSH hosts outright. Headless embedded devices generating keys on first boot were the dominant cause. The paper is still the best empirical demonstration that this failure mode is real at scale.

    source →
  7. 7Note

    Cloudflare has been fairly candid that the lava lamp wall is partly a public engagement exercise — it's in the lobby, it's on the tours, and it makes an abstract concept legible to visitors. The entropy is genuine and it is one input among several, deliberately. The useful takeaway is the design pattern rather than the lamps: mix many independent sources so that no single one being weak or compromised determines the output.

  8. 8Note

    Worth sanity-checking the direction of this number. A d6 gives less than 3 bits because 6 < 8 — three bits would require 8 equally likely outcomes. A coin gives exactly 1 bit, a d20 about 4.32, and a well-shuffled 52-card deck about 225.6 bits total, which is why a single genuinely random shuffle is close to a 24-word seed's worth of entropy and has almost certainly never occurred twice in history.

  9. 9Context

    The same bug appears far outside dice. Any random() % n where n doesn't divide the generator's range evenly produces a skew toward the low values — the classic case being shuffles and random sampling in application code. It's the reason arc4random_uniform() exists as a separate function from arc4random(), and why Python's secrets.randbelow() uses rejection sampling internally rather than a modulo.

  10. 10Context

    Casino-grade dice are held to roughly 0.0005 inch tolerances, with pips drilled and then backfilled with material of identical density so no face is lighter than another. Ordinary retail dice have hollow pips — the 6 face has six cavities of missing material and the 1 face has one, making the die very slightly favor landing 6-up. The effect is small enough to be irrelevant for a board game and large enough to be worth compensating for with extra rolls when generating a key.

  11. 11Counter

    The precise bound is worth knowing before dismissing RANDAO entirely: a validator controlling the final k slots of an epoch gets roughly k bits of choice, at the cost of forfeiting those block rewards. For consensus-internal use — committee shuffling, proposer selection — this is a well-understood and acceptable margin. It becomes exploitable exactly when an application's payout exceeds the forfeited rewards, which is a threshold any sizable on-chain lottery clears trivially.

    source →
  12. 12Source

    The changed lines were removed to silence a Valgrind warning about reading uninitialized memory — which was, in isolation, a completely reasonable thing for a maintainer to want to fix. The patch was even discussed on an OpenSSL mailing list beforehand, where it did not receive the scrutiny it needed. It ran undetected from September 2006 to May 2008, and the cleanup afterwards involved regenerating essentially every key produced on Debian and Ubuntu in that window.

    source →
  13. 13Quote

    ECDSA's nonce requirement is unforgiving in a way few primitives are: two signatures sharing a nonce let anyone solve for the private key with schoolbook algebra. fail0verflow demonstrated the recovery at 27C3 in December 2010. The lasting fix is RFC 6979 deterministic ECDSA, which derives the nonce from the message and the private key via HMAC — removing the RNG from the signing path entirely, so there is no random number left to get wrong.

    — fail0verflow, 27C3 (2010)

    source →
  14. 14Note

    This is what makes RNG bugs uniquely dangerous compared to other crypto failures. A broken cipher usually fails loudly — output that doesn't decrypt, tests that don't pass, interop that breaks. A broken RNG produces keys of the correct length, in the correct format, that work perfectly for every legitimate operation. The Debian keys functioned normally for two years. Nothing in the system was capable of noticing.