Diceware Passphrases: Why I Stopped Memorising Random Strings
18 May, 2026 Security
For years my password manager unlock was a 20-character random string. I retyped it dozens of times a day, and I still occasionally fat-fingered the symbols. Then one weekend I lost an hour fighting an autofill bug and had to type it manually on every site I touched. That afternoon I switched to a seven-word diceware passphrase. Same entropy, faster to type, easier to recover from typos because I could see where I went wrong. The lesson — the secrets you actually have to remember should not look like keyboard noise. They should look like words.
What follows is the practical case for passphrases: how the EFF Large list actually works, what the entropy buys you, the trade-offs in separator and capitalisation choices, and where passphrases beat random strings (and where they lose). For the underlying maths in detail, see my guide to password entropy — this piece assumes the basics and focuses specifically on the passphrase shape.
What a Diceware Passphrase Actually Is
Diceware is a method, not a wordlist. The original 1995 version by Arnold Reinhold used a 7,776-word list (6^5, because you rolled five physical six-sided dice per word) and a memorable mnemonic process: roll, look up the five-digit number in the list, write the word down, repeat. The dice were the cryptographically secure random number generator — your hands, doing actual work.
The EFF Large list (2016) is a modern replacement. It keeps the 7,776 entries but filters for words 3-9 letters long, removes homophones, removes obscure vocabulary, and removes anything offensive. Every word is easy to spell, easy to type, and unlikely to be confused with another word on the list. That last property matters more than it sounds — if half your list is their/there/they're, you cannot read the phrase aloud reliably.
Each word from the EFF Large list contributes log2(7776) = 12.92 bits of entropy, assuming the selection is genuinely uniform and random. Six words gives you about 77 bits. Seven words gives 90. That is the whole product.
You can generate one in your browser — the picker uses the browser's Web Crypto API for the randomness, which is the same primitive your TLS connections rely on.
Why Words Beat Random Characters
Memorability is the headline argument, but it is not the whole one. Four reasons words win for human-facing secrets:
Typing tolerance. When you mistype kQ7$mPx2!n3Rv8wL you usually have no idea where the error is. When you mistype correct-horse-battery-staple you can see correkt and fix it. Same character-level error rate; massively different recovery experience.
Mobile keyboards. Typing symbols on a phone means switching keyboard layouts. A 20-character random password with !@#$% might take 30+ taps including the layout switches. A six-word passphrase is just letters and one repeated separator — predictable, and mostly word completion does the work.
Transcription. You will eventually have to read a master passphrase aloud to a partner, write it on paper for a safe deposit box, or transcribe it from one device to another. Random ASCII transcribes badly. Words transcribe well.
Sticking to the random source. When you ask a human to "make up a random password" they pick patterns. When you ask them to "pick six random words" they pick summer, coffee, monday, dog, purple, pizza — categories of words a brain finds easy, which is exactly what attacker rule-sets exploit. The diceware method removes the human from the selection step. That is the whole security improvement.
The Maths, Briefly
The formula is the same as for any random password: H = L × log2(N). The difference is what L and N mean.
For a random character password, L is the character count and N is the size of the character set. For a passphrase, L is the word count and N is the size of the wordlist.
| Source | N (size) | bits per unit |
|---|---|---|
| Lowercase letters | 26 | 4.70 |
| Mixed case + digits + symbols | 95 | 6.57 |
| EFF Large word list | 7,776 | 12.92 |
| EFF Short word list | 1,296 | 10.34 |
| Common English vocabulary | ~20,000 | 14.29 |
A word from the EFF Large list contributes roughly twice the entropy of a random ASCII character. Six words ≈ twelve random ASCII characters in entropy terms, but the six words are about 32-40 characters long. That length is the cost of memorability — but it does not hurt cracking resistance, which is what most people care about.
What does 77 bits buy in practice? At a high-end offline rate of 100 billion guesses per second — realistic for unsalted MD5 or SHA-1 on a top GPU rig — the average crack time is 2^76 / 10^11 ≈ 7.5 × 10^11 seconds, or about 24,000 years. Against bcrypt at cost 12 (around 184 million hashes per second on the same hardware), the same maths gives roughly 13 million years. Against Argon2id with reference parameters, it stops being a number anyone cares about. The failure mode shifts well before brute force — it becomes phishing, malware on your device, or someone coercing you to type the phrase.
If you want the deeper attack-model breakdown — dictionary attacks, rainbow tables, GPU hashing rates against bcrypt versus Argon2 — the password entropy article covers it in detail.
Choosing a Separator (And Why It Matters Less Than You Think)
You will see passphrase tools offering separator options: space, dash, dot, underscore, random digit, none. I went down the rabbit hole on this so you do not have to.
The security difference between separators is negligible. A dash and a space contribute zero additional entropy because they are deterministic. A random digit between each word adds log2(10) ≈ 3.32 bits per separator, which is 3.32 × (wordcount - 1) extra bits. For a six-word phrase, that is about 16.6 extra bits — meaningful, but the simpler path is just to add one more word, which gets you 12.9 bits without complicating the phrase.
The real choice is usability and system compatibility:
| Separator | Pros | Cons |
|---|---|---|
- (dash) |
Universal, easy to type, no autocorrect issues | None significant |
space |
Looks like natural language | Some systems trim leading/trailing or collapse repeated spaces |
. (dot) |
Compact | Some forms treat it as a separator/special character |
_ (underscore) |
Universal | Slower to type on most keyboards (shift key) |
digit |
Adds entropy | Breaks the readability that is the whole point of passphrases |
(none) |
Shortest | Hard to read, hard to verify visually |
I default to dashes. They are universally supported, fast to type, and the phrase remains visually parsable.
Capitalisation, Numbers, and Symbols
Adding Capitalise first letter of each word (a common option in passphrase tools, including the one here) adds at most one bit per word — and only if the capitalisation is randomly chosen, not deterministic. If you always capitalise the first letter, an attacker who guesses the words also gets the capitalisation for free.
Appending a digit gives you about 3.3 bits. Appending a symbol gives 3-4 bits depending on which symbol set you draw from. Both are useful if you are forced to satisfy a complexity policy that demands at least one digit and one symbol — and they should be appended randomly, not as a memorable suffix like 123!.
My honest take: add one more word instead. Every extra random word is ~13 bits, which is more than capitalisation + a digit + a symbol combined, and the phrase stays readable. The tweaks are for compliance, not security.
Where Passphrases Belong
Passphrases shine for the secrets you must remember and type frequently. They are the wrong choice for secrets you store and forget.
Use a passphrase for:
- Password manager master key. This is the most important one. You unlock it dozens of times a day and you cannot store it elsewhere. Eight words is not overkill here.
- Full-disk encryption (LUKS, FileVault, BitLocker recovery key). You will type it at boot, possibly under stress. Memorability matters.
- SSH key passphrases. Especially on keys you use across machines and might re-add to an agent occasionally.
- GPG/PGP signing keys. Same logic as SSH.
- Wallet recovery phrases — though crypto wallets typically generate these for you using BIP-39, which is itself a passphrase scheme.
- Account recovery emails if you have to memorise the password.
Do not use a passphrase for:
- API keys, database passwords, service-to-service credentials. No human types these. Use a long random string from your secrets manager and call it done.
- Per-site passwords, if you have a manager. Let the manager generate 20-character random strings — they are stronger per character and you never type them.
- Session tokens, CSRF tokens, anything programmatic. Use a CSPRNG with binary output.
The split is human-typed versus machine-typed. Passphrases win the human-typed half; random strings win the rest.
Common Mistakes I See
Picking your own "random" words. This is the failure mode that destroys all the entropy. Roll dice, use Web Crypto, use diceware tooling — anything but your brain. Human-chosen words cluster around what you have seen recently. If you read about cats this morning, cat is in your head. Attacker dictionaries know this.
Using too few words. Four-word phrases were the XKCD example, but the comic was illustrating a principle, not making a recommendation for 2026. The 44 bits of entropy from a 4-word phrase against a 2,048-word common-vocabulary list is breakable by a determined attacker with cloud GPU access in under a year against bcrypt, and minutes against an unsalted hash. Six words is the modern minimum; seven for high-value accounts.
Using a custom or thematic wordlist. "Star Wars characters" or "Harry Potter spells" wordlists circulate online. Most have a few hundred entries, which collapses the entropy dramatically (log2(500) = 9 bits versus 12.92 for EFF Large). If the attacker can guess the theme, they will use that smaller list directly.
Storing passphrases in a notes app. A passphrase is meant to be memorised. If you cannot remember it, it is just a long password — and a long password in Apple Notes is no safer than a short one. Put it in your password manager, or memorise it.
Forced complexity rules. Many systems still demand "must include a number and special character". Append one digit and one symbol to satisfy the rule, and add an extra word to compensate. Do not chop the passphrase short to fit a 16-character limit if you can avoid the system entirely.
Predictable transformations. If your scheme is "the passphrase plus the site name at the end" then leaking one password leaks them all. Per-site passwords belong in the manager, generated randomly.
Generating Passphrases in Code
The critical requirement is the same as for random password generation: the randomness source must be a CSPRNG, and the selection must be modulo-bias-free.
PHP
<?php
declare(strict_types=1);
function generatePassphrase(array $wordlist, int $wordCount = 6, string $separator = '-'): string
{
$max = count($wordlist) - 1;
$words = array_map(
static fn (): string => $wordlist[random_int(0, $max)],
range(1, $wordCount),
);
return implode($separator, $words);
}
// EFF Large wordlist - load once, reuse
$wordlist = file('/path/to/eff_large_wordlist.txt', FILE_IGNORE_NEW_LINES);
$wordlist = array_map(
static fn (string $line): string => explode("\t", $line)[1] ?? '',
$wordlist,
);
echo generatePassphrase($wordlist, 7); // ~90 bits of entropy
random_int() (PHP 7.0+) wraps the OS CSPRNG and handles the modulo-bias problem internally. Do not roll your own with rand() % count($wordlist) — naive modulo introduces bias when the wordlist size does not evenly divide RAND_MAX.
Python
import secrets
from pathlib import Path
def generate_passphrase(wordlist: list[str], word_count: int = 6, separator: str = '-') -> str:
return separator.join(secrets.choice(wordlist) for _ in range(word_count))
# EFF Large wordlist - one word per line, or "nnnnn\tword" format
raw = Path('eff_large_wordlist.txt').read_text().splitlines()
wordlist = [line.split('\t')[1] if '\t' in line else line for line in raw if line.strip()]
print(generate_passphrase(wordlist, 7))
secrets.choice() (Python 3.6+) uses os.urandom() under the hood. Never use random.choice() for passphrases — random is a Mersenne Twister seeded from the system clock, which is not cryptographically secure.
JavaScript (Browser)
function secureRandomInt(max) {
// Rejection sampling to avoid modulo bias
const range = 0x100000000; // 2^32
const limit = range - (range % max);
const arr = new Uint32Array(1);
let val;
do {
crypto.getRandomValues(arr);
val = arr[0];
} while (val >= limit);
return val % max;
}
function generatePassphrase(wordlist, wordCount = 6, separator = '-') {
const words = [];
for (let i = 0; i < wordCount; i++) {
words.push(wordlist[secureRandomInt(wordlist.length)]);
}
return words.join(separator);
}
// Assuming wordlist is loaded from a JSON or fetched
console.log(generatePassphrase(window.EFF_LARGE_WORDLIST, 7));
Math.random() is explicitly not a CSPRNG. Use crypto.getRandomValues() in browsers and crypto.randomInt() (Node 14.10+) on the server. The rejection sampling above is required because crypto.getRandomValues() only gives uniform output across powers of two — naive modulo introduces a small but real bias when the wordlist size is not a power of two.
For full charset random password generation, the same CSPRNG principles apply — see how the browser-based password tool implements both schemes side by side.
Making It Stick
The first week with a new master passphrase is the painful part. After that, muscle memory takes over and you stop thinking about it. A few things I have found that shorten the painful week:
Build a mental image for the phrase, even if the words are unrelated. chimp.gravel.parka.unbolted.unbridle.smiling is six unconnected words; in 30 seconds I can picture a chimp in a parka unbolting a gate, gravel underfoot, smiling. The image survives where the rote letters do not.
Type it once a day on a real keyboard during the first week. Not autofill, not paste — actual keystrokes. Muscle memory consolidates faster than vocabulary memory.
Do not split it across visual lines in your head — if you start treating chimp.gravel.parka as one chunk and unbolted.unbridle.smiling as another, you remember the order of chunks but get the boundary wrong. Treat each word as its own unit.
If you genuinely cannot remember it after two weeks, generate a new one. Some word combinations simply do not stick for some brains. Faster to roll again than to spend a month forcing it.
Storage Still Matters
A strong passphrase does not save you from weak storage. If a service hashes your master passphrase as unsalted SHA-256, an attacker with the database dump can rainbow-table the high-frequency words straight out. The entropy advantage lives on the input side; the storage side decides whether the input format matters at all. The hashing algorithms guide goes through the failure modes, but the short version for 2026:
- Argon2id with sensible parameters (64 MiB memory, 3 iterations) — current OWASP recommendation
- bcrypt with cost factor 12 — proven, widely supported, still fine
- scrypt — acceptable, somewhat less common
- Never plain SHA-256, MD5, or PBKDF2-SHA-1 for new systems
For your own application code: hash passphrases the same way you would hash any password. Argon2id, sensible parameters, per-user salt. The passphrase format on the input side is invisible to the storage layer.
The Setup, End to End
If you want to act on this today:
- Generate a seven-word EFF Large passphrase — the browser tool or physical dice, dashes between words.
- Set it as the master for your password manager (Bitwarden, 1Password, KeePassXC — all handle arbitrary length).
- Let the manager generate 20-character random per-site passwords for everything else.
- Type the master manually for the first two weeks, not paste. Muscle memory consolidates faster than vocabulary memory.
- Write it once on paper. Safe deposit box, sealed envelope, or somewhere physically secure. Not cloud storage, not a notes app.
- Add a hardware key (WebAuthn, YubiKey, Titan) as the second factor on the password manager itself.
One phrase you remember, hundreds of random passwords behind it, hardware key as the gate. The phrase is the only secret you carry in your head. That is the one to make count.