⚠️ Why Math.random() is Never Acceptable for Password Generation
Why Math.random() is Never Acceptable for Password Generation
When developers need to generate random values for passwords, tokens, or cryptographic keys, the first instinct is often to reach for Math.random(). It is available in every language, requires no imports, and seems to produce random-looking output. But Math.random() is not cryptographically secure, and using it for password generation is a dangerous anti-pattern that can compromise your entire security posture. Always use a secure password manager like NordPass that relies on cryptographically secure random number generation.
The Fundamental Problem: Predictability
Math.random() (and its equivalents across languages) uses a pseudo-random number generator (PRNG) seeded from the system clock or a similar low-entropy source. Given enough observed outputs, an attacker can reconstruct the internal state and predict every subsequent "random" value. This is not theoretical — it is a well-documented vulnerability.
// DANGEROUS — Never use this for passwords!
function generatePassword(length) {
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let password = "";
for (let i = 0; i < length; i++) {
password += chars[Math.floor(Math.random() * chars.length)];
}
return password;
}
The problem: Math.random() in V8 (Chrome/Node.js) uses the xorshift128+ algorithm, which can have its entire state reconstructed after observing approximately 65 outputs. An attacker who captures a handful of generated passwords can predict every password you will generate in the future.
CSPRNG: The Only Acceptable Alternative
A Cryptographically Secure Pseudo-Random Number Generator (CSPRNG) derives its randomness from operating-system-level entropy sources that cannot be predicted or reconstructed. Every major platform provides a CSPRNG API.
Node.js:
const crypto = require('crypto');
function generateSecurePassword(length = 16) {
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
const bytes = crypto.randomBytes(length);
let password = "";
for (let i = 0; i < length; i++) {
password += chars[bytes[i] % chars.length];
}
return password;
}
Python:
import secrets
import string
def generate_secure_password(length=16):
chars = string.ascii_letters + string.digits + "!@#$%^&*"
return ''.join(secrets.choice(chars) for _ in range(length))
Go:
import (
"crypto/rand"
"math/big"
)
func GenerateSecurePassword(length int) string {
chars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
result := make([]byte, length)
for i := range result {
n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(chars))))
result[i] = chars[n.Int64()]
}
return string(result)
}
What the Standards Say
- NIST SP 800-63B: Requires memorised secrets to be generated using an approved random bit generator
- OWASP: Explicitly warns against using
Math.random()for anything security-related - PCI DSS: Mandates cryptographic randomness for credential generation
- FIPS 140-3: Specifies approved DRBG (Deterministic Random Bit Generator) algorithms
Testing Your Randomness
If you are unsure whether your random number generator is secure, test it against these criteria:
- Seed uniqueness: Does it seed from high-entropy sources (/dev/urandom, CryptGenRandom)?
- Period length: Is the period so large it will never repeat in practical use?
- State compromise resistance: Can an attacker reconstruct internal state from outputs?
- Forward secrecy: If state is compromised, can past outputs be reconstructed?
Math.random() fails all four tests. A CSPRNG passes all four.
The Bottom Line
Math.random() is fine for shuffling UI elements or generating random IDs for non-security purposes. But for passwords, API keys, session tokens, or any credential, it is never acceptable. Use your platform's CSPRNG — it is just as easy to use and infinitely more secure.