Why do we hash passwords instead of encrypting them, and what makes a password hash different from SHA-256?
Encryption is reversible, so a key compromise exposes every password at once; hashing has no key to steal. A password hash additionally has to be deliberately slow and memory-hard, because the attacker's advantage is throughput and a general-purpose hash like SHA-256 lets them try billions of candidates per second.
What the interviewer is scoring
- Whether the candidate frames the difference as reversibility and key custody rather than strength
- That a salt is explained as defeating precomputation, not as making one hash harder
- Does the candidate know why a fast cryptographic hash is the wrong tool here
- Whether they can say what a work factor is and how it should be chosen
- That comparison timing and the enumeration side channel are considered
Answer
Because there is nothing you need to get back
Encryption exists to let you recover the plaintext. That is its purpose, and for a password it is precisely what you do not want. Verifying a login does not require knowing the password — only whether the one presented matches the one registered — and a one-way function answers that question completely.
Encrypting instead means a key exists, and a key must live somewhere: in a config file, an environment variable, a key-management service the application can reach. Whatever protects it, the application can decrypt, so an attacker with application-level access can decrypt too, and one compromise yields every password in the database in plaintext. Hashing has no equivalent single point of failure, because there is no secret whose theft undoes the protection.
The consequence is the one users notice. A site that can email you your existing password is telling you it stored something reversible, which is a defect regardless of how well the key is guarded.
Why not SHA-256
Having established one-way, the intuition is to reach for a strong cryptographic hash. SHA-256 is an excellent hash — collision resistant, preimage resistant, no practical weaknesses. It is also a bad password hash, for a reason that has nothing to do with its cryptographic strength.
It is fast, by design. Hashing is on the critical path of file integrity, digital signatures and blockchain consensus, so enormous effort has gone into making it quick, and hardware happily computes it in parallel. Commodity GPUs perform SHA-256 at billions of hashes per second.
Now consider what an attacker with a stolen database does. They do not attack the hash function; they guess passwords and hash the guesses. Human-chosen passwords come from a distribution so skewed that a few million candidates cover a large fraction of any real user base. At billions of attempts per second, working through that list takes seconds. The hash function did nothing wrong — it was fast, and speed is the attacker's entire advantage.
The correct tool is a hash deliberately made slow. bcrypt, scrypt and Argon2 take a work factor that controls how much computation each hash costs. Tuned so a single verification takes on the order of a quarter of a second, the attacker's rate falls from billions per second to a handful, and a wordlist that took seconds now takes centuries. The defender computes one hash per login and does not care; the attacker computes billions and cares enormously.
Argon2 and scrypt go further and are memory-hard, requiring a configurable quantity of memory per hash. This targets the hardware directly: a GPU has thousands of cores but limited memory per core, so a function needing tens of megabytes per hash cannot be parallelised across them the way a compute-bound function can. Argon2id is the current default recommendation, with bcrypt remaining acceptable and extremely widely deployed.
What the salt is for
A salt is a unique random value per password, stored alongside the hash in plain sight. Its secrecy is not the point and it is not trying to make an individual hash harder to compute.
Without a salt, identical passwords produce identical hashes. That gives an attacker two gifts. They can see at a glance which accounts share a password, so cracking one cracks all of them. And they can attack the whole database at once: one pass over a candidate list, comparing each result against every row, so the cost of attacking a million users is the cost of attacking one. Precomputed tables — rainbow tables — are the extreme version of this, where the work is done in advance and shared.
A per-password salt destroys all of it. Every row must be attacked separately, precomputation is worthless because the table would have to be rebuilt per salt, and identical passwords no longer look identical. This is why bcrypt and Argon2 generate a salt themselves and embed it in the output string along with the algorithm and parameters — the design refuses to let you forget.
A pepper is the complement: a single secret value, shared across all passwords, mixed in but stored outside the database, in an HSM or the application's secret store. Its job is narrow and specific — if an attacker exfiltrates the database alone, through SQL injection or a leaked backup, without also compromising the application host, the hashes are uncrackable because a necessary input is missing. It does nothing once the attacker has both. Genuinely useful defence in depth, and not a substitute for any of the above.
Two things at the verification end
Compare in constant time. Any comparison that returns early on the first differing byte leaks, through timing, how much of a prefix matched. This matters much less for a password hash than for a token or a MAC, since the attacker cannot easily steer the hash output, but the correct function costs nothing and the habit is what you want to demonstrate.
Do not let the endpoint enumerate users. If an unknown username returns immediately while a known one with a wrong password takes 250 milliseconds, you have published which accounts exist — useful for targeted phishing and for credential stuffing. The fix is to perform a dummy hash against a fixed value when the user is not found, so both paths cost the same, and to return the same message either way.
Keeping up
A work factor chosen today will be too low in five years, because hardware improves and the calibration was to a wall-clock target on current hardware. So store the parameters with the hash, which the standard formats already do, and rehash opportunistically: at login you have the plaintext momentarily, so if the stored parameters are below current policy, recompute and update the row. Over a few months most active users are migrated with no reset and no user-visible event.
The same mechanism handles an algorithm migration from something obsolete, with one adjustment. You cannot upgrade a stored MD5 hash without the plaintext, so wrap it — store the modern hash of the legacy hash, mark the row, and unwrap it to a clean modern hash at the user's next login. That way the weak hashes stop being directly attackable immediately, rather than after everyone has eventually logged in.
Hashing removes the key that encryption would require you to protect. Making the hash slow removes the throughput that makes guessing work. You need both, and a salt so the attacker cannot amortise the guessing across every user at once.
Likely follow-ups
- What is a pepper, where does it live, and what does it protect against that a salt does not?
- How do you pick a work factor, and what do you do about it three years later?
- Your login endpoint responds faster for unknown usernames than for wrong passwords. Why does that matter?
- How would you migrate a database of MD5 password hashes without forcing every user to reset?
Related questions
- Would you use server-side sessions or JWTs for authenticating your API, and why?hardAlso on authentication5 min
- How do you split a story that is too big to fit in a Sprint?mediumSame kind of round: concept3 min
- What is the Definition of Done, who owns it, and what do you do with an item that misses it at the end of a Sprint?mediumSame kind of round: concept3 min
- Where does encapsulation or polymorphism change a design, and how do you decide between composition and inheritance?mediumSame kind of round: concept6 min
- What are the four pillars of OOP, and what is the difference between abstraction and encapsulation?easySame kind of round: concept4 min
- Take this table to third normal form, then tell me when you would deliberately denormalise — and what does ACID guarantee?mediumSame kind of round: concept5 min
- Walk me through Scrum as the Guide defines it: the accountabilities, the events, the artefacts, and what each event is for.easySame kind of round: concept5 min
- Adding a second LEFT JOIN doubled the revenue figure on a report. Explain what happened and write the correct query.mediumSame kind of round: concept4 min