Опубліковано: 2026-06-10
How Random Is Your 'Random'? CSPRNG Explained (2026)
Math.random() is predictable and crackable. Learn how a CSPRNG works, why crypto.getRandomValues() is the only safe source, and how to test entropy.

If you generated a password, token, or session ID with Math.random(), treat it as public. That's the short answer: Math.random() is predictable, and a CSPRNG (Cryptographically Secure Pseudo-Random Number Generator) is the only kind of randomness safe for security. One is a toy. The other is what your operating system uses to protect kernel secrets.
The difference isn't academic. An attacker who sees a few Math.random() outputs can reconstruct the generator's internal state and predict every value it will ever produce — past and future. A CSPRNG closes that door by design. Below is how each one actually works, the math that separates them, and how to make sure your "random" is the real thing.
PRNG vs CSPRNG: the one distinction that matters
Every software random generator is pseudo-random — it's a deterministic algorithm producing a stream that looks random. The question is whether someone can predict the next output. That single property splits the world in two.
| Property | PRNG (Math.random()) | CSPRNG (crypto.getRandomValues()) |
|---|---|---|
| Algorithm | xorshift128+ (V8) | OS CSPRNG (ChaCha20 / AES-CTR DRBG) |
| Seed source | Page-load timing, weak | Hardware entropy (RDRAND, interrupts) |
| Predictable from output? | Yes — state recovered from ~5 values | No — passes the next-bit test |
| State size | 128 bits | 256+ bits, periodically reseeded |
| Designed for | Speed (games, jitter, sampling) | Security (keys, tokens, salts) |
| Survives state leak? | No | Yes — forward secrecy via reseed |
A CSPRNG must pass two tests an ordinary PRNG fails. The next-bit test: given the first k output bits, no algorithm can predict bit k+1 better than a coin flip. And state-compromise resistance: even if an attacker steals the internal state, they can't reconstruct past outputs. Math.random() flunks both. It just isn't its job.
Why Math.random() is a security bug
V8's Math.random() runs xorshift128+ — a fast generator with a 128-bit state. Fast is the whole point; it's built for particle effects and shuffling arrays, not secrets. The fatal flaw is that the algorithm is public and the state is small.
Security researchers demonstrated years ago that you can collect a short run of Math.random() outputs, feed them to a SAT solver or Z3, and recover the full internal state. Once you have the state, you compute every future output. You also compute every past one. A "random" password reset token generated this way is guessable.
Here's the mental model — what an attacker sees in each case:
PRNG (Math.random)
observe: 0.731, 0.118, 0.992, 0.044, 0.556
│
▼ solve for 128-bit state
┌──────────────────────────────┐
│ state recovered → predict │ ✗ every future value known
│ 0.673, 0.281, 0.907, ... │ (and every past value)
└──────────────────────────────┘
CSPRNG (crypto.getRandomValues)
observe: 0x9f, 0x3a, 0xb7, 0xe1, 0x44
│
▼ solve for state?
┌──────────────────────────────┐
│ 256-bit state, reseeded from │ ✓ next byte = coin flip
│ OS entropy → no shortcut │ 2^256 brute force, infeasible
└──────────────────────────────┘
This is why the project's number-one rule is absolute: Math.random() in any crypto context is a critical security bug. Avoid tools that use Math.random(). Our Random Number Generator — runs 100% in your browser, zero data sent to any server — uses the Web Crypto API (crypto.getRandomValues()), ensuring your entropy source is as secure as your operating system's kernel.
Where real entropy comes from
A CSPRNG is still an algorithm — so where does the unpredictability come from? The seed. The operating system continuously harvests entropy from physical noise: interrupt timings, disk seek jitter, mouse movement, and dedicated hardware instructions like Intel's RDRAND (a thermal-noise-based source on the CPU die).
That entropy seeds a deterministic CSPRNG construction — usually a ChaCha20 or AES-CTR DRBG — which stretches a 256-bit seed into a practically infinite stream. The flow looks like this:
HARDWARE NOISE KERNEL POOL CSPRNG YOUR CODE
┌────────────────┐
│ RDRAND │
│ interrupt timing │──mix──▶ [ entropy pool ]──seed──▶ ChaCha20 ──stream──▶ crypto.
│ disk/IO jitter │ (256+ bits) DRBG getRandomValues()
│ device noise │ ▲ │
└────────────────┘ └──── reseed ────────┘

