• Terms of Service

Learn Internet Grow

Tech Skills

  • Learn
  • Books
  • Search
  • Create
  • Measure
  • Secure
  • Share
  • Terms of Service

Salt, Slow Hashes, and the 1979 Paper That Still Governs Your Password Table

Filed Under: Secure

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.

Password salting explained — the 1979 Morris and Thompson design still holding up a modern password store

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.

The CTSS incident — a password file swapped with the message of the day and printed on every terminal

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 mechanismWhat it defeatsThe modern name for it
DES iterated 25 timesCheap per-guess costWork factor / cost parameter
12-bit salt from the real-time clockPrecomputed tables across a user populationPer-record salt
Salt applied as an E-table perturbationCommodity DES hardwareHardware asymmetry / memory hardness
Longer passwords pushed at enrolmentSmall candidate spacesLength minimums and blocklists
No timing difference on unknown usernamesUsername enumerationConstant-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.

The M-209 scheme at 1.25 milliseconds per trial replaced by DES iterated 25 times to make each guess expensive

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.”

A 12-bit salt splitting one precomputed attack table into 4,096 separate tables, destroying amortisation

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 1979 cracking experiment in which 2,831 of 3,289 collected passwords — 86% — were recovered

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.

Splitting the world-readable password file into public account metadata and a root-only shadow file

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.

SchemeOriginSalt widthCost controlHardware resistanceStatus
descrypt — crypt(3)1979, Morris & Thompson12 bitsFixed: DES ×25E-table perturbation defeated stock DES chipsBroken. 8-character truncation, 12-bit salt; retained only to verify ancient hashes
md5crypt ($1$)1994, Poul-Henning Kamp, for FreeBSD48 bitsFixed: 1,000 iterationsNoneObsolete — Kamp himself declared it no longer safe in 2012
sha256crypt / sha512crypt ($5$/$6$)2007, Ulrich Drepperup to 96 bitsTunable rounds, default 5,000None — GPU-friendlyLegacy default on older distributions; defensible only at very high round counts
bcrypt ($2b$)1999, Provos & Mazières128 bitsTunable cost factor, 2^costBlowfish’s expensive, cache-hostile key scheduleStill recommended. OWASP minimum work factor 10; hard 72-byte input limit
PBKDF2RFC 2898 (2000), now RFC 8018arbitraryTunable iteration countMinimal — cheap on GPU and ASICChoose it when FIPS validation requires it. OWASP: 600,000 iterations with HMAC-SHA-256
scrypt2009, Colin Percival; RFC 7914arbitraryN, r, pMemory-hardRecommended. OWASP minimum N=2¹⁷ (128 MiB), r=8, p=1
Argon2idPassword Hashing Competition winner, 2015; RFC 9106 (2021)128 bitsm, t, p, optional secret KMemory-hard; hybrid data-dependent/independent addressingThe default choice. RFC 9106’s first recommended option is m=2 GiB, t=1, p=4
yescryptPHC special recognition; shipped via libxcryptarbitrarytunableMemory-hardThe 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.

The password hashing lineage from crypt(3) through md5crypt, bcrypt and scrypt to Argon2id

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.

Why a fast hash like SHA-256 is the wrong tool for password storage, where deliberate slowness is the goal

1979 Recommendations Versus 2026 Practice

Lay the paper’s conclusions next to a current checklist and the overlap is close to total.

Morris & Thompson, 1979The 2026 equivalentRediscovered, or inherited?
Never store the password; store a one-way function of it (credited to Wilkes, 1968)Identical, universallyInherited
Deliberately slow the function — DES iterated 25 timesTunable cost: bcrypt’s work factor, Argon2’s t and mInherited, then generalised into a parameter
Per-record salt, 12 bits, stored in the clear beside the hashPer-record salt, 128 bits, stored in the clear beside the hashInherited — only the width changed
Perturb DES’s E-table so commodity DES hardware is uselessMemory hardness in scrypt, Argon2, yescrypt to blunt GPU/FPGA/ASICRediscovered, with a better mechanism
Push users to longer passwords; measure how badly they chooseSP 800-63B-4: length minimums, blocklists, no composition rules, no forced rotationRediscovered, after thirty years of doing the opposite
Remove the timing difference on unknown usernamesConstant-time comparison; hash a dummy record for unknown usersRediscovered repeatedly, in nearly every framework
Salt also conceals password reuse across systemsThe core of credential-stuffing analysisInherited, 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.

A 1979 result rediscovered decades later — the same conclusions restated in each new generation of practice

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.

Newsletter

Tags

Agentic AI Agentic Development AI AI Agents AI Coding Agents Artificial intelligence Backlinks Blockchain Bounded Context Browser ChatGPT Plugins Claude Code CLPR Context Engineering Cross-Chain Cryptocurrency Developer Tools Distributed Systems DLT Domain-Driven Design Email Google Search HBAR Hedera Hedera Hashgraph Internet LLM Machine Learning MCP Microservices Model Context Protocol News NLP Orchestration Photoshop Productivity Saga Pattern SEO Smart Contracts Software Architecture software engineering Twitter Typescript Web3 Writing

Recent Articles

  • QUIC and HTTP/3: The Protocol That Won Is Being Routed Around October 11, 2026
  • The Mark I Perceptron: The Machine That Learned to See October 11, 2026
  • GraphRAG vs Vector RAG: Knowledge Graphs for AI, Explained October 11, 2026
  • Cardano Tokenomics: How ADA’s Supply Actually Works October 10, 2026
  • Transformer Encoder vs Decoder: How to Read the Diagram October 10, 2026
  • Nobody Checks the Verified Badge: The Real State of Git Commit Signing October 10, 2026

Copyright © 2026 · News Pro Theme On Genesis Framework · WordPress · Log in

Content on this site is produced with the assistance of Large Language Models and edited by a human. It is intended for educational purposes — facts may occasionally be incorrect. If you spot an error, please email Larionovdmitri@learninternetgrow.com.

Affiliate Disclosure: This site contains affiliate links to Amazon and other partners. If you purchase through these links, LearnInternetGrow.com may earn a commission at no additional cost to you. We only recommend products we genuinely believe in.

Privacy Policy · Terms of Service