HashTools
CourseLesson 2
Lesson 2 of 7

Why Storing Passwords as Hashes Isn't Enough

A plain SHA-256 hash of a password can be cracked in seconds. Salting fixes part of the problem. Slowness fixes the rest. Here's why you need both.

7 min read·Hands-on: MD5 Tool
How NOT to Store Passwords! — Computerphile

The first rule of password storage: never store the password itself. If your database leaks, plaintext passwords mean every account is immediately compromised — and most users reuse passwords across sites, so the damage spreads.

Storing a hash is better. Verify by hashing the login attempt and comparing. But "better" isn't the same as "good," and plain hashing has two specific weaknesses that a motivated attacker will exploit.

Weakness 1: lookup tables

Hashing is deterministic. SHA-256 of "password123" is always ef92b778..., on every machine, in every language. Attackers know this. Before your database even exists, someone has precomputed SHA-256 hashes for millions of common passwords and built a lookup table. Your breach dumps those hashes, the attacker queries the table, and accounts cracked within hours.

MD5 is worse: the tables are bigger because MD5 has been around longer and runs faster. Try pasting the MD5 hash of any common word into Google — you'll often get the original back as a top result.

Salting: the fix for lookup tables

A salt is a random value generated fresh for each password and stored alongside the hash. You hash the combination of salt + password, not the password alone.

Same password, two users, two different salts: you get two different hashes. An attacker can't build a table in advance because the salt is unpredictable. They'd need a separate table for each salt — which means a separate brute-force for each account. That scales badly.

The salt is not a secret. It's stored in plaintext in your database. The security comes from uniqueness, not secrecy.

Weakness 2: speed

Salting solves the precomputation problem. It doesn't solve the speed problem.

SHA-256 is designed to be fast. A commodity RTX 4090 can compute roughly 20 billion SHA-256 hashes per second. An attacker with your salted database and a list of 10 million common passwords still only needs 0.5ms to try all of them against a single account. A longer list of a billion passwords takes about 50ms per account. That's still fast enough to be a real problem for weak passwords.

The fundamental issue: SHA-256 was built for speed. Password verification needs slowness.

What actually belongs in password storage

Functions designed specifically for passwords: bcrypt, scrypt, and Argon2id. They're slow by design, include salting automatically, and expose a cost parameter you can increase as hardware improves. bcrypt at cost 12 takes ~250ms per hash on a modern server. That's imperceptible to a user but makes brute-forcing 10 million passwords take three months per account rather than milliseconds.

The lesson structure: use SHA-256 for integrity checking. Never use it for passwords. We cover bcrypt and Argon2 in Lesson 3 and how attackers exploit speed in Lesson 3 as well.

If you've inherited a codebase using md5($password) or sha256($password) — that's a live vulnerability. Prioritise re-hashing with bcrypt.

Try it yourself
MD5 Tool — runs entirely in your browser
Open tool →