Skip to content
RungsySign in

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?

Keep track of this

Add Authentication & Security to your map and Rungsy will schedule reviews so you actually remember it.