When you call crypto.getRandomValues() in the browser, you're tapping the same OS facility (getrandom() on Linux, BCryptGenRandom on Windows, arc4random on macOS) that the kernel uses for its own secrets. There is no weaker browser-specific path. That's the entire point.
The entropy math: why output length is everything
Once your source is a true CSPRNG, the only remaining variable is how many random bits you pull. Entropy follows the same formula that governs password strength:
$$H = L \times \log_2(R)$$
Where H = entropy in bits, L = number of symbols (or bytes) you generate, and R = the size of the pool each symbol is drawn from. For raw bytes, R = 256, so each byte contributes exactly 8 bits. A 16-byte token from a CSPRNG carries a full 128 bits of entropy. A 32-byte token carries 256.
The catch: this only holds if R is real. Math.random() looks like it gives you 52 bits of mantissa per call, but because the state is recoverable, its effective security entropy collapses to roughly zero against a motivated attacker. Length can't save a broken source.
| Output | CSPRNG entropy | Brute-force space | Verdict |
|---|---|---|---|
| 8 bytes | 64 bits | 2⁶⁴ | Weak — avoid for keys |
| 16 bytes | 128 bits | 2¹²⁸ | Strong — tokens, session IDs |
| 32 bytes | 256 bits | 2²⁵⁶ | Overkill — API keys, master secrets |
Math.random() ×N | ~0 effective | recover state, done | Broken |
To put 128 bits in perspective: even at an absurd 23 billion SHA-256 guesses/sec on an RTX 4090, brute-forcing a 128-bit space would take longer than the age of the universe by many orders of magnitude. The attacker doesn't fight the math — they look for the weak Math.random() shortcut instead. Close that shortcut and the math wins.
How to verify your generator is actually a CSPRNG
You can't eyeball randomness — humans are terrible at it, and so is the naked eye looking at hex. Use these checks instead.
- Read the source call. If you see
Math.random()anywhere near a secret, it's broken. Look forcrypto.getRandomValues(),crypto.randomUUID(), orcrypto.randomBytes(). - Run a statistical battery. Tools like
dieharderor the NIST SP 800-22 suite throw 15+ tests (monobit, runs, spectral) at a sample. A CSPRNG passes; xorshift output shows detectable bias on some. - Check the distribution. Generate a large batch and confirm uniform spread with no repeats where none should exist. Our Random Number Generator offers duplicate prevention and batch output precisely so you can sanity-check distribution.
- Verify the entropy bit count. A strength tool tells you whether your output actually carries the bits you think. Drop a generated secret into the Password Strength Checker to confirm the entropy estimate matches your byte length.
Statistical tests catch bias, but they can't prove unpredictability on their own — a broken generator can still look statistically uniform. That's why the source matters more than the histogram. Use the right API, then verify.
🛡️ Security Checkpoint — Complete This Step
Any secret you generated with
Math.random()— tokens, passwords, "random" IDs — should be considered compromised and regenerated from a real CSPRNG today.
- → Generate cryptographic random numbers —
crypto.getRandomValues(), OS-level entropy, neverMath.random()- → Create a high-entropy password via Password Generator — 128+ bits from the Web Crypto API in one click
- → Audit an existing secret's entropy — confirm it carries the bits you assumed
The one-line rule for every developer
If randomness protects anything — a password, a token, a salt, a nonce, a session ID — it must come from a CSPRNG. In the browser that's crypto.getRandomValues() or crypto.randomUUID(). On the server it's crypto.randomBytes(). Anywhere else, it's Math.random(), and Math.random() is for dice rolls, not defenses.
Your brain is a terrible CSPRNG. So is Math.random(). Use the one your kernel already trusts.
Frequently Asked Questions
What is a CSPRNG?
A CSPRNG (Cryptographically Secure Pseudo-Random Number Generator) is a generator whose output is computationally impossible to predict, even when an attacker has seen earlier outputs. It's seeded from operating-system entropy (hardware noise, RDRAND, interrupt timing) and passes the next-bit test and state-compromise tests that ordinary PRNGs like Math.random() fail.
Why is Math.random() not secure?Math.random() uses xorshift128+ in V8 — a fast, non-cryptographic algorithm with a small, recoverable 128-bit state. Collect a handful of outputs, solve for the state, and you can predict every future and past value. It was built for speed in games and simulations, never for security.
What should I use instead of Math.random()?
Use crypto.getRandomValues() or crypto.randomUUID() in the browser, and crypto.randomBytes() in Node.js. All three draw from the OS CSPRNG (getrandom(), BCryptGenRandom, arc4random), which is seeded from hardware entropy and is safe for keys, tokens, passwords, salts, and nonces.
How many random bytes do I need for a secure token? 16 bytes (128 bits of entropy) is the practical floor for session tokens and IDs — a 2¹²⁸ brute-force space is infeasible for any attacker. Use 32 bytes (256 bits) for API keys and master secrets. More than that adds length, not meaningful security.
Can statistical tests prove a generator is secure? No. Suites like NIST SP 800-22 or dieharder detect bias and non-uniformity, but a generator can pass every statistical test and still be predictable if its state is recoverable. Security depends on the source (a real CSPRNG), not just on the output looking uniform.