Chapter 04 // Primitives

Randomness
and Secrets

Every key, nonce, and IV in cryptography starts with random bytes. If the randomness is weak, nothing built on top of it can be strong. This chapter covers where secrets come from and what can go wrong.

Scroll to explore
01 // The Foundation

Why randomness is not optional

Every cryptographic system we have covered so far, hashes, MACs, and every one we will cover next, depends on secret values that an attacker cannot predict. If an attacker can guess your key, the cipher does not matter.

The invisible dependency

In Chapter 3, we assumed Alice and Bob already shared a secret key. In the next chapter on symmetric encryption, we will assume the same. But where does that key come from? It cannot be a password someone typed. It cannot be a timestamp. It cannot be a counter. It has to be generated in a way that no attacker can predict or reproduce.

Randomness is the answer. But not all randomness is created equal. A coin flip is random. A shuffled deck is random. Math.random() is random enough for a dice game. None of them are random enough for cryptography.

What happens when randomness fails

In 2008, a Debian maintainer accidentally removed two lines of code from OpenSSL. This reduced the entropy pool to roughly 32,000 possible values. For two years, every SSH and TLS key generated on Debian and Ubuntu systems was trivially guessable. An attacker could try all 32,000 keys in seconds.

In 2010, researchers extracted Sony's PlayStation 3 ECDSA master signing key. The cause: Sony used a fixed value where the ECDSA algorithm requires a fresh random nonce for every signature. With two signatures sharing the same nonce, the private key falls out of the math. These are not exotic academic attacks. They are full system compromises caused by bad randomness.

Strong randomness
CSPRNG256 bits of entropy
Key generation
Secure systemkeys are unguessable
Weak randomness
Math.random()predictable state
Key generation
Broken systemkeys can be guessed
02 // Core Concept

Entropy: measuring the unpredictable

Entropy is the amount of uncertainty in a random source. A fair coin flip has 1 bit of entropy. A six-sided die has about 2.6 bits. A 256-bit cryptographic key needs 256 bits of entropy to be unguessable.

🎲

Entropy Sources

Operating systems collect entropy from hardware events: mouse movements, keyboard timings, disk seek times, interrupt timings, thermal noise from hardware sensors. Each event contributes a small, unpredictable signal.

🔄

Entropy Pool

The OS continuously feeds hardware events into an entropy pool. When you request random bytes, the system draws from this pool. On modern systems the pool is continuously reseeded by a CSPRNG, so running out of entropy is effectively a solved problem.

🔑

From Entropy to Keys

Raw entropy is messy and biased. A cryptographically secure PRNG takes that entropy as a seed and stretches it into a uniform, unpredictable stream of bytes. This is what crypto.getRandomValues() gives you in the browser.

On modern systems, you almost never need to worry about running out of entropy. The kernel CSPRNG (ChaCha20 on Linux, Fortuna on macOS) continuously reseeds from hardware sources. The important thing is to use the right API, not to add your own "extra randomness" on top.

03 // Try It Yourself

See randomness in action

Generate random bytes using the browser's crypto.getRandomValues() API. Visualize the distribution and compare it with Math.random(), which is fast but not cryptographically secure.

Entropy Visualizer

Generate random bytes and see their distribution. A good source produces a flat, uniform histogram.

Byte distribution (0x00 to 0xFF)

Generate at least 10,000 bytes to see meaningful distribution differences. A truly uniform source keeps all 256 bars at roughly the same height.

04 // Under the Hood

True random vs. pseudo-random

True Random Number Generators (TRNGs) measure physical phenomena. Cryptographically Secure Pseudo-Random Number Generators (CSPRNGs) use deterministic algorithms seeded with true randomness. In practice, CSPRNGs are what you actually use.

TRNG: hardware-based randomness

TRNGs measure genuinely unpredictable physical events: radioactive decay, thermal noise in resistors, photon arrival times. Intel's RDRAND instruction uses thermal noise from a transistor circuit. These sources are slow and their raw output is biased. They need conditioning (hashing, Von Neumann debiasing) before the bytes are usable.

You rarely interact with TRNGs directly. The operating system collects their output, mixes it into an entropy pool, and feeds it to a CSPRNG that does the heavy lifting.

CSPRNG: the practical workhorse

