Why You Should Never Use Math.random() for Cryptography
When developers need to generate a random token, a temporary password, or a unique session ID, their first instinct is usually to reach for Math.random().
If you are building a dice-rolling animation or placing particles on a canvas, this is perfectly fine. However, if you use this method to generate anything related to security, your application is inherently vulnerable. In this post, we will look at why standard JavaScript randomness is an illusion, and how to fix it.
The Illusion of Randomness (PRNGs)
Computers are deterministic machines; they cannot do anything truly randomly on their own. To simulate randomness, JavaScript engines (like Google's V8) use an algorithm called a Pseudo-Random Number Generator (PRNG).
Modern browsers typically use an algorithm called xorshift128+ for Math.random(). It takes a mathematical "seed" and performs highly predictable math operations on it to output the next number.
The Vulnerability
Because a PRNG relies on a static algorithm, it is entirely predictable if an attacker can figure out the current internal state of the generator. Security researchers have proven that by observing just a few outputs of Math.random(), an attacker can mathematically calculate the seed and predict every single "random" number your application will generate in the future.
// ❌ DANGEROUS: Generating an API Token
function generateInsecureToken() {
return Math.random().toString(36).substring(2) +
Math.random().toString(36).substring(2);
}
// An attacker can predict the next token generated by this function!
The Solution: CSPRNGs
To generate secure tokens, cryptographic salts, or Initialization Vectors (IVs) for AES encryption, you must use a Cryptographically Secure Pseudo-Random Number Generator (CSPRNG).
Unlike standard PRNGs, a CSPRNG draws "entropy" (true randomness) from the physical environment of the hardware—such as minute fluctuations in CPU temperature, keystroke timing, or operating system thread states. It is mathematically impossible to predict the next output, even if you know the previous one.
The Secure Implementation
In JavaScript, you access the browser's CSPRNG using the window.crypto.getRandomValues() API. This method requires you to create a typed array (like a Uint8Array) and passes it to the browser, which fills it with cryptographically secure random bytes.
// ✅ SECURE: Generating a 16-byte random token
function generateSecureToken() {
// 1. Create an empty array to hold 16 bytes (128 bits)
const array = new Uint8Array(16);
// 2. Ask the browser hardware to fill it with secure entropy
window.crypto.getRandomValues(array);
// 3. Convert the raw bytes to a readable hex string
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}
console.log(generateSecureToken());
// Output: e.g., "7f8a9b2c3d4e5f6a7b8c9d0e1f2a3b4c"
See Entropy in Production
In cryptographic applications, unpredictable randomness is the foundation of security. We rely exclusively on crypto.getRandomValues() in ZeroKey to generate AES Initialization Vectors (IVs) and PBKDF2 salts.
If an attacker could predict our IVs, the AES-256-GCM encryption would immediately become vulnerable to ciphertext manipulation.
Conclusion
Math.random() is great for UI animations, games, or A/B testing logic. But the golden rule of web security is simple: if the random data you are generating is used to protect user access, encrypt data, or identify a session, you must use the Web Crypto API.