Cryptography

CSPRNGs: Why Secure Randomness Is Harder Than It Looks

June 5, 2026 9 min read Haven Team

A cryptographic key is just a number an attacker shouldn't be able to guess. So is a session token, a nonce, a salt, and the private half of every public-key pair. All of them come from a random number generator — and if that generator is predictable, the strongest cipher in the world is wrapping a secret an attacker can simply recompute. Randomness is the foundation everything else stands on, and it has failed in production more times than it should have.


"Random" is a slippery word. The dice-roll randomness most people picture — unpredictable to a human, roughly evenly distributed — is nowhere near strong enough for cryptography. A function can pass every statistical test for uniformity and still be completely predictable to someone who understands how it works. For security, randomness has a much harder requirement: even an adversary who has seen a long stream of your generator's output, and knows its exact algorithm, must not be able to predict the next bit or reconstruct the previous ones.

That requirement is what separates a cryptographically secure pseudorandom number generator (CSPRNG) from an ordinary PRNG. The distinction is not academic. The wrong generator has handed attackers private keys, forged sessions, and decryptable traffic — not by breaking the math, but by undermining the unpredictability the math assumed.

Three Kinds of "Random"

It helps to separate the categories cleanly, because mixing them up is where most mistakes start.

Type Example Safe for keys?
Statistical PRNG Mersenne Twister, rand(), Math.random() No — predictable from output
CSPRNG OS getrandom(), /dev/urandom, HMAC/CTR-DRBG Yes — when properly seeded
True (hardware) RNG RDSEED, thermal/jitter noise sources As a seed — usually feeds a CSPRNG

A statistical PRNG like the Mersenne Twister is built for speed and even distribution — perfect for simulations and game logic, fatal for keys. Observe enough of its output and its entire internal state can be reconstructed, after which every future "random" value is known. Yet Math.random() still shows up in security-sensitive code with depressing regularity.

One rule that prevents most disasters

If a number protects something, it must come from a CSPRNG. In practice that means your operating system's randomness interface or a vetted crypto library's secure generator — never the default language RNG, never a hash of the current time.

What Makes a Generator Cryptographically Secure

A CSPRNG has two properties an ordinary PRNG lacks. The first is next-bit unpredictability: given all previous output, no efficient algorithm can predict the next bit better than a coin flip. The second is state-compromise resilience: even if an attacker somehow learns the generator's internal state at one moment, they shouldn't be able to recover the random values it produced earlier (backward secrecy), and well-designed generators continually mix in fresh entropy so the state heals forward over time.

Internally, a modern CSPRNG is usually a deterministic construction — a "DRBG," deterministic random bit generator — built on a strong primitive like a hash function (HMAC-DRBG), a block cipher in counter mode (CTR-DRBG), or a stream cipher like ChaCha20. It takes a relatively small high-entropy seed and stretches it into an effectively unlimited stream of output that's computationally indistinguishable from true randomness. The security of the whole thing reduces to two things: the strength of that primitive, and the quality of the seed.

Entropy: The Part You Can't Fake

A deterministic generator is only as unpredictable as its seed. Feed a CSPRNG a guessable seed and its output, however well-stretched, is guessable too. So where does genuine unpredictability — entropy — come from? Computers are deterministic by design, so the OS harvests it from physical events that are hard to predict: precise timing of interrupts, disk and network activity, sensor jitter, and dedicated hardware instructions like Intel's RDSEED that sample thermal noise.

The operating system pools this entropy, conditions it through cryptographic mixing, and uses it to seed a CSPRNG that then serves random bytes to applications. This is why the right thing to do is almost always to ask the OS rather than build your own. On Linux that's the getrandom() syscall (or /dev/urandom); on most other systems there's an equivalent secure call your crypto library wraps for you.

The old folklore that /dev/urandom is "less secure" than /dev/random is, on modern kernels, a myth. Once the system's CSPRNG has been seeded with enough entropy at boot, urandom produces output that is cryptographically as strong — and it won't block forever waiting on an entropy estimate that was always conservative.

When Randomness Failed in the Real World

The history of broken randomness is a better teacher than any specification. In 2008, a well-intentioned change to Debian's OpenSSL package removed a line of code that fed entropy into the seed, on the theory that it triggered a memory-analysis warning. The result was catastrophic: for nearly two years, keys generated on affected systems were drawn from a tiny set of possibilities — small enough to enumerate. Every SSH and TLS key produced in that window had to be considered compromised and regenerated.

A different failure mode is reusing randomness that should be unique. Many signature schemes require a fresh, secret random value — a nonce — for every signature. Reuse that nonce across two signatures, or make it predictable, and the math leaks the private key directly. This class of bug has bitten real products, including a famous case where a games console's signing system reused the same value for every signature, letting researchers extract the master private key. The lesson generalizes: a repeated or predictable nonce can be as damaging as a leaked key.

Then there is deliberate sabotage. The Dual_EC_DRBG generator, once a NIST-standardized option, contained a mathematical structure widely believed to allow whoever chose its constants to predict its output. It was a reminder that a generator's design — not just its implementation — is part of the trust you're extending.

Getting It Right in Practice

The encouraging news is that secure randomness is, for most developers, a solved problem you simply have to not opt out of. A short checklist covers the overwhelming majority of cases:

This is the kind of invisible foundation that good security depends on and bad security skips. The ciphers and protocols described elsewhere on this blog — from elliptic-curve cryptography to forward secrecy — all assume the keys feeding them are genuinely unguessable. When we build the systems behind Haven, secure key material is generated client-side from properly seeded randomness, because a private key that an attacker can recompute isn't private at all, no matter how strong the cipher around it.

Randomness is the rare part of cryptography where the failures are almost always boring: a default function used out of place, a seed that wasn't a secret, a nonce reused once too often. None of them break the math. They just make it irrelevant.

Try Haven free for 15 days

Encrypted email and chat in one app. No credit card required.

Get Started →