What Makes a Password Strong?
Password strength comes down to one metric: entropy — the number of bits of randomness an attacker must overcome. More entropy means more combinations to try, which means more time to crack.
The formula:
Entropy (bits) = log₂(Alphabet size ^ Password length)
= Password length × log₂(Alphabet size)
| Character Set | Size | 12-char entropy | 16-char entropy |
|---|---|---|---|
| Digits only | 10 | 39.9 bits | 53.1 bits |
| Lowercase only | 26 | 56.4 bits | 75.2 bits |
| Lower + upper | 52 | 68.4 bits | 91.2 bits |
| Lower + upper + digits | 62 | 71.5 bits | 95.3 bits |
| Lower + upper + digits + symbols | 94 | 78.6 bits | 104.8 bits |
Modern password crackers on consumer hardware: ~100 billion attempts/second for simple hashes. At that rate:
- 56-bit entropy: cracked in ~7 months
- 72-bit entropy: cracked in ~72,000 years
- 128-bit entropy: heat death of the universe
Practical recommendation: 128+ bits of entropy for passwords stored with strong algorithms; for anything using MD5/SHA1 (which attackers exploit against leaked databases), aim for 80+ bits.
How Attackers Crack Passwords
Understanding attacks helps you defend against them.
Dictionary Attacks
Attackers try common words, names, common substitutions (a→@, e→3, s→5), and append common suffixes (123, 2024!, !):
password → P@ssw0rd → P@ssw0rd123 → P@ssw0rd2024!
RockYou, Have I Been Pwned, and similar wordlists contain billions of common passwords and their variations.
Mask Attacks (Pattern-Based)
If an attacker knows the password policy (must have uppercase, digit, symbol), they narrow the search:
Mask: ?u?l?l?l?l?l?d?s → 1 uppercase + 5 lowercase + 1 digit + 1 symbol
This is why "Password1!" is weak — it matches the most common policy-compliant pattern.
Rainbow Tables
Precomputed hash tables for common passwords. Defeated by password salting (which all good password hashing algorithms do automatically).
What Doesn't Work (Common Misconceptions)
Substitutions (l33tspeak): Attackers include common substitutions in their dictionaries. P@ssw0rd is in every wordlist.
Adding numbers/symbols at the end: Attackers know users do this. correct1! is not much stronger than correct1.
Patterns on keyboard: qwerty, asdfgh, 123qwe are all in every wordlist.
Personal information: Birthdates, pet names, addresses — these are the first things targeted in targeted attacks.
What Actually Works
Random Characters
A truly random 16-character password from the full character set has ~105 bits of entropy and is essentially uncrackable:
Kp7!mNqX3#rL9sYv
The problem: impossible to memorize.
Diceware Passphrases
A string of random common words is both high-entropy and memorable:
correct horse battery staple
5 words from a 7776-word wordlist: log₂(7776^5) = 64.6 bits. Not crackable by brute force.
6 words: 77.5 bits. 8 words: 103.4 bits.
Words beat random characters for memorability at equivalent entropy.
Password Managers
The practical solution: generate a unique, truly random 20+ character password for every service, store them all in a password manager. You only memorize one strong master password.
Generating Passwords in Code
// Browser / Node.js — cryptographically secure
function generatePassword(length = 20, options = {}) {
const {
uppercase = true,
lowercase = true,
digits = true,
symbols = true,
} = options;
let charset = '';
if (lowercase) charset += 'abcdefghijklmnopqrstuvwxyz';
if (uppercase) charset += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
if (digits) charset += '0123456789';
if (symbols) charset += '!@#$%^&*()-_=+[]{}|;:,.<>?';
if (!charset) throw new Error('At least one character type required');
const array = new Uint32Array(length);
crypto.getRandomValues(array);
return Array.from(array)
.map(n => charset[n % charset.length])
.join('');
}
// Usage
const password = generatePassword(24, { symbols: true });
// => "Kp7!mNqX3#rL9sYv2@Hf8qR"
import secrets
import string
def generate_password(length=20, use_symbols=True):
chars = string.ascii_letters + string.digits
if use_symbols:
chars += '!@#$%^&*()-_=+[]{}|;:,.<>?'
# secrets.choice uses os.urandom — cryptographically secure
return ''.join(secrets.choice(chars) for _ in range(length))
# Diceware-style passphrase
import random
WORDLIST = ['apple', 'bridge', 'cloud', ...] # load your wordlist
def generate_passphrase(num_words=6):
return ' '.join(secrets.choice(WORDLIST) for _ in range(num_words))
Password Rules for Different Use Cases
| Use Case | Minimum | Recommended | Notes |
|---|---|---|---|
| Web account (bcrypt) | 12 chars | 20+ chars | bcrypt handles work factor |
| Admin / privileged account | 16 chars | 30+ chars | Use passphrase or password manager |
| Wi-Fi password | 12 chars | 20 chars | WPA2 has no rate limiting |
| PIN (4 digits) | — | Use 6 digits | Only for low-stakes, locked device |
| Encryption key passphrase | 20 chars | Diceware 8 words | Protects encrypted volume |
Evaluating Password Strength
A simple entropy-based meter:
function estimateEntropy(password) {
let charset = 0;
if (/[a-z]/.test(password)) charset += 26;
if (/[A-Z]/.test(password)) charset += 26;
if (/[0-9]/.test(password)) charset += 10;
if (/[^a-zA-Z0-9]/.test(password)) charset += 32;
const entropy = password.length * Math.log2(charset || 1);
if (entropy < 40) return { score: 0, label: 'Very Weak' };
if (entropy < 56) return { score: 1, label: 'Weak' };
if (entropy < 72) return { score: 2, label: 'Fair' };
if (entropy < 88) return { score: 3, label: 'Strong' };
return { score: 4, label: 'Very Strong' };
}
For a more sophisticated analysis (pattern detection, dictionary checking), use the zxcvbn library, which models realistic attack patterns.
→ Test and analyze your password strength with the Password Strength Analyser.