Most password advice is either wrong or impractical. “Use a mix of uppercase, lowercase, numbers, and symbols” sounds rigorous but produces passwords like P@ssw0rd1! that are easy for computers to crack and hard for humans to remember. This guide cuts through the noise.
What Actually Makes a Password Strong?
Password strength is fundamentally about entropy — how many guesses an attacker needs to find the password.
A 12-character password like Tr0ub4dor&3 feels complex. But because it follows a common pattern (word + number substitutions + symbol), a dictionary attack with rule-based mutations can crack it faster than a random 8-character password.
True randomness beats perceived complexity every time.
Password Entropy
Entropy measures unpredictability in bits. Each bit doubles the search space.
If you choose from a pool of N characters and use a password of length L:
Entropy = L × log₂(N)
| Character Pool | Pool Size | 12-char entropy |
|---|---|---|
| Lowercase only | 26 | 56 bits |
| Lower + upper | 52 | 68 bits |
| Lower + upper + digits | 62 | 72 bits |
| Lower + upper + digits + symbols (32) | 94 | 79 bits |
| Passphrase (word list of 7776 words) | 7776 | ~51 bits/word |
For reference:
- < 50 bits: crackable in seconds to hours on modern hardware
- 50–70 bits: adequate for low-risk accounts with rate limiting
- 70+ bits: strong for most purposes
- 100+ bits: future-proof
Generate a Strong Password
Try the ZeroTool Password Generator →
Generates cryptographically random passwords using your browser’s crypto.getRandomValues() API. Options for length, character sets, and number of passwords. Nothing is sent to a server.
How Password Generators Work
A secure password generator must use a cryptographically secure random number generator (CSPRNG), not a standard RNG.
The difference matters:
// WRONG: Math.random() is NOT cryptographically secure
// An attacker who knows the seed can predict all output
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let password = '';
for (let i = 0; i < 16; i++) {
password += chars[Math.floor(Math.random() * chars.length)];
}
// CORRECT: crypto.getRandomValues() is cryptographically secure
function generatePassword(length = 16, charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*') {
const array = new Uint32Array(length);
crypto.getRandomValues(array);
return Array.from(array, n => charset[n % charset.length]).join('');
}
In Python:
import secrets
import string
def generate_password(length=16, use_symbols=True):
alphabet = string.ascii_letters + string.digits
if use_symbols:
alphabet += string.punctuation
return ''.join(secrets.choice(alphabet) for _ in range(length))
# secrets module uses os.urandom() — cryptographically secure
password = generate_password(20)
Note: random.choice() in Python is NOT cryptographically secure. Always use secrets.choice() for security-sensitive randomness.
Passphrases: An Alternative to Random Character Strings
A passphrase is a sequence of random dictionary words:
correct-horse-battery-staple
violet-mountain-cloud-seven-river
Each word from a 7776-word list (Diceware) adds about 12.9 bits of entropy. Four words gives ~52 bits, five words gives ~64 bits, six words gives ~77 bits.
Advantages:
- Easier to type on mobile
- More memorable if you occasionally need to remember it
- Visually distinct — less likely to be misread from a screen
Disadvantages:
- Longer (more characters to type)
- Some systems have character limits that break long passphrases
Password Length vs Complexity
If you must choose between longer and more complex, choose longer. An additional character multiplies the search space by the pool size (26–94x). Adding special characters when you already have lower/upper/digits adds less than you think — only 3 extra bits for a 16-character password.
Practical recommendation: use at least 16 characters with mixed character classes.
You Should Not Be Remembering Passwords
If you are generating a unique random password for every account (which you should), you cannot memorize them. This is why password managers exist.
A password manager stores all your passwords encrypted with a single master password. You memorize one strong passphrase; the manager handles the rest.
Good options:
- Bitwarden — open source, free tier is excellent, self-hostable
- 1Password — polished UX, strong team sharing features
- KeePassXC — local-only, no cloud dependency
Never reuse passwords. One data breach exposes all your accounts if you reuse.
Developer-Specific: Generating Secrets
For application secrets (API keys, JWT secrets, session keys, encryption keys), use your platform’s secure random bytes function:
# Shell — 32 random bytes as hex (good for most secrets)
openssl rand -hex 32
# Shell — base64 encoded
openssl rand -base64 32
import secrets
# 32-byte hex secret (64 chars)
api_key = secrets.token_hex(32)
# URL-safe base64 secret
session_secret = secrets.token_urlsafe(32)
// Node.js
import { randomBytes } from 'crypto';
const apiKey = randomBytes(32).toString('hex');
For JWT secrets, use at least 256 bits (32 bytes). For AES-256, you need exactly 32 bytes. Never use a human-memorable string as a cryptographic key.
Have I Been Pwned Integration
The Have I Been Pwned (HIBP) API lets you check whether a password has appeared in known data breaches — without sending the actual password:
- Compute SHA-1 hash of the password
- Send only the first 5 characters of the hash to the API
- The API returns all hashes starting with those 5 characters
- Check locally if the full hash is in the list
import hashlib
import requests
def is_pwned(password):
sha1 = hashlib.sha1(password.encode()).hexdigest().upper()
prefix, suffix = sha1[:5], sha1[5:]
response = requests.get(f'https://api.pwnedpasswords.com/range/{prefix}')
hashes = (line.split(':') for line in response.text.splitlines())
return any(h == suffix for h, _ in hashes)
if is_pwned("P@ssw0rd1"):
print("This password has appeared in a known breach!")
Any password that appears in the HIBP database should never be used, regardless of its apparent complexity.
Password Requirements That Actually Help
When building registration or password reset forms:
Do:
- Set a minimum length (at least 12, preferably 16)
- Allow all printable characters including spaces
- Reject passwords from the HIBP database
- Show a strength meter
Do not:
- Require specific character classes (forces patterns, reduces entropy)
- Set a maximum length (meaningless with modern hashing)
- Rotate passwords on a fixed schedule (causes predictable increments)
- Allow password hints
Summary
| Goal | Recommendation |
|---|---|
| Random password | 16+ chars, mixed character set |
| Memorable passphrase | 6+ random words from Diceware |
| Application secret | secrets.token_hex(32) or openssl rand -hex 32 |
| Password storage | Argon2id ≥ 3 iterations, 64MB memory |
| Breach check | Have I Been Pwned API |
| Daily password management | Bitwarden or 1Password |
Entropy, randomness, and uniqueness — not arbitrary complexity rules — are what make passwords secure.
Generate a cryptographically strong password with ZeroTool →