Every password-storage checklist says roughly four things: never store the plaintext, use a deliberately slow hash, give every record its own salt, and raise the cost parameter as hardware gets faster. Here is the uncomfortable part — password salting explained as a modern best practice is a rediscovery, not an innovation. All four rules were argued, with measurements and a controlled experiment, across four pages of Communications of the ACM in November 1979: Robert Morris and Ken Thompson’s Password Security: A Case History, volume 22, issue 11, pages 594–597.
That paper contains a per-record salt, a deliberately expensive hash, an explicit hardware-asymmetry defence, a cracking experiment that recovered 86% of a real user population, and an honest account of what its authors got wrong. Nearly half a century later the current guidance says the same things with bigger numbers. The interesting question is not what changed — the parameters changed and almost nothing else did. The interesting question is why an industry that had the answer in print in 1979 spent forty years relearning it, one breach at a time.

The Failure That Made Hashing Mandatory — and It Was Not a Unix Failure
Morris and Thompson open not with Unix but with CTSS at MIT in the early 1960s, and the anecdote matters because it is so mundane. Two administrators were editing files at the same time. An editor bug swapped their temporary files. One was editing the system’s message of the day; the other was editing the password file. The password file was printed on every terminal as the login banner.
No cryptanalysis, no adversary, no exploit — a race condition in a text editor. The generalisable claim underneath is the durable one: a file protected by the access-control system is still readable by whoever holds the backup tape, the crash dump, or the operator console. Permission bits protect a secret only against the threat model you happened to enumerate.
The response was to stop storing recoverable passwords at all. Two points of credit cut against the way this story usually gets told. First, one-way encryption of passwords was not a Unix invention — Morris and Thompson attribute it to Maurice Wilkes, Time-Sharing Computer Systems (1968). Second, crypt(3) does not encrypt the password. The password is the key; a constant is the plaintext; the ciphertext is what gets stored. That inversion is the whole trick: there is no decryption operation to attack, because nothing anyone cares about was ever encrypted. The vocabulary drift is not harmless — describing storage as “encryption” licences a reversible implementation, and reversible implementations are what keep turning up in breach post-mortems.

What Morris and Thompson Actually Built
Their first scheme used the password as a key to a software simulation of an M-209 cipher machine encrypting a constant. It worked, and it was measurably fatal: 1.25 milliseconds per trial on a PDP-11/70. Do the arithmetic they did. Every five-letter lower-case string is 26⁵ ≈ 11.9 million candidates — about four hours of machine time. A 250,000-word dictionary is roughly five minutes. The scheme was not broken cryptographically; it was broken economically, and that was the only kind of break that mattered.
The replacement has three moving parts, and it is the design everyone still uses.
Slowness on purpose. The new function keyed DES with the first eight characters of the password, encrypted a zero constant, and repeated the operation 25 times. Twenty-five iterations is not a security parameter in any modern sense; it is a throughput ceiling, chosen so a single login check stays imperceptible to a human while an attacker’s enumeration budget collapses by the same factor. Deliberate cost is not a 2010s idea that arrived with bcrypt. It shipped in 1979, in the standard C library.
Salt. Twelve bits, taken from the real-time clock when the password was set, stored in the clear alongside the hash as two characters of the encoded string. Twelve bits is 4,096 values, and that number is the point: an attacker who wants a precomputed table covering a population of accounts must build 4,096 tables instead of one. The work of attacking a collection stops amortising.
Hardware asymmetry. This is the part most retellings drop, and it is the most interesting decision in the paper. The salt is not concatenated with the password — it selects one of 4,096 perturbations of DES’s E-table, the expansion permutation inside the round function. The resulting function is therefore not DES, which meant that in 1979, as DES was being cast into commercial silicon, none of that hardware could be pointed at a Unix password file. One mechanism, two effects: precomputation dies, and the attacker’s fast path dies with it.
| The 1979 mechanism | What it defeats | The modern name for it |
|---|---|---|
| DES iterated 25 times | Cheap per-guess cost | Work factor / cost parameter |
| 12-bit salt from the real-time clock | Precomputed tables across a user population | Per-record salt |
| Salt applied as an E-table perturbation | Commodity DES hardware | Hardware asymmetry / memory hardness |
| Longer passwords pushed at enrolment | Small candidate spaces | Length minimums and blocklists |
| No timing difference on unknown usernames | Username enumeration | Constant-time comparison, dummy hashing |
They also found and closed a timing side channel that leaked whether a username existed — in 1979, in four pages, alongside everything else.

