Password storage: do's and don'ts
Updated 2026-05-06
Password storage is one of the most-studied problems in security and one of the most consistently misimplemented in production. The 2026 best practice is short: Argon2id with parameters tuned to ~250ms, vendor library not custom code, server-side pepper from a secrets manager, breached-password screening at write time. Anything else is technical debt.
The do's and don'ts below codify these defaults. For broader auth context, see the account takeover defense guide.
Do
Use Argon2id for new password hashing deployments
Argon2id is the OWASP-recommended modern password hash, designed to resist GPU and ASIC attacks via memory-hard cost. It is the winner of the Password Hashing Competition and the default for new deployments.
OWASP Password Storage Cheat Sheet and NIST SP 800-63B both recommend Argon2id as the preferred algorithm for new deployments. Recommended parameters in 2026: m=64MB (memory), t=3 (iterations), p=1 (parallelism).
Tune cost parameters to take ~250ms per hash on production hardware
The cost parameter is the single tuning knob for password hash strength. Too low and brute-force is cheap; too high and login latency becomes user-visible. The 250ms target balances both.
OWASP cheat sheet recommends 250ms–500ms target. Below 100ms, GPU brute-force is too cheap; above 500ms, login UX degrades and the auth service becomes a DoS surface.
Store hashes with the algorithm and parameters embedded
Algorithm choices change over time. Storing the parameters with each hash (in the standard PHC format) lets the system migrate users transparently to stronger parameters on next login without forcing a password reset.
PHC (Password Hashing Competition) string format embeds algorithm + cost parameters in the hash itself, e.g. $argon2id$v=19$m=65536,t=3,p=1$<salt>$<hash>. Universally supported by modern password libraries.
Apply a server-side pepper from a secrets manager
A pepper is a server-side secret combined with the password before hashing, stored separately from the hashes. If the database is exfiltrated but the pepper is not, the hashes are unusable to the attacker.
Defense in depth: the pepper raises the bar for offline brute-force. Implementations differ; the canonical pattern is HMAC-SHA256(pepper, password) before passing to Argon2id.
Don't
Don't use MD5, SHA-1, or SHA-256 for password hashing
These are general-purpose hash functions, designed to be fast, exactly the wrong property for password hashing. GPU brute-force can compute billions of MD5 / SHA-1 / SHA-256 hashes per second.
Password breaches publicly cracked at scale (LinkedIn, Adobe, RockYou) used MD5 / SHA-1 / unsalted SHA-256. Modern attackers crack billions of fast-hash passwords per day on commodity GPUs.
Don't roll your own password hashing
Subtle mistakes (timing attacks, salt reuse, parameter mistakes, custom modifications) defeat the security properties. Use a vetted library that implements Argon2id, bcrypt, or scrypt correctly.
Multiple production breaches have traced to custom password hash schemes that introduced timing oracles or salt reuse. OWASP and NIST both explicitly recommend against rolling custom implementations.
Don't truncate passwords before hashing
Some legacy implementations silently truncate passwords above N characters, which collapses the password space and fails users who picked long passphrases. Hash the entire password.
Bcrypt has a documented 72-byte limit that some libraries silently truncate. For Argon2id, no truncation is required; for bcrypt, hash with HMAC-SHA512 first to avoid the limit.
Don't accept passwords without breached-password screening
Passwords appearing in known breaches are the lowest-friction credential-stuffing target. Screening at registration and password change rejects the weakest passwords before they are stored.
Have I Been Pwned k-anonymity API and equivalents in CIAM platforms (Auth0 breached password detection, Stytch, Cognito) make this a default-on capability with no privacy cost.