Auth's password-reset lesson said to generate a "random, secure, unguessable" token without explaining what makes one random-number generator secure and another one — used constantly in ordinary code — a serious vulnerability the moment it touches anything security-relevant.
3 min read
Lesson 2's IVs, lesson 6's Diffie-Hellman secret values, lesson 8's salts, and Auth's password-reset tokens all share one hidden requirement: they need to be genuinely unpredictable to anyone who didn't generate them. If an attacker can predict or narrow down these values, every cryptographic guarantee built on top of them weakens or collapses entirely — the mathematics of the primitive can be flawless and still fail in practice if the randomness feeding it isn't.
Most programming languages' default random function (Math.random() in
JavaScript, random.random() in Python) is a pseudorandom number
generator (PRNG) — deterministic, seeded from some starting state, and
optimized for speed and statistical distribution, not unpredictability
against a determined attacker. Critically, many of these generators are
seeded in ways that can be guessed or narrowed down (system time,
process ID), and some are mathematically reversible: observing enough
outputs can let an attacker reconstruct the internal state and predict
every future output.
A Cryptographically Secure Pseudorandom Number Generator (CSPRNG) is
built with a different, stronger goal: even if an attacker sees a large
number of its past outputs, they should have no computationally feasible
way to predict any future output or reconstruct the internal state.
Every language provides one — crypto.randomBytes() in Node.js,
secrets module in Python, crypto.getRandomValues() in browser
JavaScript — deliberately separate from the ordinary random module,
specifically so the two aren't accidentally interchanged.
import random
import secrets
# WRONG for a password reset token — predictable in principle
token = str(random.random())
# RIGHT — cryptographically secure
token = secrets.token_urlsafe(32)A CSPRNG isn't a theoretical nicety — predictable randomness has directly caused real security failures: session tokens generated from a weak PRNG seeded with the server's boot time, allowing an attacker to narrow the search space enough to guess valid sessions; cryptographic keys generated with insufficient entropy on embedded devices, resulting in many different devices sharing the same "random" key because their weak PRNGs all produced identical output from similarly limited starting states. In every documented case, the mathematics of the encryption or signing scheme itself was sound — the randomness feeding it was the actual point of failure.
A CSPRNG doesn't invent unpredictability from nothing — it needs a genuine source of entropy (real-world unpredictable events: hardware timing jitter, environmental noise) to seed itself properly, especially right after a system boots, before much unpredictable activity has happened yet. This is why virtual machines and embedded devices — environments with genuinely less hardware-level unpredictability available — have historically been a real source of weak-randomness vulnerabilities: the CSPRNG algorithm itself was fine, but it started from too little genuine entropy.
Never use an ordinary random function for anything security-relevant —
a token, a key, a salt, an IV, a nonce — always reach for the
language's specifically-labeled cryptographic random source instead. This
single habit prevents an entire category of vulnerability that has
nothing to do with any cryptographic primitive being broken, and
everything to do with feeding a sound primitive predictable input.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. Why is an ordinary PRNG like Math.random() dangerous for generating a security token?
2. What does a CSPRNG guarantee that an ordinary PRNG does not?
3. Why have virtual machines and embedded devices historically been a source of weak-randomness vulnerabilities?
Cryptography Fundamentals