Why not just SHA
SHA is fast, so attackers can reverse huge numbers of passwords instantly with rainbow tables or dictionaries. A single shared salt only blocks rainbow tables, not targeted brute force.
The right approach
- A unique salt per user: random, stored alongside the hash; it need not be secret but must be unique;
- Use a slow hash: bcrypt / scrypt / Argon2 are deliberately slow and tunable to slow down brute force;
- Raise the work factor over time as hardware speeds up, but not so high it hurts login latency.
After a leak
- Force a reset: tell users to change passwords, don't just suggest it;
- Add verification: require a second factor for suspicious logins;
- Never email the password: "forgot password" should send a one-time reset link, not the original (which would mean you stored it in plaintext).
Try it
Strong passwords and hashing: Password generator, Hashing.
Real-world cases: three password storage incidents
- Storing passwords with MD5: one dump and rainbow tables recover them in bulk. Use slow hashes such as bcrypt / scrypt / Argon2 with an adequate work factor.
- One global salt: with every password sharing a salt, identical passwords still produce identical digests and match in one pass. Salt must be unique and random per record.
- Treating a hash as encryption: expecting to "decrypt" a password for third-party sign-in only leads to forced resets. Hashing is one-way; if you need reversibility, use encryption and manage keys properly.
FAQ
Does the salt need to be secret? No — store it alongside the digest; its job is making identical passwords differ. How do I pick a work factor? Target roughly 100–300ms per verification and raise it as hardware improves. Should I encrypt before hashing? Optional, and only with solid key management; a well-parameterised Argon2 usually suffices. How do I upgrade the algorithm smoothly? On a successful login, verify with the old algorithm then re-store the digest with the new one.
Migration and review: make algorithm upgrades routine
Most password storage is not designed once but replaced gradually over years, so you need a mechanism that keeps working, or the algorithm silently weakens over time.
- Identify old formats: store an algorithm and parameter prefix alongside the digest so verification can select the right algorithm rather than guessing. Inventory legacy rows first to separate plaintext from weak hashes.
- Upgrade silently on login: when an old hash verifies, immediately recompute and overwrite with the new algorithm. Users notice nothing and nobody is forced to reset — but instrument the progress so two formats do not coexist indefinitely.
- Force upgrades in bulk: for accounts that never log in, require a reset on next sign-in using a risk policy, or set a hard deadline and downgrade accounts that miss it.
- Review parameters: keep the work factor in configuration and re-evaluate verification time against current hardware at each release. Tens of milliseconds means it is too weak; over half a second and users feel the slowdown.
- Ban plaintext and reversible storage: "encrypting so we can recover the password" should be explicitly forbidden. Recoverable means leakable, and losing key and ciphertext together is far worse than a hash.
Watch the adjacent surfaces too: reset tokens must be single-use and short-lived, support workflows must not bypass verification, and administrators must never be able to read passwords — these are breached more often than the algorithm itself.
Related risks beyond the algorithm
Credential stuffing reuses passwords leaked elsewhere, so pairing the hash with login rate limiting, anomaly detection and multi-factor authentication matters. Should one measure be bypassed, the rest of the chain still holds.