Storing Passwords
bcrypt/argon2, salts, and why 'we hash with SHA-256' is a breach report waiting to happen.
30 minDifficulty 2/5security · authAI-writtenWritten by a model on 16 August 2026 and not yet read by a person. Checked automatically: schema, the pedagogical rules the hand-written material is held to, and every diagram parsed for real.
Before this
Why this exists
A database breach exposes a table of `SHA-256(password)` values, and within hours, most of them are cracked — not because SHA-256 is broken, but because it's FAST, and a fast hash function lets an attacker try billions of guesses per second against a leaked hash. Password hashing needs a function that's deliberately, expensively slow, which is a completely different requirement from every other use of hashing in software.
The mental model
General-purpose hash functions (SHA-256, MD5) are optimised to be as FAST as possible — exactly the wrong property for a password hash, because speed is what lets an attacker brute-force guesses at scale. Password hashing functions (bcrypt, argon2, scrypt) are deliberately slow and TUNABLE, so as hardware gets faster, you can dial up the cost to keep brute-forcing impractical.
How it works
A salt defeats precomputed lookup tables (rainbow tables)
Without a salt, two users with the password 'password123' produce the IDENTICAL hash — an attacker can precompute a giant table mapping common passwords to their hashes once, then look up any leaked hash instantly. A random salt, unique per user and stored alongside the hash, makes every hash different even for identical passwords, so a precomputed table would need one entry per possible salt, which is computationally infeasible.
Bcrypt and argon2 have a configurable cost factor, on purpose
bcrypt's cost factor (commonly 10-12) determines how many rounds of internal computation each hash requires — doubling the cost factor roughly doubles the time to compute ONE hash. This is deliberate: as hardware gets faster over the years, increasing the cost factor for new hashes keeps the time-per-guess for an attacker roughly constant, which a fixed-speed function like SHA-256 can never do.
Argon2 additionally costs memory, not just time
A purely time-costly hash can still be attacked efficiently with specialized parallel hardware (GPUs, ASICs) that run many hash attempts simultaneously. Argon2 is designed to also require a significant amount of MEMORY per hash attempt, which is much harder and more expensive to parallelize at scale — this is why argon2 is generally recommended over bcrypt for new systems.
The hash function's own output already includes the salt and parameters
A bcrypt hash string like `$2b$12$N9qo8uLOickgx2ZMRZoMye...` encodes the algorithm version, the cost factor, the salt, AND the resulting hash all in one string — there's no need for a separate salt column, because the verification function parses all of this back out of the stored string to check a login attempt correctly.
The mechanism
When a user sets a password, the hashing function generates a random salt, runs the deliberately slow algorithm (with a configured cost factor) over the password combined with that salt, and stores the resulting encoded string — salt and parameters included — in the database. Verifying a login attempt re-runs the same algorithm using the salt and parameters extracted from the stored string, and compares the result to the stored hash, never decrypting or reversing anything, since a proper hash function is one-way by design.
What people get wrong
- Any cryptographic hash function, like SHA-256, is suitable for hashing passwords.
- SHA-256 is deliberately fast, which is exactly the wrong property for password storage — its speed is what lets an attacker with a leaked hash try billions of guesses per second, whereas a purpose-built password hash function is deliberately, tunably slow to make that infeasible. This confusion is extremely common because SHA-256 is a genuinely secure hash function for OTHER purposes (integrity checking, digital signatures) — its security properties there just don't include the slowness password hashing specifically requires.
- Encrypting a password (with a reversible algorithm) is more secure than hashing it, since you could recover it if needed.
- A password should NEVER be recoverable, by anyone, including the service storing it — hashing is deliberately one-way specifically so that even a full database breach doesn't expose the original passwords, which encryption (being reversible with the right key) doesn't guarantee. The ability to 'recover' a password is itself the vulnerability — a service that can decrypt your password could also be compromised into revealing it, whereas a properly hashed password structurally cannot be reversed even by the service itself.
- A salt needs to be kept secret, like a password, for the security benefit to work.
- A salt is stored right alongside the hash, in plain view — its purpose isn't secrecy, it's ensuring that identical passwords produce different hashes, defeating precomputed lookup tables, which works whether or not the salt itself is ever exposed. Conflating a salt with a secret leads to unnecessary complexity trying to hide it, when its actual security value comes entirely from being unique per user, not from being hidden.
When not to use it
- You're building a new system today and choosing which password hashing algorithm to use.
- argon2 (specifically argon2id) is the current best-practice recommendation, given its memory-hardness in addition to time cost — bcrypt remains acceptable and widely used, but argon2 is the stronger default for new systems.
- You need to verify a piece of data's integrity (has this file been tampered with), not authenticate a user.
- A fast, general-purpose cryptographic hash like SHA-256 is entirely appropriate here — the slowness that matters for passwords is irrelevant (even counterproductive) for integrity checking, which is a genuinely different use case.
Terms
- Salt
- — A random value, unique per password, combined with the password before hashing to ensure identical passwords produce different hashes, defeating precomputed lookup tables.
- Cost factor
- — A tunable parameter (in bcrypt, argon2) controlling how computationally expensive each hash operation is, adjustable over time as hardware gets faster.
- Rainbow table
- — A precomputed table mapping common passwords to their hashes, useful for quickly reversing an unsalted hash but defeated by per-password salting.
- Memory-hard
- — A property of algorithms like argon2 requiring significant memory per computation, making large-scale parallel attacks (using GPUs or ASICs) much more expensive.
In an interview
Why is using SHA-256 directly to hash passwords considered a security mistake, even though SHA-256 itself is a secure hash function?
- SHA-256 is deliberately fast, which is a desirable property for its intended uses (integrity checking) but a dangerous one for passwords
- speed lets an attacker with a leaked hash try enormous numbers of password guesses per second
- purpose-built password hashing functions (bcrypt, argon2) are deliberately slow and tunable specifically to prevent this
Can you recall it?
Why does a fast, secure cryptographic hash function like SHA-256 make a poor choice for storing passwords, even though it's cryptographically sound?