Security
Password Hashing
A database breach that hands over plaintext passwords compromises every user instantly. The fix isn't just hashing them, it's hashing them with something deliberately slow.
SecurityIntermediate6 min readPrerequisites: Encryption & TLS
After this lesson, you should be able to:
- Explain why a fast, general-purpose hash is actually dangerous for password storage, the speed that helps elsewhere makes stolen hashes cheap to crack.
- Describe what a deliberately slow, salted algorithm buys: fewer guesses per second, and no shared precomputed table across users.
- Distinguish password hashing, never meant to be reversed, from encryption, meant to be decrypted by whoever holds the key.
- Recognize that 'we hash passwords,' without naming a specific slow algorithm, is an incomplete answer.
At a Glance
- Storing passwords in plaintext, or encrypted with a key the attacker also gets, means a database breach instantly compromises every user's password, no cracking required.
- Hashing a password stores something one-way: the server never needs the original password back, only needs to check that a new attempt hashes to the same value.
- The hard part isn't hashing, it's that a fast hash, the exact property that makes a hash useful elsewhere, is precisely wrong here, speed is what lets an attacker try billions of guesses a second against a stolen hash. The fix is a deliberately slow, salted algorithm.
01 The Problem
A startup stores user passwords in plaintext in the users table, "we'll add hashing later," or encrypts the column with a key that lives in the same config file as the application itself. A database backup leaks, or the database gets breached directly. Every user's actual password is immediately readable, no cracking, no guessing, just reading a column. Because most people reuse passwords across sites, this breach doesn't stop at this one app. It hands over working credentials for users' email, banking, and everything else they reused that password on.
The fix everyone reaches for is hashing: store SHA256(password) instead of the password itself. The database now holds something that doesn't directly reveal the password, and checking a login attempt is just hashing the submitted attempt and comparing. This looks solved. It isn't. SHA-256 is fast, deliberately, that's the entire design goal of a general-purpose hash function, and an attacker who steals a table of SHA-256 password hashes can compute billions of candidate hashes a second on ordinary hardware, running through every common password and every password from every other published breach until enough of them match. A fast hash on stolen data isn't much slower to crack than no hash at all, for the passwords that actually matter, the ones people chose.
Password storage needs a hash that's one-way, can't recover the password from the stored value directly, and deliberately slow, expensive enough per guess that trying billions of them isn't practical. Those are two separate properties, and a general-purpose hash function like SHA-256 was never designed to trade speed away in that direction.
02 What It Is
Password hashing stores a one-way transformation of a password, never the password itself, so the server can verify a login attempt without ever needing to store or recover the original. The server hashes the submitted attempt and compares it to the stored hash. Matching means the same input, without either side ever needing the plaintext to persist anywhere.
A general-purpose hash, SHA-256, MD5, is designed to be fast, because its usual job is checksumming, verifying a file wasn't corrupted, computing a lookup key, tasks where speed is exactly what's wanted. That same speed is a liability the moment the hash's input is something an attacker wants to guess, because fast means cheap to try billions of times.
A password hashing algorithm, bcrypt, scrypt, argon2, is deliberately slow, configurably so, tunable to take a meaningful fraction of a second per hash even on modern hardware, and salted: a random value unique to each password gets mixed in before hashing, so two users with the same password get completely different stored hashes, and an attacker can't precompute one table of common-password hashes and check it against every row at once, they have to redo the work per row.
The hard part isn't implementing bcrypt or argon2, that's a single library call. It's recognizing that "hash the password" isn't a complete answer on its own, which hash and how slow it is matter enormously, and the property that makes a hash function good for its most common uses, speed, is exactly what makes it bad for this one. A team that hashes passwords with SHA-256 and calls it done has technically hashed the password and still built something that falls apart the moment it's breached, just more slowly than plaintext.
In Plain Terms
A lock that takes one second to pick and a lock that takes a full day to pick both keep a casual passerby out equally well. The difference only shows up the moment someone's actually trying every key on a ring of a million, when the one-second lock is open before lunch and the day-long lock still isn't open by the time anyone gives up. Speed nobody notices under normal use is exactly what matters once someone's trying millions of attempts on purpose.
03 How It Works
Rendering diagram…
Rendering diagram…
04 Trade-offs
| Fast hash (SHA-256, MD5) | Slow, salted hash (bcrypt, argon2) | |
|---|---|---|
| Speed per guess | Microseconds; billions of attempts per second on stolen hashes | Deliberately slow, tunable, often tens to hundreds of milliseconds per attempt |
| Salting | Not built in; identical passwords produce identical hashes unless added manually | Built in; each password gets a unique salt, identical passwords produce different hashes |
| Designed for | Checksums, lookups, integrity checks, anywhere speed is the goal | Exactly this: making a stolen hash expensive to attack at scale |
| What a breach exposes | Effectively the password itself, for anything common or previously leaked, cracked within hours | A hash expensive enough per guess to make cracking common passwords impractical at scale |
Watch out
It's tempting to hear "we hash passwords" and consider the storage problem solved, hashing sounds like the whole answer. Which hash, and how slow it is, is the actual answer. A password column full of SHA-256 hashes is not meaningfully safer than plaintext for the passwords people actually pick, dictionary words, common patterns, anything already in a previous breach's wordlist, because the speed that makes SHA-256 useful everywhere else is exactly what makes those guesses cheap to try at scale. "We hash passwords," without naming bcrypt, scrypt, or argon2 specifically, is a half-finished sentence.
05 Where You'll Actually See This
Neither case study has user accounts or passwords at all, both trust a single caller directly. The moment either needed actual user login, a merchant's dashboard account, say, this is the first decision that has to be made correctly, evaluated once at signup and leaned on for the life of every account after.
Encryption and TLS covers a mechanism that looks similar on the surface and solves a genuinely different problem. Encrypted data is meant to be decrypted back, by whoever holds the key, a credit card number eventually needs to be recovered to charge it again. A password hash is never meant to be reversed by anyone, including the server that stored it. Encrypting a password column instead of hashing it means whoever has the key, the application, an attacker who steals both the database and the key, can recover every password directly, which defeats the purpose entirely. The two get conflated because both turn readable data into something unreadable, but one is designed to come back, and one never is.
How to reason about it
When asked how you'd store passwords, don't just say "we hash them." Name the specific algorithm, bcrypt, scrypt, or argon2, and say why it has to be slow, not fast. An answer that would apply equally to hashing a file for a checksum has described a hash, not a password hashing algorithm, and those solve different problems even though they're both technically hashing.
06 Quick Check
Two users at the same company happen to pick the exact same password. In a system using bcrypt with per-password salting, what do their two stored password hashes look like?