Password Generator Guide: Entropy, Character Sets, and NIST 2024
Most passwords are not random. They follow patterns — capitalized first letter, number at the end, common word substitutions — that attackers have catalogued and baked into their cracking dictionaries. A good password generator sidesteps all of that by drawing from a cryptographically secure source of randomness and giving you direct control over the alphabet it samples from. Use the Password Generator to follow along as you read.
What "Strong" Actually Means
Password strength is measured in entropy bits. Entropy quantifies how many equally likely possibilities an attacker must search through when brute-forcing. The formula is straightforward:
entropy = length × log₂(charset_size)If your charset has 62 characters (a–z, A–Z, 0–9) and your password is 16 characters long:
entropy = 16 × log₂(62) = 16 × 5.95 ≈ 95 bitsEach additional bit doubles the search space. Going from 64 bits to 96 bits does not add 50% more work for the attacker — it multiplies the work by 2^32, which is about 4 billion times harder.
| Entropy | Resistance | Suitable For |
|---|---|---|
| < 40 bits | Cracked in seconds on modern hardware | Nothing |
| 40–60 bits | Hours to days on a GPU cluster | Low-value, short-lived tokens only |
| 60–80 bits | Years without a breach + fast hash | Acceptable minimum for online accounts |
| 80–128 bits | Decades to centuries on any plausible hardware | Password manager entries, API keys |
| > 128 bits | Computationally infeasible for the foreseeable future | Encryption keys, master passwords |
These estimates assume the attacker has obtained a hashed copy of your password and is running an offline attack. Against online login forms with rate limiting, even 40 bits is practically uncrackable — but breached databases are the real threat model.
Length vs. Complexity
A common misconception is that requiring symbols makes passwords significantly stronger. Compare these two passwords, both at a similar entropy level:
| Password | Charset Size | Length | Entropy | Crack Time (10B/s) |
|---|---|---|---|---|
correct-horse-battery | 26 (lowercase + hyphen) | 21 chars | ~99 bits | > 10,000 years |
xK9#mQ2! | 95 (full printable ASCII) | 8 chars | ~52 bits | ~1 hour |
Tr0ub4dor&3 | 95 | 11 chars | ~71 bits | ~550 years |
n7Xq2pLm9Rv4kYwZ | 62 (alphanumeric) | 16 chars | ~95 bits | > 1,000 years |
The 8-character symbol-heavy password is far weaker than a 16-character alphanumeric one, yet it looks more "complex." Length is the dominant factor. Adding symbols to a short password is a worse tradeoff than simply making the password longer.
The crack-time figures above assume 10 billion guesses per second — achievable with a consumer GPU against a fast hash like MD5 or SHA-1. Against a proper slow hash (bcrypt, Argon2), those times stretch by 5–6 orders of magnitude. See the hashing guide for details, and use the bcrypt generator to hash passwords for storage.
Character Sets
Each character set adds a different amount of entropy per character. Here is the math for common sets:
| Character Set | Size | Bits/Char | 16-char entropy |
|---|---|---|---|
| Lowercase only (a–z) | 26 | 4.70 | 75 bits |
| Lowercase + uppercase | 52 | 5.70 | 91 bits |
| Alphanumeric (a–z, A–Z, 0–9) | 62 | 5.95 | 95 bits |
| Alphanumeric + 10 symbols | 72 | 6.17 | 99 bits |
| Full printable ASCII | 95 | 6.57 | 105 bits |
| Hex (0–9, a–f) | 16 | 4.00 | 64 bits |
Going from alphanumeric to full ASCII saves you roughly one character of length for the same entropy — a minor gain that often is not worth the tradeoff of having passwords that break some forms, terminals, or config file parsers that mishandle quotes and backslashes.
Ambiguous characters
Some generators offer an "exclude ambiguous characters" option that removes 0, O, l, 1, and I. This reduces the charset from 62 to 57 for alphanumeric, costing about 0.13 bits per character — negligible. Enable it when passwords need to be read aloud or typed manually. Disable it for passwords that go directly into a password manager and are never typed by hand.
NIST 2024 Guidelines
NIST SP 800-63B (updated in 2024) is the authoritative US standard for password policy. Its main changes from older guidance are worth knowing:
- Minimum length is 8 characters for user-chosen passwords, but NIST recommends supporting up to at least 64 characters. Services that cap passwords at 12 or 16 characters are noncompliant and harm security.
- No forced composition rules. Requiring uppercase, numbers, and symbols does not meaningfully increase security and leads to predictable patterns (
Password1!). NIST explicitly advises against mandating specific character types. - No periodic rotation unless there is evidence of compromise. Forced rotation causes users to make incremental, predictable changes (
Password1!→Password2!). - Check against breach lists. Verifiers should reject passwords found in known data breaches. Services like Have I Been Pwned expose a k-anonymity API that lets you check without transmitting the full password.
- Allow all printable Unicode including spaces. Blocking characters like
&or"pushes users toward weaker passwords.
The practical takeaway for password generation: maximize length, use a broad but safe charset, and let the output go straight into a password manager rather than forcing users to memorize something they will simplify.
Passphrases and Diceware
A passphrase is a sequence of randomly chosen words rather than random characters. The classic method is Diceware: roll five physical dice, look up the resulting number in the EFF large wordlist (7,776 words), and repeat for each word. The entropy per word is:
log₂(7776) ≈ 12.92 bits per word| Word Count | Entropy | Example | Crack Time (10B/s) |
|---|---|---|---|
| 4 words | ~51 bits | piano-surge-clinic-waffle | ~1 hour |
| 5 words | ~64 bits | piano-surge-clinic-waffle-orbit | ~580 years |
| 6 words | ~77 bits | piano-surge-clinic-waffle-orbit-fudge | ~1.2 billion years |
| 7 words | ~90 bits | piano-surge-clinic-waffle-orbit-fudge-tonic | computationally infeasible |
Passphrases have one advantage over random character strings: humans can memorize them. A 6-word Diceware passphrase at 77 bits is stronger than the typical 8-character symbol-bearing password and can be typed from memory. The downside is length — 35–45 characters — which breaks some legacy systems with short password fields.
Important: the word selection must be uniformly random. Choosing words yourself or from a biased source (common words you like) eliminates most of the entropy. Proper Diceware uses physical dice or a CSPRNG. A 4-word passphrase where you pick "comfortable" words has roughly the entropy of 2–3 random words.
Where Passwords Go After Generation
A generated password is only as safe as where you store it. The correct destination is a password manager — never:
- A plaintext file or notes app
- A spreadsheet, even encrypted with a weak password
- A browser bookmark title or URL field
- Your email drafts folder
- A sticky note, physical or digital
Password managers (Bitwarden, 1Password, KeePassXC) encrypt your vault with a master password and, critically, use a slow key derivation function (PBKDF2, Argon2) to protect the vault encryption key even if the file is stolen.
On the server side, passwords must never be stored in plaintext or with a fast hash (MD5, SHA-256). Use a purpose-built password hashing algorithm:
| Algorithm | Memory Hard | NIST / OWASP Status | Notes |
|---|---|---|---|
| Argon2id | Yes | Recommended (OWASP first choice) | Winner of PHC 2015; best modern choice |
| bcrypt | No | Acceptable | 64-byte input limit; work factor via cost |
| scrypt | Yes | Acceptable | Memory-hard; harder to tune safely |
| PBKDF2 | No | Acceptable (FIPS-approved) | Use with SHA-256; high iteration count required |
Use the bcrypt generator to hash a password and verify a hash directly in your browser, or read the hashes and password hashing guide for a deeper look at how these algorithms work.
Programmatic Generation
When generating passwords in code, you must use a cryptographically secure pseudorandom number generator (CSPRNG). The standard library CSPRNG in every major runtime is the correct tool. Never use Math.random() or equivalent non-cryptographic generators.
Node.js — crypto.randomBytes
import { randomBytes, randomInt } from 'crypto';
const CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
function generatePassword(length: number): string {
// randomInt(min, max) is uniformly distributed and cryptographically secure
return Array.from({ length }, () => CHARSET[randomInt(CHARSET.length)]).join('');
}
console.log(generatePassword(20)); // e.g. "nX4pKqL8rVwM2cYtBs9J"
// For raw random bytes (hex token, API key):
const token = randomBytes(32).toString('hex'); // 64 hex chars = 256 bitsPython — secrets module
import secrets
import string
CHARSET = string.ascii_letters + string.digits # 62 chars
def generate_password(length: int = 20) -> str:
# secrets.choice uses os.urandom internally — CSPRNG
return ''.join(secrets.choice(CHARSET) for _ in range(length))
print(generate_password(20))
# URL-safe token (base64-encoded, suitable for URLs and headers):
token = secrets.token_urlsafe(32) # 43 chars, ~192 bits of randomness
# Hex token (suitable for DB storage):
token_hex = secrets.token_hex(32) # 64 hex chars, 256 bitsBrowser — crypto.getRandomValues
function generatePassword(length: number, charset: string): string {
const values = new Uint32Array(length);
crypto.getRandomValues(values); // Web Crypto API — CSPRNG in all modern browsers
// Rejection sampling: discard values that would cause modulo bias
const max = Math.floor(0x100000000 / charset.length) * charset.length;
return Array.from(values)
.filter(v => v < max) // eliminate bias
.slice(0, length) // take exactly 'length' chars
.map(v => charset[v % charset.length])
.join('');
}
const CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
console.log(generatePassword(20, CHARSET));The rejection sampling step above is important. Naive modulo operations on random integers introduce a small but measurable bias toward lower-index characters when the charset size does not evenly divide the integer range. For a 62-character charset and a 32-bit integer, the bias is about 0.0015% per character — tiny but non-zero.
Common Generator Pitfalls
Math.random() and non-CSPRNG sources
Math.random() in JavaScript, rand() in C, and random.random() in Python are all seeded from system entropy at startup but are not designed to be cryptographically unpredictable. An attacker who observes a few outputs can reconstruct the internal state and predict future outputs. Any password, token, or secret generated with these functions should be considered compromised.
Insufficient charset after filtering
Some generators strip "confusing" characters, special characters, and characters that break specific systems, leaving an effective charset of 30–40 characters. At 16 characters with a 36-character charset (alphanumeric, lowercase), entropy drops to 83 bits — still fine. But if you then also restrict length to 12 characters, you are at 62 bits — marginally acceptable and at risk if the hash is weak.
Predictable patterns from poor composition logic
Generators that ensure "at least one uppercase, one digit, one symbol" by placing those required characters at fixed positions (e.g., always a digit at position 0, always a symbol at the end) reduce the search space significantly. If an attacker knows the generator always produces the pattern [digit][lower×n][symbol], they search fewer combinations. Composition requirements should be enforced by generating random passwords and discarding any that do not meet the criteria — not by filling specific slots with specific character types.
Reseeding on page reload
Client-side generators that use Date.now() or the page load timestamp as a seed are vulnerable if an attacker knows roughly when a password was generated. Even a week-wide window of uncertainty has only ~600 million milliseconds — a trivial search space.crypto.getRandomValues() does not have this problem.
Quick Reference
| Use Case | Recommended Config | Min Entropy |
|---|---|---|
| Online account (password manager entry) | 20 chars, alphanumeric + symbols | 128 bits |
| Password manager master password | 6-word Diceware passphrase | 77 bits (memorable) |
| API key / secret token | crypto.randomBytes(32).toString('hex') | 256 bits |
| Session token | crypto.randomBytes(16).toString('base64url') | 128 bits |
| Temporary invite code (human-readable) | 12 chars, alphanumeric, no ambiguous | 71 bits |
| WiFi / device PIN (must be typed) | 4-word passphrase or 12 alphanumeric chars | 51–71 bits |
Generate cryptographically secure passwords for all these scenarios directly in your browser with the Password Generator — all randomness stays on your machine, nothing is transmitted.