Why You Hash Passwords Instead of Encrypting Them
Passwords are hashed, not encrypted — because you never need to read them back, only check them. Here is why one-way hashing, salts, and slow algorithms like bcrypt matter.
If your database stores user passwords in a form you could decrypt back to plain text, you have already lost. The whole point of password storage is that even you cannot recover the original. That is the difference between hashing and encryption, and it is why every serious system hashes.
Encryption Is Reversible — That Is the Problem
Encryption transforms data so it can be turned back into the original, given a key. That reversibility is a feature when you need the data again: TLS encrypts a message so the recipient can decrypt and read it. But it makes encryption the wrong tool for passwords.
If you encrypt passwords, the key that decrypts them has to live somewhere — in config, in a secrets manager, in application memory. An attacker who gets your database and that key can decrypt every password at once. You have turned a credential store into a single-key safe, and the password is the most valuable thing inside.
Hashing is one-way by design. A hash function takes input and produces a fixed-length digest, and there is no “un-hash” operation. The same input always yields the same digest, but you cannot run the function backward to recover the input. There is no key to steal because there is no key.
The key insight is that you never need to read a password back. When a user logs in, you do not need to know their stored password — you only need to know whether the password they just typed is the same one. So you hash the attempt and compare it to the stored hash. Match means correct; no match means wrong. You verify without ever recovering.
Why a Plain Fast Hash Is Not Enough
So you hash with SHA-256 and you are done? Not quite. General-purpose hashes like MD5 and SHA-256 are built to be fast — they are meant to digest gigabytes quickly. For passwords, speed is exactly what you do not want, because it helps the attacker more than you.
Two attacks make a plain fast hash weak:
- Rainbow tables. Attackers precompute hashes for billions of common passwords and store them in lookup tables. If you store a bare SHA-256 of
password123, it is the same digest in every database on earth, so cracking it is a single table lookup. - Brute force. Modern GPUs compute billions of fast hashes per second. If the hash is cheap, guessing through huge candidate lists is cheap too.
The fix is two ingredients. First, a salt: a unique random value generated per user and stored alongside the hash. You hash the salt together with the password, so identical passwords produce different digests. That breaks rainbow tables outright — a precomputed table is useless against a salt it never saw, and the attacker must attack each user’s hash separately.
Second, a slow, work-factored algorithm: bcrypt, scrypt, or Argon2. These are deliberately expensive, with a tunable cost parameter. You make one hash take, say, a quarter of a second — unnoticeable on a single login, but devastating for an attacker who needs to try billions of guesses. As hardware gets faster, you raise the cost factor to keep pace.
Putting It Together
A login flow with proper hashing looks like this:
Signup: hash = bcrypt(password, salt, cost) → store hash (salt + cost embedded)Login: bcrypt(attempt, stored_salt, cost) == stored_hash ?You never store the password, never decrypt anything, and never hold a key that could reverse the whole table. Even if your database leaks, an attacker faces a per-user, deliberately slow guessing problem rather than a one-key unlock. That is the entire reason the industry settled on hashing: not because it is fancier, but because the only safe password is one you yourself cannot read.
FAQ
FAQ
If I lose a hash, can I get the password back?+
Is a salt secret? Where do I store it?+
Why not just SHA-256 with a salt — is bcrypt really needed?+
Related reading
2026-06-04
ACID vs BASE: What Database Guarantees Actually Promise
ACID and BASE describe two ends of a tradeoff between strict correctness and scalable availability. Learn what each guarantee means, when each fits, and why most modern databases sit somewhere in between.
2026-06-04
Big-Endian vs Little-Endian
Byte order explained: how big-endian and little-endian lay out multi-byte numbers in memory, why network protocols pick one, and when the difference actually bites you.
2026-06-04
Big-O Notation in Plain English
Big-O describes how an algorithm's runtime or memory grows as input grows. Learn the common classes — O(1), O(log n), O(n), O(n log n), O(n^2), O(2^n) — with plain examples.
2026-06-04
CORS in Plain English: Why the Browser Blocks Your Fetch
A clear walkthrough of CORS and the same-origin policy — what an origin is, why your fetch fails, how servers opt in, and the big misconception about who CORS actually protects.
2026-06-04
Environment Variables and PATH, Explained
What environment variables actually are, why they hold config and secrets, and how PATH decides which binary runs when you type a command.
Get the best tools, weekly
One email every Friday. No spam, unsubscribe anytime.