Randomness is one of those concepts that seems simple until you look closely. You ask for a random number, you get one, what's the big deal?
In cryptography, it's a very big deal. The difference between "random" and "cryptographically random" is the difference between a system that's secure and one that's trivially broken. And in JavaScript, the difference between these two types of randomness is literally two function calls: Math.random() and crypto.getRandomValues().
Let's unpack what CSPRNGs are, why they matter, and why using the wrong random function can undermine your entire security architecture.
The Two Types of Random
There are two fundamentally different kinds of random number generators, and they serve completely different purposes.
**PRNG (Pseudo-Random Number Generator).** This is what Math.random() uses. It's a deterministic algorithm that produces a sequence of numbers that look random but are completely predictable if you know the starting value (called the seed). PRNGs are fast, efficient, and perfect for games, simulations, and statistical sampling. They are absolutely not suitable for cryptography.
**CSPRNG (Cryptographically Secure Pseudo-Random Number Generator).** This is what crypto.getRandomValues() uses. It's also a deterministic algorithm at its core, but it's designed so that even if an attacker observes the output, they cannot predict future outputs or reconstruct past outputs. CSPRNGs gather entropy from unpredictable physical sources to ensure their output is genuinely unpredictable.
The key difference is predictability. A PRNG's output is predictable if you know its internal state. A CSPRNG's output is not predictable even if you know the algorithm, because it's seeded with high-entropy data from physical sources that an attacker cannot observe.
Why Math.random() Is Dangerous for Security
Math.random() is a PRNG. In most JavaScript engines, it uses an algorithm like xorshift128+ or a similar fast PRNG. These algorithms produce numbers that pass statistical tests for randomness, meaning the distribution of outputs is uniform and doesn't show obvious patterns.
But statistical randomness is not cryptographic randomness. The critical weakness is that Math.random() is predictable.
Here's how an attack might work. An attacker observes a few outputs from your Math.random() call. Because the algorithm is known and the internal state space is limited, the attacker can work backwards to determine the internal state of the generator. Once they know the state, they can predict every future output and reconstruct every past output.
This has been demonstrated in practice. Researchers have shown that observing just a few Math.random() outputs is enough to predict the entire sequence. There are even online tools where you can paste a few Math.random() outputs and get back the internal state.
If you're using Math.random() to generate:
- **Session tokens:** An attacker who observes one token can predict future tokens and hijack sessions. - **Password reset links:** An attacker can predict the token and reset passwords for any user. - **API keys:** An attacker can predict the key and impersonate legitimate clients. - **Encryption keys:** An attacker can predict the key and decrypt all "encrypted" data. - **CSRF tokens:** An attacker can predict the token and bypass CSRF protection.
In every one of these cases, using Math.random() completely undermines the security you think you have. The encryption, the tokens, the session management, it's all theater if the underlying randomness is predictable.
How CSPRNGs Work
A CSPRNG solves the predictability problem by incorporating true entropy from the physical world.
Computers are deterministic machines. They execute instructions and produce predictable outputs. So where does randomness come from? The operating system collects it from sources that are fundamentally unpredictable:
- **Hardware noise:** Thermal noise in electronic components, which is genuinely random at the quantum level. - **Timing events:** Precise timing of user input, network packets, disk operations. These are influenced by countless factors that no attacker can control or predict. - **Hardware random number generators:** Modern CPUs (like Intel's RDRAND instruction) provide hardware-level randomness based on thermal noise.
The operating system collects this entropy into an entropy pool. When a CSPRNG needs to produce random output, it draws from this pool. The output is unpredictable because it's seeded with data that an attacker cannot observe or reproduce.
In the browser, crypto.getRandomValues() taps into this system. The details vary by platform, but the result is the same: random numbers that are cryptographically secure and suitable for generating keys, tokens, and other security-critical values.
Using crypto.getRandomValues() in the Browser
The Web Crypto API provides crypto.getRandomValues() for generating cryptographically secure random values. Here's how to use it:
javascript // Generate a random 32-byte value (256 bits) const array = new Uint8Array(32); crypto.getRandomValues(array); console.log(array);
This fills the array with 32 bytes of cryptographically secure random data. You can use it for any size array, though there's typically a limit of 65,536 bytes per call.
For a random hex string (useful for keys and tokens):
javascript function generateRandomHex(bytes) { const array = new Uint8Array(bytes); crypto.getRandomValues(array); return Array.from(array) .map(b => b.toString(16).padStart(2, '0')) .join(''); }
console.log(generateRandomHex(32)); // 64-character hex string
For a random base64 string:
javascript function generateRandomBase64(bytes) { const array = new Uint8Array(bytes); crypto.getRandomValues(array); return btoa(String.fromCharCode(...array)); }
console.log(generateRandomBase64(32));
For a UUID (though crypto.randomUUID() is simpler):
javascript // Modern browsers support this directly console.log(crypto.randomUUID());
## Using CSPRNG in Node.js
In Node.js, you have several options. The global crypto object (available in Node 15+) provides the same API as the browser:
javascript const array = new Uint8Array(32); crypto.getRandomValues(array);
Or use the traditional crypto module:
javascript const crypto = require('crypto'); const buf = crypto.randomBytes(32); console.log(buf.toString('hex'));
Both use the same underlying CSPRNG. The randomBytes method is async-friendly with crypto.randomFillSync for non-blocking use.
Using CSPRNG in Python
Python's secrets module is the CSPRNG interface:
python import secrets
# Generate a random hex string print(secrets.token_hex(32))
# Generate a URL-safe string print(secrets.token_urlsafe(32))
# Generate random bytes print(secrets.token_bytes(32))
This is what Django uses internally for its SECRET_KEY generation. For more on that, see our Django SECRET_KEY guide.
Real-World Consequences
The danger of using the wrong random function isn't theoretical. There are numerous documented cases of serious security failures caused by weak randomness.
In 2012, a vulnerability was found in certain RSA key generation implementations where insufficient entropy during key generation produced predictable keys. Millions of keys had to be regenerated.
In 2015, a JavaScript library used for Bitcoin wallet generation was found to use Math.random() for key generation. Every wallet generated by the library was vulnerable. Attackers could predict private keys and steal funds.
In 2017, a security researcher demonstrated that he could predict the session tokens generated by a popular web framework because it used a PRNG with insufficient entropy. He hijacked active sessions in real time.
These are not edge cases. They are direct consequences of using the wrong type of randomness for security purposes. The fix in every case was the same: use a CSPRNG.
How to Tell Which One You're Using
If you're not sure whether your code is using a secure random function, here's a quick guide:
**Insecure (PRNG):** - Math.random() in JavaScript - random.random() in Python (use secrets instead) - random module in most languages without a crypto-specific alternative - Any function documented as "for statistical purposes" or "not for cryptography"
**Secure (CSPRNG):** - crypto.getRandomValues() in browsers - crypto.randomBytes() in Node.js - secrets module in Python - /dev/urandom or /dev/random on Linux - CryptGenRandom on Windows - SecRandomCopyBytes on macOS/iOS
When in doubt, look for the word "crypto" or "secure" in the function name. If neither appears, it's probably not suitable for security purposes.
The Bottom Line
Randomness is the foundation of cryptographic security. Every encryption key, every session token, every password reset link, every CSRF token relies on random numbers being truly unpredictable. Use the wrong random function, and all of these become predictable, and your security collapses.
The rule is simple: for anything security-related, use a CSPRNG. In the browser, that means crypto.getRandomValues(). In Node.js, crypto.randomBytes(). In Python, the secrets module. Never Math.random(), never the random module, never anything that doesn't explicitly claim cryptographic security.
If you need to generate a secret key right now, use our Django Key Generator, which uses crypto.getRandomValues() to produce cryptographically secure keys directly in your browser. And for the broader context of how these keys are used in web frameworks, read our Django SECRET_KEY guide.
Your security is only as strong as your randomness. Make it count.