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.

Advanced

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:

  1. Entropy pool — collects randomness from hardware sources
  2. Seed extraction — derives a high-quality seed from the entropy pool
  3. Output generation — produces random bytes using a cryptographic algorithm (e.g., ChaCha20, AES-CTR)
  4. 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.

Try It — Password Generator

Open full tool