A CSPRNG starts from a truly random seed and uses a deterministic algorithm (ChaCha20, AES-CTR, or HMAC-DRBG) to produce an arbitrarily long stream of bytes. The output is computationally indistinguishable from true randomness. Even if you observe gigabytes of output, you cannot predict the next byte without knowing the seed.

This is the engine behind every crypto.getRandomValues() call, every os.urandom(), and every /dev/urandom read. Fast (gigabytes per second), uniform, and secure.

True Random Number Generator
Physical sourcethermal noise
Conditioning
Random bytes~100 KB/s
seeds
Cryptographically Secure PRNG
Seedfrom TRNG
ChaCha20 / AES-CTR
Random bytes~1 GB/s

What makes a PRNG "cryptographically secure"?

Three properties separate a CSPRNG from a regular PRNG. Next-bit unpredictability: given the first k bits of output, no algorithm can predict bit k+1 with probability better than 50%. State compromise recovery: even if an attacker learns the internal state at time T, they cannot recover outputs generated before T once the PRNG reseeds. Backward security: past outputs remain unpredictable even after a state leak.

Math.random() in JavaScript (V8 engine) uses xorshift128+. It fails all three properties. Given just a few outputs, the full internal state can be recovered and every future (and past) value predicted. It was never designed for security.

05 // The Danger

Weak PRNGs are predictable

A linear congruential generator (LCG) is one of the simplest PRNGs. Given a few outputs, you can predict everything that comes next. Try it yourself and see why cryptography demands better.

PRNG Predictability

A weak PRNG leaks its future outputs once you know the algorithm. A CSPRNG does not.

06 // The Experiment

Humans are terrible random number generators

Think you can type random digits? Enter a sequence and see how your choices compare to a truly random source. Humans are predictably bad at being unpredictable.

Human Randomness Test

Type at least 40 random digits (0-9) as fast as you can. Try to be as unpredictable as possible.

07 // When It Goes Wrong

The catastrophe of nonce reuse

Keys, nonces, and IVs all come from randomness. A nonce (number used once) must never repeat with the same key. Reuse one, and XOR-based encryption completely falls apart.

The two-time pad

If you XOR two different plaintexts with the same keystream (because the nonce was reused), the keystreams cancel out: C1 XOR C2 = P1 XOR P2. The attacker now has the XOR of two plaintexts, which leaks enormous amounts of information. With frequency analysis and some guessing, both messages can often be recovered completely.

This is not theoretical. WEP (the original Wi-Fi encryption protocol) was broken exactly this way. Its 24-bit IV space was too small, causing inevitable nonce collisions within hours of normal traffic. The result: any WEP network could be cracked in minutes.

Nonce Reuse Attack

Two messages encrypted with the same key. Toggle between unique and reused nonces to see what leaks.

Nonce 11d582e25...Ciphertext 1 
Nonce 2559db42c...Ciphertext 2 
C1 XOR C200000000000000000000000000000000
P1 XOR P2 (plaintext XOR)13110013060a5441004f4403161d4400
C1 XOR C2 reveals nothing about the plaintexts.

With unique nonces, each message is encrypted with a different keystream. XORing the ciphertexts produces random-looking output that leaks no information about either plaintext.

08 // Best Practices

How to generate secrets correctly

The rules are simple. Use the right API. Never roll your own. Never add extra steps that reduce entropy.

🔑

Key Generation

Use crypto.getRandomValues() in browsers, os.urandom() in Python, crypto/rand in Go, SecureRandom in Java. Never use Math.random(), rand(), or any non-cryptographic PRNG for key material.

🔢

Nonces and IVs

For AES-GCM, generate a random 96-bit (12-byte) nonce per encryption. For AES-CBC, generate a random 128-bit (16-byte) IV. Never repeat a (key, nonce) pair.

🎫

Tokens and Session IDs

Web session tokens, CSRF tokens, and password reset tokens must be cryptographically random. At least 128 bits of entropy. If an attacker can predict a token, they can hijack sessions or reset passwords.

🧂

Salts for Password Hashing

Each password gets a unique random salt (16+ bytes). The salt does not need to be secret, but it must be unique per user. This prevents precomputed rainbow tables from working.

The most common mistake is not generating bad randomness. It is using good randomness incorrectly: reusing nonces, truncating random bytes, or XORing together multiple random sources (which can reduce entropy if done wrong). Keep it simple: generate, use, done.

Up Next

Chapter 05 // Symmetric Encryption

Continue Learning