Engineer's toolkit
Password hashing
Passwords need slow, salted, memory-hard hashes. How the guessing math works, which algorithm to pick, and how to tune the cost for your login servers.
You never need to recover a user's password. You only need to check one. So store a hash, never the password itself and never an encrypted copy. The hash must also be slow on purpose, because people choose weak passwords like summer2026!. A password hashing function, sometimes called a key derivation function, is a deliberately slow, tunable hash. Use Argon2id.
Concept
A salt is a random value, unique per user, stored alongside the hash. It stops identical passwords from producing identical hashes, so attackers can't reuse one precomputed table for everyone.
The big idea
Fast hashes are the wrong tool. SHA-256 is designed to be fast. A single high-end GPU can compute on the order of 20 billion SHA-256 hashes per second. Work through an 8-character password of lowercase letters and digits:
keyspace = 36^8 ≈ 2.8 × 10^12 candidates
SHA-256 on 1 GPU ≈ 2 × 10^10 per second
time to exhaust ≈ 2.8 × 10^12 / 2 × 10^10 ≈ 141 secondsEvery password of that shape falls in about two and a half minutes. Now take bcrypt at cost 10, which the same GPU runs at roughly 5,000 hashes per second:
time to exhaust ≈ 2.8 × 10^12 / 5 × 10^3 ≈ 5.6 × 10^8 seconds ≈ 18 yearsThat gap is the point of a password hashing function. Argon2id is also memory-hard. Each guess needs megabytes of RAM, which removes most of the advantage of GPUs and ASICs that have many cores but little memory per core.
Salts block a second attack. Without them, one cracked hash unlocks every user who chose the same password. Libraries generate and store the salt for you.
Walking through it
| Algorithm | Minimum parameters (OWASP) | Notes |
|---|---|---|
| Argon2id | 19 MiB memory, 2 iterations, 1 lane | First choice |
| scrypt | N = 2^17, r = 8, p = 1 | Memory-hard. Use it if Argon2 is unavailable |
| bcrypt | Cost 10 or more | Battle-tested, not memory-hard. Truncates input at 72 bytes |
| PBKDF2-HMAC-SHA256 | 600,000 iterations | Only when FIPS 140 compliance requires it |
Here is Argon2id with the argon2-cffi package:
from argon2 import PasswordHasher # pip install argon2-cffi
ph = PasswordHasher() # Argon2id defaults, with a fresh salt per call
stored = ph.hash("correct horse battery staple")
print(stored.startswith("$argon2id$"))
def login(stored: str, attempt: str) -> bool:
try:
ph.verify(stored, attempt)
except Exception: # wrong password, or a malformed hash
return False
return True
print(login(stored, "correct horse battery staple"))
print(login(stored, "Tr0ub4dor"))Output:
True
True
FalseThe stored string holds the algorithm, its parameters and the salt. So you can raise the parameters later. When the library reports that a hash uses old settings, rehash it at the next successful login.
Tune the cost so one hash takes about 100 to 500 ms on your login servers. Then do the capacity math. At 250 ms of CPU per login, one core handles 4 logins per second. A spike of 400 logins per second needs 100 cores for hashing alone.
Tip
Encrypting passwords is also wrong. Encryption is reversible, so whoever gets the key gets every password.
Common mistakes
- Using SHA-256 or MD5 for passwords, even with a salt.
- Writing your own salting scheme instead of using the library's.
- Leaving login unthrottled. Each attempt costs real CPU, so unthrottled logins are a cheap denial-of-service attack, and they make online guessing easy. Limit by account and by IP, as in rate limiting.
Check yourself
- Why is a GPU a bigger threat to SHA-256 than to Argon2id? Answer: Argon2id needs megabytes of memory per guess, so thousands of GPU cores can't run in parallel on it.
- Why does a login endpoint need rate limits even with Argon2? Answer: every attempt burns CPU, so a flood of attempts is cheap to send and expensive to serve.
Key takeaways
- Store a salted hash made by a slow, memory-hard function such as Argon2id.
- Never use a fast hash such as SHA-256 for passwords.
- Tune the cost to about 100 to 500 ms per hash, and plan the capacity.
- Rate-limit logins, and rehash when parameters change.
Further learning
The OWASP Password Storage Cheat Sheet is the current reference for the parameters in the table above.
OWASP Password Storage Cheat SheetWebsite with the collection of all the cheat sheets of the project.cheatsheetseries.owasp.orgNext, return to the authentication and authorization overview.
Related
Cryptography for engineers
The few building blocks you need for everyday security decisions, which question each one answers, and where teams go wrong. Start here for the map.
Authentication and authorization
Authentication proves who you are, authorization decides what you can do, and service-to-service calls need their own identity. The map for the rest of this topic.
Rate limiting
Why systems cap how much work a client can send, which algorithm counts, where the limit lives and what the client is told. Start here for the rate-limiting map.