Cryptographically Secure Random Password Generation
Understand how cryptographically secure random number generators (CSPRNGs) work and why they are essential for password generation. Covers Web Crypto API, entropy sources, and common pitfalls.
Detailed Explanation
Why Cryptographic Randomness Matters
A password is only as strong as its randomness. Using Math.random() in JavaScript — or similar non-cryptographic PRNGs — produces passwords that may appear random but are actually predictable. An attacker who knows the algorithm and can estimate the seed can reproduce the entire output sequence.
Math.random() vs crypto.getRandomValues()
Math.random() (INSECURE for passwords)
// DO NOT use for password generation
Math.random().toString(36).slice(2);
Problems:
- Uses a deterministic PRNG (typically xorshift128+ in V8)
- Seeded from a low-entropy source (system time)
- State can be recovered from a few outputs
- Not designed for security applications
crypto.getRandomValues() (SECURE)
// Correct approach
const array = new Uint8Array(16);
crypto.getRandomValues(array);
Properties:
- Uses the operating system's CSPRNG (CryptGenRandom on Windows, /dev/urandom on Linux/macOS)
- Seeded from hardware entropy (CPU timing, interrupt jitter, hardware RNG)
- Output is computationally indistinguishable from true randomness
- Standardized in the Web Crypto API (available in all modern browsers)
How CSPRNGs Work
A cryptographically secure pseudorandom number generator combines:
- Entropy pool — collects randomness from hardware sources
- Seed extraction — derives a high-quality seed from the entropy pool
- Output generation — produces random bytes using a cryptographic algorithm (e.g., ChaCha20, AES-CTR)
- Reseeding — periodically refreshes the seed from new entropy
Common Pitfalls
1. Modulo Bias
// BIASED: some characters are slightly more likely
const index = randomByte % 62;
// UNBIASED: rejection sampling
function unbiasedRandom(max) {
const limit = 256 - (256 % max);
let value;
do {
const array = new Uint8Array(1);
crypto.getRandomValues(array);
value = array[0];
} while (value >= limit);
return value % max;
}
When the random range (256 for a byte) is not evenly divisible by the character set size, some outputs are slightly more probable. Rejection sampling eliminates this bias.
2. Insufficient Entropy Collection
Some systems start with low entropy (embedded devices, VMs at boot time). Password generation should verify entropy availability or block until sufficient entropy is collected.
3. Predictable Seeds
Using timestamps, process IDs, or other guessable values as seeds undermines the entire generator.
Browser Support
The Web Crypto API (crypto.getRandomValues()) is supported in:
- All modern browsers (Chrome, Firefox, Safari, Edge)
- Node.js (
crypto.randomBytes()) - Deno (
crypto.getRandomValues()) - Web Workers and Service Workers
Verifying Randomness
While true randomness cannot be proven, statistical tests can detect obvious patterns:
- NIST SP 800-22 — Statistical Test Suite for Random Number Generators
- Diehard tests — battery of statistical tests
- TestU01 — comprehensive PRNG testing framework
Use Case
Understanding cryptographic randomness is essential for any developer building password generators, token systems, or cryptographic applications. It explains why the Web Crypto API must be used instead of Math.random() and how to avoid subtle biases that weaken generated passwords even when the character set and length appear adequate.