Hash passwords with Argon2id when you can. OWASP's baseline is memory 19 MiB, 2 iterations and parallelism 1. Fall back to scrypt, then bcrypt for legacy systems, and PBKDF2 only when FIPS compliance is required. Never use reversible encryption for passwords.
Hash, do not encrypt
OWASP's cryptographic storage guidance says passwords should not use reversible encryption and should be stored with a secure password hashing algorithm. If your database key leaks, encrypted passwords all leak together. Hashes still have to be cracked one by one.
Order of preference
From OWASP's Password Storage Cheat Sheet:
- Argon2id, preferred.
- scrypt, when Argon2id is not available.
- bcrypt, for legacy systems only.
- PBKDF2, when FIPS 140 compliance is required.
Minimum settings
Argon2id with parallelism 1. The page lists these as equal in defense, trading memory for CPU:
- 19 MiB (baseline). Iterations: 2.
- 46 MiB. Iterations: 1.
- 12 MiB. Iterations: 3.
- 9 MiB. Iterations: 4.
- 7 MiB. Iterations: 5.
scrypt: N of 2^17 (128 MiB), r of 8, p of 1, with listed alternatives that trade memory for parallelism.
bcrypt: work factor at least 10, maximum password length 72 bytes, and avoid pre-hashing with a plain hash because of null byte issues and password shucking.
Tune upward to what your login servers can afford.
Peppers
A pepper is a secret shared across all stored passwords, unlike a per-user salt. OWASP says to keep it in a secrets vault or HSM, away from the password database. It adds defense in depth on top of a proper hash, and does nothing alone. If a pepper is compromised, you must change it and users hashed under it must reset their passwords.
Rollout steps
- Choose the library's Argon2id implementation rather than writing your own.
- Store the algorithm parameters with each hash so you can upgrade later.
- On successful login, rehash users with old settings.
- Do not log passwords or hashes.
- Add multi-factor authentication. Hashing protects stored data. It does not stop phishing. See which MFA to require.
Choosing your parameters in practice
OWASP's list is a set of minimums that trade memory against time, so pick the row that fits your login servers. If you run many small instances with limited memory, a row with more iterations and less memory may fit better. If memory is cheap, the higher memory row costs less CPU. Measure how long one hash takes on production hardware under realistic load, check that a burst of logins does not exhaust memory, and then set values at or above the minimum. Because you store parameters with each hash, you can raise them later and upgrade users as they sign in. Revisit the numbers whenever you change hardware.
Quick check
Is SHA-256 good enough for passwords?
No. It is fast, which helps attackers. Use a purpose built password hashing function.
Do I need a unique salt?
Yes. Modern libraries generate and store it for you.
Sources
Last reviewed: 19 October 2026.