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 PoolPool Size12-char entropy
Lowercase only2656 bits
Lower + upper5268 bits
Lower + upper + digits6272 bits
Lower + upper + digits + symbols (32)9479 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:

  1. Compute SHA-1 hash of the password
  2. Send only the first 5 characters of the hash to the API
  3. The API returns all hashes starting with those 5 characters
  4. 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

GoalRecommendation
Random password16+ chars, mixed character set
Memorable passphrase6+ random words from Diceware
Application secretsecrets.token_hex(32) or openssl rand -hex 32
Password storageArgon2id ≥ 3 iterations, 64MB memory
Breach checkHave I Been Pwned API
Daily password managementBitwarden or 1Password

Entropy, randomness, and uniqueness — not arbitrary complexity rules — are what make passwords secure.

Generate a cryptographically strong password with ZeroTool →