Password Salting Explained: Why Twelve Bits Was the Real Invention
Strip away the DES specifics and salt is one idea: make the attacker’s work non-amortisable across targets. An unsalted hash is a pure function of the password, so one table serves every account on earth that chose that password. Add a per-record random value and the function becomes a family of functions, one per record. The attacker’s cost stops scaling with the size of the precomputation and starts scaling with the number of accounts.
Three consequences follow, and they are the ones engineers most often muddle.
Salt is not a secret. It is stored in the clear, next to the hash, usually inside the same encoded string. If it had to be secret it would be a key, and you would have a key-management problem instead of a storage problem. Its security property is uniqueness, not confidentiality — which is why it must come from a CSPRNG rather than a username, an email address, or a row ID. Those are not unique across systems, and a salt that repeats across systems partially amortises again.
Rainbow tables are a solved problem, and salt is what solved them. A rainbow table — Philippe Oechslin’s 2003 refinement of Hellman’s time-memory trade-off — is precomputation with a chain structure that trades storage for lookup work. Against a 12-bit salt it needs 4,096 times the storage; against the 128-bit salts bcrypt and Argon2 use, 2¹²⁸ tables, which is not a large engineering problem but an impossible one. Rainbow tables did not fall out of fashion, they were designed out of existence, and when one still works it is because someone shipped an unsalted hash.
Salt does nothing about per-guess cost, and cost does nothing about amortisation. These are orthogonal defences and you need both. A salted-but-fast hash gives an attacker who holds your table a cheap attack against each account individually; a slow-but-unsalted hash gives them one expensive precomputation that serves every account at once. Morris and Thompson shipped both in the same function.
That leaves pepper, constantly confused with salt and genuinely different: a single site-wide secret mixed into every hash and stored somewhere the database is not — an HSM, a KMS, the application environment. Its threat model is narrow. An attacker who takes the database but not the application secret gets hashes they cannot attack at all; against full host compromise it does nothing, and it adds a rotation problem salt does not have. Argon2 standardises it as the optional K secret parameter; the OWASP Password Storage Cheat Sheet, read 13 August 2026, reduces the rule to one sentence — a pepper “should not be stored along with the generated hash.”

The 86% Result: Human Choice Was Already the Weak Link
The paper’s most quoted number comes from an experiment the authors ran on their own users. Of 3,289 collected passwords, 2,831 fell — 86% — to short-string enumeration plus dictionaries. The breakdown is a period document in itself: 15 single-character passwords, 72 of two characters, 464 of three, 477 four-character alphanumeric strings, 706 five-letter same-case strings, 605 six-letter lower-case strings, and 492 hits from dictionaries and name lists. Dennis Ritchie’s smaller run, in On the Security of UNIX, recovered 60 of 67 passwords gathered from ten installations in about twelve hours.
Two halves of that result aged in opposite directions. The distribution has barely moved; every large password-corpus study since has found the same long tail of short, dictionary-derived, name-derived choices. The response went wrong for thirty years. Morris and Thompson recommended pushing users toward longer passwords and rejecting obviously weak ones at enrolment; what the industry built instead was composition rules — one upper, one digit, one symbol, rotate every 90 days — which shrank the effective candidate space by making everyone’s transformation predictable. NIST’s Digital Identity Guidelines, SP 800-63B-4, published 31 July 2025, formally ends that detour: an 8-character minimum with 15 recommended, support for at least 64 characters, no composition rules, no mandatory rotation, and a required check against a blocklist of known-compromised values. Blocklist plus length is the 1979 recommendation with a better corpus behind it.
The other 1979 echo is the attack path. The Morris worm of November 1988 carried a small internal dictionary and tried it against the hashes in /etc/passwd — the exact attack the paper had described, against the exact file it had warned was the wrong place to keep hashes. Eugene Spafford’s analysis puts the reach at an estimated 3,000–6,000 machines, 5–10%, over a guessed denominator of roughly 60,000 hosts. Nobody counted; the range is the honest figure.

The Real Bug Was the File, Not the Maths
The failure that actually mattered was not cryptographic. /etc/passwd was world-readable because it carried the username-to-uid/gid mapping every program needed, and the hashes sat in the same file. Every unprivileged user on the machine therefore held a complete copy of every hash on the system and could attack them offline, at leisure, with no failed-login counter to trip and no audit record to leave. The cryptography was ahead of its time; the file layout handed the attacker the ciphertext for free. That is a recurring shape — a strong primitive rendered irrelevant by an operational decision made for an unrelated reason.
The fix — public account metadata in /etc/passwd, hashes in a root-only /etc/shadow — took the better part of a decade to become normal. System V Release 3.2 shipped shadow passwords in 1988; 4.3BSD-Reno followed in 1990; on free Unix the work was Julianne Haugh’s Shadow Password Suite, written after a break-in, first targeted at SCO Xenix, presented at USENIX Security in 1992, and eventually the shadow-utils package every Linux distribution still ships.
The modern restatement writes itself. Hashes in a database a read-only reporting replica also carries; in a nightly dump sitting in object storage under a permissive bucket policy; in a debug log line, an exception payload, an analytics event. The 2026 version of the /etc/passwd bug is not a world-readable file — it is every copy of the table you did not think of as a copy.

