Cryptographic vs Pseudo-Random Numbers: When Security Matters
Two Kinds of Random
Computers cannot produce true randomness — they are deterministic machines. Instead, they use algorithms to generate sequences that look random. These are called pseudo-random number generators (PRNGs).
For most applications, pseudo-randomness is fine. Shuffling a playlist, picking a background color, or generating test data — the output just needs to be unpredictable, not unbreakable.
But when security depends on the randomness — passwords, encryption keys, session tokens — pseudo-randomness is dangerous. An attacker who understands the PRNG state can predict future outputs.
That is where cryptographically secure pseudo-random number generators (CSPRNGs) come in.
How PRNGs Work
A PRNG starts from a seed value and applies a deterministic algorithm to produce a sequence. The same seed always produces the same sequence.
// Simple PRNG: Linear Congruential Generator (illustrative only)
let seed = 12345;
function lcg() {
seed = (seed * 1103515245 + 12345) & 0x7fffffff;
return seed / 0x7fffffff;
}
lcg(); // 0.5823...
lcg(); // 0.1267...
If you know the seed and the algorithm, you can reproduce every number — past and future. This predictability is the fundamental weakness.
JavaScript's Math.random() is a PRNG. The exact algorithm varies by browser (V8 uses xorshift128+), but all share the same property: not cryptographically secure.
How CSPRNGs Differ
A CSPRNG adds three properties on top of statistical randomness:
- Next-bit unpredictability: Given all previous outputs, the next bit cannot be predicted with probability better than 50%
- Backtracking resistance: Even if the internal state is compromised, previous outputs cannot be reconstructed
- Entropy harvesting: The generator collects randomness from hardware or OS events (timing, thermal noise, mouse movements) to seed itself
In JavaScript, the CSPRNG is crypto.getRandomValues():
// Cryptographically secure random bytes
const array = new Uint32Array(1);
crypto.getRandomValues(array);
console.log(array[0]); // Unpredictable, even to an attacker
In Node.js:
const crypto = require('crypto');
// Secure random bytes
const bytes = crypto.randomBytes(32);
console.log(bytes.toString('hex'));
// Secure random integer in range
const n = crypto.randomInt(1, 101); // 1 to 100 inclusive
When to Use Each
| Use Case | Generator | Why |
|---|---|---|
| UI animations, delays | PRNG | No security impact |
| Shuffling playlist | PRNG | No attacker motivation |
| Dice rolls in casual games | PRNG | Low stakes |
| Test data generation | PRNG | Speed matters more than security |
| Password generation | CSPRNG | Predictable passwords get cracked |
| Session tokens | CSPRNG | Predictable tokens enable hijacking |
| Encryption keys | CSPRNG | Keys must be unguessable |
| Nonces in protocols | CSPRNG | Replay attacks if predictable |
| Lottery/prize draws | CSPRNG | Financial stakes |
| Online gambling | CSPRNG | Regulatory requirement |
Rule of thumb: If knowing the random value would let an attacker cause harm, use a CSPRNG.
Practical Vulnerability Examples
Predictable Session Tokens
// ❌ Vulnerable: predictable session token
const token = Math.random().toString(36).slice(2);
// ✅ Secure: unpredictable session token
const token = Array.from(crypto.getRandomValues(new Uint8Array(32)))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
If an attacker observes a few Math.random() outputs, they can reconstruct the internal state and predict all future tokens — including tokens assigned to other users.
Password Reset Links
// ❌ Vulnerable: guessable reset code
const code = Math.floor(Math.random() * 1000000);
// ✅ Secure: unguessable reset code
const code = crypto.randomInt(1000000);
Race Condition in Async Random
// ❌ Problematic: two calls may get related values
const a = await crypto.getRandomValues(new Uint8Array(16));
const b = await crypto.getRandomValues(new Uint8Array(16));
// ✅ Better: get all randomness in one call
const both = crypto.getRandomValues(new Uint8Array(32));
const a = both.slice(0, 16);
const b = both.slice(16);
Performance Comparison
CSPRNGs are slower because they must read from the operating system's entropy pool:
// Benchmark: 1 million random numbers
// Math.random(): ~50ms
// crypto.getRandomValues(): ~200ms
The 4–5x slowdown is the cost of security. For most applications, this is negligible — you are not generating a million tokens per second.
Key Takeaways
- PRNGs are deterministic algorithms — same seed produces same sequence
Math.random()is a PRNG and is never safe for security-critical randomness- CSPRNGs harvest hardware entropy and resist prediction even if state is compromised
- Use CSPRNGs for passwords, tokens, keys, nonces, and anything with financial stakes
- Use PRNGs for animations, shuffling, test data, and non-security contexts
- The performance cost of CSPRNGs is negligible for typical web application loads
Try It Yourself
Generate secure random numbers with customizable ranges and precision using our Random Number Generator. Choose between integer, decimal, and unique-value modes for any use case.
Related Guides
- Random Number Guide — how random number generators work in computing
- Randomness in Programming — statistical properties and common distributions