Random Numbers: The Most Underestimated Component in Cryptography
1. Why Should You Care?
In 2010, the PlayStation 3โs security system was completely broken. Not because the encryption algorithm was flawed, but because Sony used a fixed โrandomโ number when creating digital signatures.
# Sony's fatal mistake (simplified)
def sign_game(game_data):
k = 4 # This number should be different every time!
signature = ecdsa_sign(game_data, private_key, k)
return signatureThe result? Anyone could calculate Sonyโs private key, sign arbitrary code, and run pirated games and homebrew software on PS3.
One โrandomโ number that wasnโt random, and the entire security system collapsed.
This isnโt an isolated case. From the Debian OpenSSL bug (2008) to Android Bitcoin wallets being drained (2013), countless security disasters in history stem from random number problems.
2. Definition
Random numbers in cryptography refer to unpredictable values used to generate keys, initialization vectors (IVs), nonces, salts, and more.
Cryptographically secure random numbers must have:
- Unpredictability: Even knowing all previously generated numbers, you canโt predict the next one
- Non-reproducibility: Same conditions wonโt produce the same sequence
- Uniform distribution: All possible values have equal probability
Key distinctions:
- True Random Number Generator (TRNG): From physical phenomena, truly unpredictable
- Pseudo-Random Number Generator (PRNG): Algorithm-generated, appears random but is actually deterministic
- Cryptographically Secure PRNG (CSPRNG): Specially designed PRNG where knowing partial output doesnโt help predict other output
3. Why Random Numbers Matter So Much
Key Generation
key = random(256 bits)
If random() is predictable:
- Attackers can guess the key
- Encryption becomes uselessA 256-bit AES key has 2^256 possibilities. But if your random number generator can only produce 2^32 different keys (because the seed is only 32 bits), attackers only need to try 4.2 billion timesโa few hours of work.
Initialization Vectors (IV)
AES-CBC encryption:
ciphertext = AES_CBC(plaintext, key, IV)
If IV is predictable:
- Attackers can perform chosen-plaintext attacks
- Even with a secure key, encryption can still be brokenNonces in Digital Signatures
This is why Sonyโs PS3 was broken:
ECDSA signature:
signature = sign(message, private_key, k)
If k is reused:
private_key = (message1 - message2) / (signature1 - signature2)
โ Directly calculate the private key!4. Why rand() Can Kill Your Security
Standard Library rand() Isnโt Designed for Security
// C's rand() - NEVER use for cryptography!
int seed = time(NULL); // Seed is current time
srand(seed);
int key = rand(); // "Random" keyProblems:
- Seed too small: Usually only 32 bits, at most 4.2 billion possibilities
- Seed predictable:
time(NULL)is current seconds, attackers roughly know when you generated the key - Simple algorithm: Linear Congruential Generator (LCG), mathematically easy to reverse
# Python's random module - equally insecure!
import random
random.seed() # Uses system time
key = random.getrandbits(256) # Don't do this!Real Case: Debian OpenSSL Disaster (2008)
// Someone "fixed" a warning in this code
MD_Update(&m, buf, j); // Removed
^
|
Valgrind said this variable was uninitialized
MD_Update(&m, &(md_c[0]), sizeof(md_c)); // KeptThis โfixโ removed the main source of entropy. Result:
- OpenSSL could only generate 32,768 different keys
- All SSH and SSL certificates generated on affected systems could be brute-forced
- Affected millions of Debian/Ubuntu servers
5. True Random vs Pseudo-Random
True Random Numbers (TRNG)
Sources: Physical phenomena
- Radioactive decay
- Thermal noise
- Quantum effects
- Mouse movements, keyboard timing
Physical phenomenon โ Measurement โ Digitization โ True random bitsPros: Truly unpredictable Cons: Slow, requires special hardware, limited bit rate
Pseudo-Random Numbers (PRNG)
Seed โ Algorithm โ Seemingly random sequence# Simplified PRNG algorithm
def prng(seed):
state = seed
while True:
state = (state * 1103515245 + 12345) % 2**31
yield statePros: Fast, reproducible (useful for testing) Cons: Completely deterministicโknowing the algorithm and seed means knowing all output
Cryptographically Secure Pseudo-Random Numbers (CSPRNG)
PRNG designed specifically for security:
Entropy Pool โโโบ CSPRNG โโโบ Secure random output
โ
Continuously collecting entropyProperties:
- Even seeing partial output, you canโt predict other output
- Even if internal state is leaked, you canโt recover previous output
- Continuously collects entropy from environment to update internal state
6. Operating System Sources of Randomness
Linux: /dev/random vs /dev/urandom
# Blocking - waits when entropy is insufficient
head -c 32 /dev/random
# Non-blocking - always returns immediately
head -c 32 /dev/urandomModern recommendation: Almost always use /dev/urandom
Historical concerns (outdated):
/dev/randomis โmore secureโ because it waits for true entropy/dev/urandommight be โentropy-starvedโ
Reality:
- Modern Linuxโs urandom uses the same entropy pool
- After system boot, urandom is cryptographically as secure as random
/dev/randomโs blocking only causes problems (DoS, performance)
Sources of Entropy
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Hardware Entropy Sources โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ - CPU timing jitter โ
โ - Hardware RNG (RDRAND/RDSEED instructions) โ
โ - TPM (Trusted Platform Module) โ
โ - Dedicated entropy generation hardware โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Software Entropy Sources โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ - Interrupt timing โ
โ - Disk I/O timing โ
โ - Network packet timing โ
โ - Keyboard/mouse events โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโWindows: CryptGenRandom / BCryptGenRandom
// Windows API
BYTE buffer[32];
BCryptGenRandom(NULL, buffer, 32, BCRYPT_USE_SYSTEM_PREFERRED_RNG);macOS/iOS: SecRandomCopyBytes
var bytes = [UInt8](repeating: 0, count: 32)
let status = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)7. Correct Usage in Various Languages
Python
import secrets # Python 3.6+, designed for cryptography
# Generate secure random bytes
key = secrets.token_bytes(32) # 256-bit key
# Generate secure hex string
token = secrets.token_hex(32) # 64-character hex string
# Generate URL-safe token
url_token = secrets.token_urlsafe(32)
# Secure random number within range
dice = secrets.randbelow(6) + 1 # 1-6
# Secure random choice
password_char = secrets.choice('abcdefghijklmnopqrstuvwxyz0123456789')# NEVER do this!
import random
key = random.getrandbits(256) # Insecure!Node.js
const crypto = require('crypto');
// Generate secure random bytes
const key = crypto.randomBytes(32);
// Generate secure UUID
const { randomUUID } = require('crypto');
const uuid = randomUUID();
// Generate secure random integer in range
function secureRandomInt(min, max) {
const range = max - min;
const bytesNeeded = Math.ceil(Math.log2(range) / 8);
const randomBytes = crypto.randomBytes(bytesNeeded);
const randomValue = parseInt(randomBytes.toString('hex'), 16);
return min + (randomValue % range);
}// NEVER do this!
const key = Math.random() * 2**256; // Insecure!Go
import (
"crypto/rand"
"encoding/hex"
)
// Generate secure random bytes
func generateKey() ([]byte, error) {
key := make([]byte, 32)
_, err := rand.Read(key)
if err != nil {
return nil, err
}
return key, nil
}
// Generate secure hex string
func generateToken() (string, error) {
bytes := make([]byte, 32)
_, err := rand.Read(bytes)
if err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}// NEVER do this!
import "math/rand"
key := rand.Int63() // Insecure!Rust
use rand::Rng;
use rand::rngs::OsRng;
fn main() {
// Use OS CSPRNG
let mut key = [0u8; 32];
OsRng.fill(&mut key);
// Generate secure random number in range
let n: u32 = OsRng.gen_range(1..=100);
}8. Common Mistakes and How to Avoid Them
Mistake 1: Using Time as Seed
# Wrong
import random
random.seed(int(time.time()))
key = random.getrandbits(256)
# Attackers know roughly when you generated the key
# They only need to try all seconds within that time windowMistake 2: Reusing nonce/IV
# Wrong
iv = b'0' * 16 # Fixed IV
for message in messages:
ciphertext = aes_cbc_encrypt(message, key, iv) # Same IV!Mistake 3: Reducing Random Number Range
# Wrong
import secrets
key = secrets.randbelow(1000000) # Only 1 million possibilities!
# Correct
key = secrets.token_bytes(32) # 2^256 possibilitiesMistake 4: Not Reseeding After VM Snapshot
VM snapshot โ Restore โ Generate "random" numbers
โ
Same as last time after the snapshot!Some CSPRNGs need to recollect entropy after VM restore.
Mistake 5: Implementing Your Own RNG
# Wrong: "I designed a better algorithm"
def my_random():
global state
state = (state * 31337 + 12345) ^ (time.time_ns() % 256)
return state
# This will almost certainly have security issues9. Testing Random Number Quality
Basic Statistical Tests
import secrets
from collections import Counter
# Generate many random numbers
samples = [secrets.randbelow(256) for _ in range(100000)]
# Check distribution
counter = Counter(samples)
for i in range(256):
expected = 100000 / 256
actual = counter[i]
if abs(actual - expected) / expected > 0.1: # More than 10% deviation
print(f"Warning: Abnormal distribution for value {i}")NIST Random Number Test Suite
# Using NIST SP 800-22 test suite
# https://csrc.nist.gov/projects/random-bit-generation/documentation-and-software
./assess 1000000 # Test 1 million bitsTests include:
- Frequency test (ratio of 0s and 1s)
- Block frequency test
- Runs test
- Longest run test
- Matrix rank test
- Discrete Fourier transform test
- And moreโฆ
10. Summary
Three things to remember:
Always use CSPRNG. In any security-sensitive scenarioโkeys, IVs, nonces, salts, tokensโyou must use a cryptographically secure random number generator. Python uses
secrets, Node.js usescrypto.randomBytes. Never userandomorMath.random().Random number quality determines encryption strength. A 256-bit key only has 2^256 security if every bit is truly random. If the generator is flawed, actual security might be 2^32 or lower.
Reuse is fatal. Reusing a nonce in ECDSA directly leaks the private key. Reusing a nonce in AES-CTR lets attackers recover plaintext with XOR. Every random number must be one-time use.
11. Whatโs Next
Weโve completed the five core concepts of cryptography basics: encryption isnโt security, what cryptography solves, symmetric vs asymmetric encryption, hash functions, and random numbers.
In the next section, weโll dive into symmetric encryption engineering: Starting with DESโthe basic idea of block ciphers, why we need blocks, and why DES failed.