From crypt(3) to Argon2id: Forty-Seven Years of One Argument
The lineage from 1979 to now is a single argument continued with better tools: keep raising the attacker’s cost, and keep denying them whatever hardware advantage they have acquired since the last round.
| Scheme | Origin | Salt width | Cost control | Hardware resistance | Status |
|---|---|---|---|---|---|
descrypt — crypt(3) | 1979, Morris & Thompson | 12 bits | Fixed: DES ×25 | E-table perturbation defeated stock DES chips | Broken. 8-character truncation, 12-bit salt; retained only to verify ancient hashes |
md5crypt ($1$) | 1994, Poul-Henning Kamp, for FreeBSD | 48 bits | Fixed: 1,000 iterations | None | Obsolete — Kamp himself declared it no longer safe in 2012 |
sha256crypt / sha512crypt ($5$/$6$) | 2007, Ulrich Drepper | up to 96 bits | Tunable rounds, default 5,000 | None — GPU-friendly | Legacy default on older distributions; defensible only at very high round counts |
bcrypt ($2b$) | 1999, Provos & Mazières | 128 bits | Tunable cost factor, 2^cost | Blowfish’s expensive, cache-hostile key schedule | Still recommended. OWASP minimum work factor 10; hard 72-byte input limit |
PBKDF2 | RFC 2898 (2000), now RFC 8018 | arbitrary | Tunable iteration count | Minimal — cheap on GPU and ASIC | Choose it when FIPS validation requires it. OWASP: 600,000 iterations with HMAC-SHA-256 |
scrypt | 2009, Colin Percival; RFC 7914 | arbitrary | N, r, p | Memory-hard | Recommended. OWASP minimum N=2¹⁷ (128 MiB), r=8, p=1 |
Argon2id | Password Hashing Competition winner, 2015; RFC 9106 (2021) | 128 bits | m, t, p, optional secret K | Memory-hard; hybrid data-dependent/independent addressing | The default choice. RFC 9106’s first recommended option is m=2 GiB, t=1, p=4 |
yescrypt | PHC special recognition; shipped via libxcrypt | arbitrary | tunable | Memory-hard | The crypt(3) method several current Linux distributions default to for new hashes |
Two rows deserve a sentence each. bcrypt is where future-adaptability gets named: Niels Provos and David Mazières called their 1999 USENIX paper A Future-Adaptable Password Scheme, and the contribution is not the Blowfish key schedule but the exposed cost factor. Morris and Thompson’s 25 iterations were a constant baked into a library; bcrypt made the constant a parameter, which is what let one scheme survive twenty-five years of hardware progress without a format change. Argon2id answers the threat the E-table hack answered, one hardware generation later: GPUs, FPGAs and ASICs are cheap at parallel compute and expensive at parallel memory, so Argon2 charges the attacker in RAM.
On Linux the whole family now lives behind one library: libxcrypt implements everything from descrypt through yescrypt and ships an --enable-hashes=strong build option that, in its own words, “enables only the hash functions that are strong enough to be safe for newly hashed passwords.” The 1979 function is still in there. It is simply no longer in the set marked strong.

Why “Just SHA-256 It” Is Wrong
This is the most common way a competent engineer builds a bad password table, and the reasoning behind it is almost right. SHA-256 is a serious cryptographic hash: preimage-resistant, collision-resistant, standardised, ubiquitous, fast.
Fast is the defect. SHA-256 was designed for throughput — it must run at line rate over multi-gigabyte files, and it exists in dedicated CPU instructions and in enormous quantities of purpose-built silicon. A commodity GPU evaluates it billions of times per second. Every property that makes it an excellent integrity primitive makes it a hostile password primitive, because the design goal of a password hash is the exact inverse: be as slow as the defender can tolerate.
Three follow-on errors are worth naming. Salting a fast hash is not enough — salted SHA-256 kills precomputation and leaves per-guess cost at nanoseconds, and against the 86%-crackable distribution the 1979 paper documented, an attacker needs no table, only a dictionary and an afternoon. Iterating it yourself is not enough either — “SHA-256 in a loop ten thousand times” is a home-made PBKDF2 without the analysis, without the standardised encoding, and, decisively, without memory hardness; the attacker’s GPU runs your loop too, at their parallelism rather than yours. HMAC-SHA-256 is not a password hash — it is a MAC, fast by design, and using it for storage inherits every problem above plus a key to manage.
Fast hashes are exactly right for integrity checking, content addressing, deduplication, signatures and HMAC. “Cryptographic hash” is not a single job description, and a fast one in a password column is a category error dressed as a reasonable default.

