Guide

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 seconds

Every 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 years

That 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

AlgorithmMinimum parameters (OWASP)Notes
Argon2id19 MiB memory, 2 iterations, 1 laneFirst choice
scryptN = 2^17, r = 8, p = 1Memory-hard. Use it if Argon2 is unavailable
bcryptCost 10 or moreBattle-tested, not memory-hard. Truncates input at 72 bytes
PBKDF2-HMAC-SHA256600,000 iterationsOnly when FIPS 140 compliance requires it

Here is Argon2id with the argon2-cffi package:

Python
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
False

The 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.org

Next, return to the authentication and authorization overview.