The Math Behind Password Security: Entropy, Brute Force, and Best Practices
Password security advice has been notoriously wrong for decades. The old "must have uppercase, lowercase, number, and symbol" rules produce passwords like P@ssw0rd that are both easy to guess and annoying to remember. NIST finally updated its guidance in SP 800-63B, and the math supports a completely different approach. This guide covers the actual numbers behind password strength, modern cracking capabilities, and what the evidence says about best practices.
Generate cryptographically secure passwords with our Password Generator and compute hashes with our Hash Generator.
The Entropy Formula
Password entropy measures unpredictability in bits. The formula is:
Entropy = log2(N^L) = L × log2(N)
Where:
N = size of the character set
L = length of the password
This assumes randomly chosen characters—human-created "random" passwords have far less entropy than these calculations suggest, which is why password managers matter.
Specific Entropy Values
| Password Type | N (charset) | L (length) | Entropy (bits) |
|---|---|---|---|
| 8-char lowercase only | 26 | 8 | 37.6 bits |
| 8-char lowercase + uppercase | 52 | 8 | 45.6 bits |
| 8-char mixed case + digits | 62 | 8 | 47.6 bits |
| 8-char mixed + symbols (94 chars) | 94 | 8 | 52.4 bits |
| 12-char mixed + symbols | 94 | 12 | 78.6 bits |
| 16-char random printable | 94 | 16 | 104.9 bits |
| 20-char random printable | 94 | 20 | 131.1 bits |
// JavaScript entropy calculator
function calculateEntropy(length, charsetSize) {
return length * Math.log2(charsetSize);
}
// Examples
calculateEntropy(8, 26); // 37.6 bits (lowercase only)
calculateEntropy(8, 94); // 52.4 bits (full ASCII printable)
calculateEntropy(16, 94); // 104.9 bits
calculateEntropy(20, 94); // 131.1 bits
// Required length for N bits of entropy
function requiredLength(targetBits, charsetSize) {
return Math.ceil(targetBits / Math.log2(charsetSize));
}
requiredLength(128, 94); // 20 characters
requiredLength(128, 62); // 22 characters
requiredLength(128, 26); // 28 characters
Real Cracking Speeds in 2026
Modern GPU clusters can attempt billions of password hashes per second. The specific throughput depends entirely on the hashing algorithm—this is why algorithm choice for password storage is critical:
| Algorithm | RTX 4090 Speed | 8-char 94-charset Time |
|---|---|---|
| MD5 | ~164 billion/sec | ~2.3 hours |
| SHA-1 | ~57 billion/sec | ~6.6 hours |
| SHA-256 | ~23 billion/sec | ~16.4 hours |
| bcrypt (cost 10) | ~184,000/sec | ~560 years |
| bcrypt (cost 12) | ~46,000/sec | ~2,200 years |
| scrypt (N=32768) | ~16,000/sec | ~6,400 years |
| Argon2id (t=3, m=64MB) | ~11,000/sec | ~9,300 years |
The key insight: an 8-character password with all symbols is cracked in hours against MD5 but would take millennia against Argon2id. The hashing algorithm matters more than password complexity for stored passwords.
Time-to-Crack Table at Various Entropy Levels
Assuming a well-resourced attacker with 100 RTX 4090 GPUs attacking a bcrypt cost-12 hash:
| Entropy | Possible combinations | Average crack time |
|---|---|---|
| 40 bits | ~1 trillion | ~3 hours |
| 52 bits | ~4.5 quadrillion | ~550 days |
| 64 bits | ~18 quintillion | ~6,000 years |
| 80 bits | ~1.2 × 10^24 | ~390 million years |
| 128 bits | ~3.4 × 10^38 | Longer than the universe's age |
This shows that 64 bits of entropy (achievable with a 12-character random mixed-case password or a 5-word passphrase) is effectively uncrackable against bcrypt even with significant resources.
NIST SP 800-63B: The Evidence-Based Guidance
NIST's 2017 guidelines (updated in the 800-63B Digital Identity Guidelines) reversed decades of conventional wisdom. Key recommendations:
What NIST recommends:
- Minimum 8-character length (15+ for admin accounts)
- Support up to 64 characters (or more)
- Allow all ASCII printable characters plus spaces
- Check against known breached passwords (Have I Been Pwned API)
- Use a salt unique to each password before hashing
- Use a slow hashing function: bcrypt, scrypt, PBKDF2 (FIPS-approved), or Argon2
What NIST says to STOP doing:
- Forced periodic password rotation (increases weak passwords—users just increment:
Password1!→Password2!) - Complexity rules (uppercase + lowercase + number + symbol requirements)
- Password hints or security questions
- SMS as the sole second factor (susceptible to SIM swapping)
Diceware: The Physics-Based Passphrase
Diceware passphrases use physical dice rolls to select words from a numbered wordlist, providing provable randomness without a computer:
// EFF Long Wordlist: 7,776 words (6^5 = 7776, one 5-dice roll per word)
// Entropy per word: log2(7776) ≈ 12.92 bits
// Passphrase entropy by word count:
// 3 words: 38.8 bits (weak)
// 4 words: 51.7 bits (moderate)
// 5 words: 64.6 bits (strong — recommended minimum)
// 6 words: 77.5 bits (very strong)
// 7 words: 90.4 bits (excellent)
// Example 5-word passphrase: "correct horse battery staple union"
// Entropy: 64.6 bits (hard to crack, easy to remember)
The EFF wordlist is designed so that 3-letter hints uniquely identify each word, making passphrases easier to remember. A 5-word Diceware passphrase is both more memorable and more secure than a complex 8-character password.
Why Password Managers Solve Everything
The root problem with passwords is that humans cannot:
- Generate truly random strings
- Remember unique high-entropy strings for 100+ accounts
- Avoid reusing passwords across sites
Password managers solve all three. A password manager lets every account have a unique, 20-character random password without memorization. The only password that needs to be remembered is the manager's master password—where a 5+ word Diceware passphrase is ideal.
bcrypt vs scrypt vs Argon2 for Password Storage
bcrypt: The classic choice. Cost parameter is adjustable (doubles work per increment). Limitation: maximum 72-byte input (passwords >72 chars are truncated). Well-studied, available everywhere.
scrypt: Memory-hard (GPU cracking requires proportional RAM). Parameters: N (CPU/memory cost), r (block size), p (parallelism). Better than bcrypt for offline attack resistance.
Argon2id: Winner of the Password Hashing Competition (2015). Combines Argon2i (side-channel resistant) and Argon2d (GPU resistant). Three parameters: time cost (t), memory cost (m), parallelism (p). Current OWASP recommendation: t=3, m=64MB, p=4 for interactive logins.
// Node.js with Argon2 (argon2 npm package)
const argon2 = require('argon2');
// Hash a password
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 65536, // 64 MB in KB
timeCost: 3,
parallelism: 4
});
// "$argon2id$v=19$m=65536,t=3,p=4$..."
// Verify
const valid = await argon2.verify(hash, password);
// bcrypt (bcryptjs npm package - pure JS, or bcrypt for native)
const bcrypt = require('bcryptjs');
const saltRounds = 12; // ~300ms on modern hardware
const hash = await bcrypt.hash(password, saltRounds);
const valid = await bcrypt.compare(password, hash);
Rainbow Tables and Why Salting Is Non-Negotiable
A rainbow table is a precomputed table mapping hashes back to passwords. Without salting, if two users have the same password, their hashes are identical—and a single rainbow table entry reveals both.
A salt is a random value (16+ bytes) unique to each password, prepended before hashing. This forces an attacker to crack each password individually—precomputed tables are useless.
// Never do this - no salt!
const insecure = crypto.createHash('sha256').update(password).digest('hex');
// bcrypt and Argon2 automatically handle salting - the salt is embedded in the hash
// "$argon2id$v=19$m=65536,t=3,p=4$[SALT_HERE]$[HASH_HERE]"
// Manual salting (if using PBKDF2 for FIPS environments)
const salt = crypto.randomBytes(32);
const hash = await crypto.pbkdf2(password, salt, 600000, 32, 'sha256');
// Store both salt and hash