1979 Recommendations Versus 2026 Practice
Lay the paper’s conclusions next to a current checklist and the overlap is close to total.
| Morris & Thompson, 1979 | The 2026 equivalent | Rediscovered, or inherited? |
|---|---|---|
| Never store the password; store a one-way function of it (credited to Wilkes, 1968) | Identical, universally | Inherited |
| Deliberately slow the function — DES iterated 25 times | Tunable cost: bcrypt’s work factor, Argon2’s t and m | Inherited, then generalised into a parameter |
| Per-record salt, 12 bits, stored in the clear beside the hash | Per-record salt, 128 bits, stored in the clear beside the hash | Inherited — only the width changed |
| Perturb DES’s E-table so commodity DES hardware is useless | Memory hardness in scrypt, Argon2, yescrypt to blunt GPU/FPGA/ASIC | Rediscovered, with a better mechanism |
| Push users to longer passwords; measure how badly they choose | SP 800-63B-4: length minimums, blocklists, no composition rules, no forced rotation | Rediscovered, after thirty years of doing the opposite |
| Remove the timing difference on unknown usernames | Constant-time comparison; hash a dummy record for unknown users | Rediscovered repeatedly, in nearly every framework |
| Salt also conceals password reuse across systems | The core of credential-stuffing analysis | Inherited, almost never credited |
So why did it take forty years? Three mechanisms, none of them about intelligence.
The knowledge lived in a platform primitive, and the platform changed. Every Unix program that authenticated a user got salting and iteration free from crypt(3), without any developer understanding either. When applications moved to the web, credentials moved into application databases and crypt(3) left the path — so a generation of frameworks re-implemented password storage from first principles and inherited none of the defaults, because the defaults had never been in the application layer to begin with. Safe behaviour that lives only in a platform does not survive a platform migration.
The failure is silent, and the primary source was expensive. A bad password table produces no symptom until the day it leaks, and the cost then falls mostly on users rather than on the team that wrote it — no feedback loop, no learning without an incident. Meanwhile the paper sat behind a subscription for most of the intervening decades and was cited far more often than read, and nearly everything lost in transmission, including the E-table reasoning and the timing side channel, lives in the parts a citation does not carry.
The vocabulary actively misled. “Encrypt the password” survived in job descriptions, compliance language and framework documentation long after crypt(3) demonstrated the password is a key rather than a plaintext. Wrong words produce wrong architectures, reliably.

