Password Security and Entropy: Why Length Beats Complexity
26 February, 2026 Security
Complexity requirements have been the default password policy for decades: uppercase, lowercase, number, symbol, and call it done. Then NIST published SP 800-63B in 2017 and quietly told everyone they had been wrong. The research shows that P@ssw0rd1 is not meaningfully more secure than password1 against a prepared attacker - and that length is the metric that actually matters. This article works through the math so you can understand exactly why, and how to implement genuinely secure password generation in your applications.
What Is Password Entropy
Entropy in the context of passwords means unpredictability - specifically, how many bits of information a password contains when chosen from a given method. A password with H bits of entropy requires an attacker to try up to 2^H guesses in the worst case to find it by brute force.
The concept comes from information theory (Shannon entropy), but for password analysis we use a simplified model: we care about the size of the search space, not the compression complexity of a specific string. The two are related but distinct. When a policy forces P@ssword1 as a pattern, the actual entropy is far lower than the theoretical maximum, because real-world attackers use that pattern in their wordlists.
Entropy gives you a precise, comparable measurement across different password schemes. Instead of arguing whether symbols are "better" than extra characters, you calculate bits and compare directly.
How Entropy Is Calculated
The formula is:
H = L x log2(N)
Where:
- H = entropy in bits
- L = length of the password (number of characters)
- N = size of the character set (number of possible characters at each position)
Each character position contributes log2(N) bits. A password's total entropy is the sum of those contributions across all positions.
Concrete Examples
Lowercase letters only (a-z), 8 characters:
- N = 26, L = 8
- H = 8 x log2(26) = 8 x 4.7 = 37.6 bits
Mixed case + digits (a-z, A-Z, 0-9), 8 characters:
- N = 62, L = 8
- H = 8 x log2(62) = 8 x 5.95 = 47.6 bits
Full printable ASCII (95 characters), 8 characters:
- N = 95, L = 8
- H = 8 x log2(95) = 8 x 6.57 = 52.5 bits
Full printable ASCII, 12 characters:
- N = 95, L = 12
- H = 12 x log2(95) = 12 x 6.57 = 78.8 bits
The jump from 8 to 12 characters adds 26 bits. Adding a symbol set (going from 62 to 95 characters) only adds about 5 bits per character - and you only get that benefit if the symbols are chosen randomly, not substituted predictably (@ for a, 0 for o).
The formula assumes each character is chosen uniformly at random and independently. The moment you apply rules - capitalize the first letter, end with a digit, substitute symbols - you reduce the effective N without changing the apparent character set.
Length vs Complexity
The myth that complexity beats length comes from a misreading of how attacks work. An attacker with a modern GPU cluster does not try every possible character combination in order. They work through:
- Common wordlists (RockYou, Have I Been Pwned dumps)
- Rule-based mutations on those words (
passwordtoP@ssw0rd,password1,PASSWORD) - Markov-chain generated candidates based on real password patterns
- Only then, brute force from the full character set
Against strategies 1-3, P@ssw0rd1 offers nearly no resistance - it appears directly in mutation rulesets. Hashcat ships with rules that generate it automatically. Against strategy 4, the length of a truly random password determines how long brute force takes.
The Math on "Secure Complexity"
P@ssword1 contains 9 characters from a 95-character set. Theoretical maximum: 9 x 6.57 = 59 bits. Actual entropy against a wordlist + rules attack: effectively 0, because it is in the wordlist.
A randomly generated 12-character lowercase password (xvqmjptkrfan) has 56.4 bits of theoretical entropy and no wordlist match. It is more resistant to practical attack despite using a smaller character set.
NIST SP 800-63B section 5.1.1 now recommends:
- Minimum 8 characters for memorized secrets
- Maximum length of at least 64 characters
- No mandatory complexity rules
- Screening against known compromised password lists
- No periodic forced resets (they cause predictable patterns like
Password2025!)
Character Sets and Their Bits
The per-character entropy contribution for each common charset:
| Character Set | Size (N) | Bits per Character |
|---|---|---|
| Digits only (0-9) | 10 | 3.32 |
| Lowercase (a-z) | 26 | 4.70 |
| Lowercase + digits | 36 | 5.17 |
| Mixed case (a-zA-Z) | 52 | 5.70 |
| Mixed case + digits | 62 | 5.95 |
| Mixed case + digits + 8 symbols | 70 | 6.13 |
| Full printable ASCII | 95 | 6.57 |
| ASCII + extended (Latin-1) | 191 | 7.58 |
Note the diminishing returns: going from lowercase-only (4.70 bits) to full printable ASCII (6.57 bits) gains less than 2 bits per character. Adding 4 characters to a lowercase-only password (4 x 4.70 = 18.8 bits) beats that gain by an order of magnitude.
Passphrases vs Random Strings
The Diceware Method
Diceware generates passphrases by rolling physical dice and looking up results in a 7776-word list (6^5 words). Each word contributes log2(7776) = 12.92 bits of entropy.
A 4-word Diceware passphrase: 4 x 12.92 = 51.7 bits A 6-word Diceware passphrase: 6 x 12.92 = 77.5 bits A 7-word Diceware passphrase: 7 x 12.92 = 90.5 bits
The EFF wordlist (2016) is designed so every word is memorable and distinct. Six words produce a passphrase that exceeds the entropy of a random 12-character full-ASCII password.
XKCD 936
The webcomic "correct horse battery staple" (XKCD #936, 2011) illustrated this principle with a specific claim: four random common English words have around 44 bits of entropy (using a 2048-word common vocabulary: log2(2048) = 11 bits x 4 = 44 bits). That is weaker than Diceware because the vocabulary is smaller and more guessable. The comic's point was directionally correct - the specific numbers vary significantly by wordlist.
Comparison Table
| Scheme | Charset / Source | Length | Entropy (bits) |
|---|---|---|---|
Common password (P@ssword1) |
95 chars (theoretical) | 9 | ~0 effective |
| 8-char random lowercase | 26 chars | 8 | 37.6 |
| 8-char random full ASCII | 95 chars | 8 | 52.5 |
| 12-char random full ASCII | 95 chars | 12 | 78.8 |
| 16-char random full ASCII | 95 chars | 16 | 105.1 |
| 4-word Diceware | 7776 words | 4 words | 51.7 |
| 6-word Diceware | 7776 words | 6 words | 77.5 |
| 6-word common words | 2048 words | 6 words | 66.0 |
Passphrases trade memorability for modest length overhead. For machine-generated secrets (API keys, database passwords), a random 20+ character string from a full charset is the unambiguous right choice.
Common Attacks: Brute Force
Brute force iterates through every possible combination. Its feasibility depends on:
- Attack speed (hashes checked per second)
- Password entropy (search space size)
- Hashing algorithm used to store the password
A consumer NVIDIA RTX 4090 benchmarks at approximately:
- 164 billion MD5 hashes per second
- 21 billion bcrypt (cost 5) hashes per second
- 184 million bcrypt (cost 12) hashes per second
- 1.4 million Argon2id hashes per second (reference parameters)
At 184 million bcrypt/s against a 52-bit password (8 chars, full ASCII):
- Search space: 2^52 = 4.5 x 10^15
- Time to exhaust: 4.5 x 10^15 / 184 x 10^6 = roughly 280 days on a single GPU
Against a 78-bit password (12 chars, full ASCII), the same hardware would require approximately 2^78 / 184 x 10^6 hashes - on the order of tens of billions of years. Length makes brute force computationally infeasible long before "impossibly complex" character sets do.
Common Attacks: Dictionary and Rainbow Tables
Dictionary Attacks
Modern dictionary attacks use mutated wordlists. The RockYou leak (2009, ~14 million passwords) and subsequent breaches have produced wordlists containing hundreds of millions of real passwords. Hashcat's best64.rule ruleset applies 64 common transformations (capitalize, append digits, substitute characters) to each base word, multiplying the effective dictionary size.
Against a password that follows any recognizable pattern - even one that satisfies complexity requirements - dictionary + rules attacks succeed in seconds to hours.
Rainbow Tables
A rainbow table is a precomputed lookup structure: it maps password hashes back to the original passwords without storing every hash individually. A 2007 rainbow table for MD5-hashed 8-character alphanumeric passwords fits in under 1 GB and can crack hashes in seconds (time-memory trade-off, Hellman chains).
Salting defeats rainbow tables completely. A cryptographic salt is a random value, unique per user, prepended or appended to the password before hashing. Since each hash now includes a unique random component, precomputed tables are useless - an attacker must attack each hash individually. bcrypt, Argon2, and scrypt all incorporate salting automatically.
Never use unsalted hashing (plain MD5, SHA-1, SHA-256) for passwords. CVE-2012-3488 and dozens of similar CVEs document real systems that stored unsalted hashes.
The correct algorithm hierarchy for password storage in 2025:
- Argon2id - OWASP recommended, PHC winner, memory-hard
- bcrypt - proven, widely supported, cost factor adjustable
- scrypt - memory-hard, acceptable alternative
Do not use PBKDF2 for new systems unless FIPS compliance is required.
Password Managers
The fundamental problem with password security advice is human memory. A person who uses a unique, randomly generated 20-character password for every service is meaningfully secure. A person who reuses a memorable but weak password everywhere is not.
Password managers resolve this by:
- Generating cryptographically random passwords per site
- Storing them encrypted (typically AES-256-GCM or XChaCha20-Poly1305) behind one master password
- Autofilling so users never type stored passwords (defeats keyloggers for most scenarios)
A well-implemented password manager (Bitwarden, 1Password, KeePass) reduces the practical problem to protecting one strong master passphrase. That master passphrase should be a long Diceware passphrase - memorable by design, high entropy by construction.
For developers: consider whether your application should support passkeys (WebAuthn / FIDO2) instead of passwords. NIST SP 800-63B-4 (2024 draft) formally recommends phishing-resistant authenticators as the preferred approach.
Generating Secure Passwords
The critical requirement for secure password generation is a cryptographically secure random number generator (CSPRNG). Standard library rand() functions in most languages are not CSPRNGs - they are seeded with predictable values and produce statistically predictable output.
/generators/password
PHP
<?php
declare(strict_types=1);
function generatePassword(int $length = 16, string $charset = ''): string
{
if ($charset === '') {
$charset = 'abcdefghijklmnopqrstuvwxyz'
. 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
. '0123456789'
. '!@#$%^&*()-_=+[]{}|;:,.<>?';
}
$charsetLength = strlen($charset);
$password = '';
for ($i = 0; $i < $length; $i++) {
// random_int() uses the OS CSPRNG (getrandom() on Linux)
$password .= $charset[random_int(0, $charsetLength - 1)];
}
return $password;
}
// 16-char full charset: ~105 bits of entropy
echo generatePassword(16);
// Passphrase from a wordlist
function generatePassphrase(array $wordlist, int $wordCount = 6): string
{
$words = array_map(
static fn (): string => $wordlist[random_int(0, count($wordlist) - 1)],
range(1, $wordCount)
);
return implode('-', $words);
}
random_int() was added in PHP 7.0 and wraps the OS CSPRNG. Never use rand() or mt_rand() for security-sensitive values.
Python
import secrets
import string
def generate_password(length: int = 16, charset: str = '') -> str:
if not charset:
charset = (
string.ascii_lowercase
+ string.ascii_uppercase
+ string.digits
+ '!@#$%^&*()-_=+[]{}|;:,.<>?'
)
# secrets.choice() uses os.urandom() internally
return ''.join(secrets.choice(charset) for _ in range(length))
def generate_passphrase(wordlist: list[str], word_count: int = 6) -> str:
return '-'.join(secrets.choice(wordlist) for _ in range(word_count))
# 20-char password: ~131 bits of entropy
print(generate_password(20))
The secrets module (Python 3.6+) is the correct choice. Never use random.choice() for passwords - random is seeded deterministically and is not cryptographically secure.
JavaScript
// Node.js
import { randomInt } from 'node:crypto';
function generatePassword(length = 16, charset = '') {
if (!charset) {
charset =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' +
'0123456789!@#$%^&*()-_=+[]{}|;:,.<>?';
}
let password = '';
for (let i = 0; i < length; i++) {
password += charset[randomInt(0, charset.length)];
}
return password;
}
// Browser - Web Crypto API (all modern browsers)
function generatePasswordBrowser(length = 16) {
const charset =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' +
'0123456789!@#$%^&*()-_=+[]{}|;:,.<>?';
const array = new Uint32Array(length);
crypto.getRandomValues(array);
return Array.from(array, (n) => charset[n % charset.length]).join('');
}
Math.random() is explicitly not a CSPRNG. Use crypto.getRandomValues() in browsers and node:crypto in Node.js.
Conclusion
Password entropy is not an abstract concept - it is a measurable property with direct consequences for how long a password resists attack. The formula H = L x log2(N) gives you a precise tool to compare schemes and make informed decisions.
The key findings from the math:
- Length multiplies entropy linearly. Every extra character at full ASCII adds 6.57 bits.
- Character set growth is logarithmic and saturates quickly. Going from 62 to 95 characters adds less than 0.7 bits per character.
- Complexity rules that enforce recognizable patterns reduce real entropy dramatically, regardless of what the theoretical charset implies.
- Salting with Argon2id, bcrypt, or scrypt is non-negotiable for password storage.
- CSPRNGs (
random_int(),secrets,crypto.getRandomValues()) are mandatory for password generation.
Generate a strong, properly random password right here: /generators/password