Skip to content

Interview question · Cryptography, PKI & TLS

How should passwords be stored, and why not a fast hash?

SHA-256 with a salt sounds right and fails. The answer that scores explains the attacker's economics and names the parameters.

easyEssentialNewPreparation tags: Meta, Google, AmazonUpdated Nov 2026

TL;DR: Store a salted hash from a deliberately slow, memory-hard function: Argon2id first, scrypt or bcrypt where Argon2id is unavailable, PBKDF2 where FIPS compliance requires it. A fast hash such as SHA-256 lets an attacker who steals the database test billions of guesses per second on GPUs; a slow function cuts that by many orders of magnitude, and a unique salt per password stops one guess being tested against every account at once. Never encrypt passwords (reversible) and never store them in plain text.

How to approach it

Start from the threat: the database is stolen and the attacker guesses passwords offline, at their own speed. Then explain how each design choice changes their cost. Finish with concrete parameters and the operational details (upgrading old hashes, peppers).

A strong answer

FIG · PASSWORD STORAGE · THE STOLEN DATABASE TEST
What an attacker with your users table can do

Fast hash (SHA-256), no salt

Guess

from wordlists

GPU hashing

billions per second

One guess tests every account

Outcome, unsafe: Most passwords recovered

Precomputed tables work too

Argon2id with unique salts

Guess

per account

Memory-hard hash

tens of ms, lots of RAM

Salt forces per-account work

Outcome, safe: Only weak passwords fall

Pair with breached-password checks and MFA

The threat. After a breach, the attacker has every stored value and unlimited time. Their tool is offline guessing: take a candidate password, compute the stored function, compare. The goal of storage design is to make each guess as expensive as possible and to stop guesses being shared across accounts.

What each element does.

ElementWhat it stops
Hashing (one-way) instead of encryptionAnyone with the key recovering every password at once
Unique random salt per passwordPrecomputed tables, and testing one guess against all accounts simultaneously
Slow function (work factor)High guess rates; each guess costs the attacker real time
Memory-hard function (Argon2id, scrypt)Cheap parallel cracking on GPUs and custom hardware
Optional pepper (secret key kept outside the database, for example in an HSM)Cracking at all if only the database is stolen

Concrete choices. The OWASP Password Storage Cheat Sheet recommends Argon2id with at least 19 MiB of memory, 2 iterations and 1 degree of parallelism; scrypt (N=2^17, r=8, p=1) as an alternative; bcrypt with a work factor of 10 or more for legacy systems (note bcrypt's 72-byte input limit); and PBKDF2-HMAC-SHA256 with 600,000 iterations where FIPS-140 compliance requires it. Tune parameters so a login takes acceptable server time, and revisit as hardware improves.

Why not SHA-256 with a salt. The salt prevents precomputation but not speed. SHA-256 is designed to be fast, and commodity GPUs compute billions of SHA-256 hashes per second, so common and moderately complex passwords fall quickly. A slow, memory-hard function makes each guess thousands to millions of times more expensive.

Operations. Store the algorithm and parameters with each hash so you can raise them later; when a user logs in with an old-format hash, verify it and re-hash with current parameters. Compare in constant time. Rate-limit online login attempts separately, since slow hashing protects stolen databases, not the login form.

Storing and checking a password, in code. The standard library's scrypt gives a salted, memory-hard hash; the record keeps its parameters so they can be raised later, and the comparison runs in constant time.

import hashlib
import hmac
import os

N, R, P = 2**14, 8, 1  # scrypt cost: tune to your login latency budget


def hash_password(password: str) -> str:
    salt = os.urandom(16)
    dk = hashlib.scrypt(password.encode(), salt=salt, n=N, r=R, p=P)
    return f"scrypt${N}${R}${P}${salt.hex()}${dk.hex()}"


def verify(password: str, stored: str) -> bool:
    _, n, r, p, salt, dk = stored.split("$")
    test = hashlib.scrypt(password.encode(), salt=bytes.fromhex(salt),
                          n=int(n), r=int(r), p=int(p))
    return hmac.compare_digest(test.hex(), dk)


a = hash_password("correct horse battery staple")
b = hash_password("correct horse battery staple")
print(a != b)  # => True
print(verify("correct horse battery staple", a))  # => True
print(verify("Correct horse battery staple", a))  # => False
print(a.split("$")[0], len(a.split("$")[-1]) * 4, "bit key")  # => scrypt 512 bit key

What interviewers probe next

  • "You inherit a database of unsalted MD5 hashes. What now?" Wrap them immediately: store Argon2id(MD5(password)) for every row, then migrate to plain Argon2id at each user's next login.
  • "Where should the pepper live?" Outside the database, ideally in an HSM or KMS that computes the keyed step, so a database dump alone is not enough.
  • "Doesn't slow hashing let attackers DoS your login?" It costs server CPU, so rate-limit and cap request rates per account and source.

What if?

What if · interview follow-ups

Hold your answer as the constraints change

You join a team whose user table stores passwords as unsalted SHA-256 hashes, and the database backup was found on a public bucket.

  1. Start here

    Why are unsalted fast hashes so weak?

How it's scored

Weak: Recommends SHA-256 or encryption; omits salts.

Hire: Names Argon2id or bcrypt, explains salts and work factors, and the offline-guessing threat.

Strong Hire: Gives current parameters, covers memory hardness, peppers, upgrading legacy hashes and constant-time comparison.

Common mistakes

  • Encrypting passwords "so support can recover them".
  • One global salt for all users.
  • Never revisiting work factors.
  • Forgetting bcrypt's 72-byte input limit when combining it with long passphrases.
You just read a free answer. Every topic opens its first 5 answers to anyone. Sign in and the count per topic goes to 10, and we keep track of how far you get in every topic.Google sign-in · free · no payment details
HOW DID IT GO?
0
READING SIGNED OUT

Signing in doubles your free answers, from 5 to 10 per topic, and the site starts remembering you: mastery per topic, bookmarks, and a next-focus recommendation. Free, no card.

Sign in free
UP NEXT ON YOUR JOURNEY
DISCUSSION · 0

No comments yet. Share a correction or a different way to answer it.