The Lesson Worth Taking Past Passwords
The transferable claim is not “salt your hashes.” It is that the field’s foundational papers usually already contain the answer, and that most things sold as new best practice are rediscoveries whose interesting property is the propagation failure, not the insight. Saltzer and Schroeder’s 1975 protection principles — least privilege, fail-safe defaults, economy of mechanism, complete mediation — sit on every current cloud-security checklist under different names. When you meet a mature problem, read the earliest serious paper on it before this year’s blog posts: the paper usually tells you which modern practices are load-bearing and which are cargo.
The specific idea generalises well past password storage. Make the attacker’s work non-amortisable. That is what salt does, and it is the same shape as ASLR randomising per process, per-tenant key derivation making one stolen key useless against the next tenant, and per-installation identifiers defeating cross-device correlation. The 1979 insight was never really about passwords — it was that an attacker’s economics collapse when the cost of attacking n targets stops being the cost of attacking one.
❓ Frequently Asked Questions
Does a salt need to be kept secret?
No. A salt is stored in the clear, normally inside the same encoded hash string, and the scheme assumes the attacker has it. Its property is uniqueness, not confidentiality — it exists so precomputation cannot be shared across records. A secret mixed into every hash is a pepper: a different mechanism, with a different threat model, kept outside the database.
How long should a salt be?
Use your library’s default — 128 bits from a cryptographically secure random number generator, which is what bcrypt and Argon2 both use. Never derive one from a username, email address or row identifier: those repeat across systems, and a repeating salt partially restores the amortisation that salt exists to destroy. The 12 bits of 1979 was sized against the storage costs of 1979.
Why is SHA-256 wrong for passwords if it is a secure hash?
Because password storage is the one application where speed is the vulnerability. SHA-256 is built for throughput and runs billions of times per second on commodity GPUs, which is exactly what an offline guessing attack needs. Salting it removes precomputation but leaves per-guess cost at nanoseconds. Use a tunable, memory-hard scheme — Argon2id, scrypt, bcrypt or yescrypt — and keep SHA-256 for integrity, content addressing and HMAC.
Are rainbow tables still a threat?
Only against unsalted hashes, which is why they have largely disappeared as a practical concern. A rainbow table is precomputation, and a per-record salt multiplies the number of tables required by the number of possible salts. At 128 bits that is 2¹²⁸ tables — not expensive, but impossible. Any modern report of a successful rainbow-table attack is a report that someone shipped a hash with no salt.
What should a new system use in 2026?
Argon2id, through a maintained library rather than a hand-rolled implementation, tuned to the largest cost your verification servers can absorb at peak. RFC 9106’s first recommended configuration is m=2 GiB, t=1, p=4; OWASP’s cheat sheet, read 13 August 2026, offers lighter server-side trades starting at m=19 MiB, t=2, p=1. bcrypt at a work factor of at least 10 remains defensible; PBKDF2 is right only where FIPS validation forces it.
Was the Robert Morris of the 1979 paper the same Robert Morris who wrote the 1988 worm?
No — they were father and son. Robert Morris Sr. co-wrote Password Security: A Case History with Ken Thompson and later worked as a chief scientist at the NSA. Robert Tappan Morris, his son, was the Cornell graduate student who released the November 1988 internet worm. The relationship is a matter of record; there is no evidence of involvement by the father, and none should be inferred.
📚 Further Reading
- The Five Best Password Managers On The Market — the user-side half of the problem: if the 86% distribution is the weak link, a generator is the fix.
- Passkey vs Password Manager: Do You Need Both? — where the shared-secret model gets replaced rather than hardened.
- Your Face Is a Password You Can’t Change — the salt argument applied to biometrics, where per-target randomisation is exactly what you cannot do.
- Did Your Email Get Hacked Due to a Data Breach? — what a leaked password table looks like from the other end.
- Encrypt and Secure Your Data — the adjacent primitive, and a useful contrast: encryption is reversible by design, which is why it is the wrong tool for a password column.
📚 Recommended Resources
Disclosure: some links below are Amazon affiliate links. If you buy through them, I earn a small commission — at no extra cost to you — which helps keep this site running.
Start with the free primary sources. The paper this article is built on, Morris and Thompson’s Password Security: A Case History, is four pages and worth reading end to end — the ACM listing is canonical, and a full-text copy is mirrored at the University of Bologna. For current parameters rather than 1979 ones, RFC 9106 and the OWASP Password Storage Cheat Sheet are free, maintained, and enough to configure a real system. The original Unix sources that contain crypt(3) are free from the Unix Heritage Society under the 24 January 2002 Caldera licence, and POSIX.1-2024 is readable online at no cost. The books below are for going deeper, not for getting started.
Practical UNIX & Internet Security (3rd ed.)
Simson Garfinkel, Gene Spafford & Alan Schwartz · Book
The standard reference on the model this article dissects, co-written by the author of the definitive Morris worm analysis cited above. Its treatment of the password file, shadow passwords, and crypt(3) is the fullest book-length account of the material here. Note the date — 2003 — and read the cryptographic parameter advice as history rather than configuration; the architecture and threat modelling have aged far better than the numbers.
The Linux Programming Interface
Michael Kerrisk · Book
The best explanation of what actually sits behind /etc/passwd, /etc/shadow, and the account-database calls on a modern Linux system, written by the long-time maintainer of the man-pages project. If you want to know precisely what a login path touches — rather than what the abstraction claims — this is the chapter to read.
The Art of Software Security Assessment
Mark Dowd, John McDonald & Justin Schuh · Book
The systematic treatment of the failure mode this article keeps returning to: a sound primitive undone by the code and the operational layout around it. Its auditing method — follow the data to every place it is copied, logged, or serialised — is exactly the discipline that finds the 2026 equivalents of the world-readable password file.
Advanced Programming in the UNIX Environment (3rd ed.)
W. Richard Stevens & Stephen A. Rago · Book
The systems-programming reference for everything surrounding authentication on a Unix system — process credentials, the user database interfaces, and the privilege transitions a login path performs. Pair it with Kerrisk: Stevens and Rago cover the portable POSIX surface, Kerrisk covers what Linux actually does.