security-audit
Password Hashing Algorithms: An Encyclopedia and Engineering Guide
A practical map of MD5, SHA, PBKDF2, bcrypt, scrypt, Argon2, yescrypt, and historical password schemes, with selection, tuning, and migration guidance.
The short engineering answer is simple: for a new general-purpose password database, start with Argon2id through a maintained framework or library. If Argon2id is unavailable, scrypt is the usual memory-hard fallback. PBKDF2-HMAC remains important when a validated platform or compliance boundary requires it. Keep bcrypt for compatible legacy systems, but understand its 72-byte input boundary and plan a measured migration. Never store passwords with raw MD5, SHA-1, SHA-256, SHA-3, BLAKE2, or BLAKE3.
The longer answer matters because “password hash” is used for several different things. Some are fast checksum functions. Some are old operating-system formats. Some are password-based key derivation functions. Their names may all end in “hash,” but they do not create the same cost for an offline attacker.
First separate the primitives
| Primitive | Reversible? | Typical purpose | Suitable for password storage? |
|---|---|---|---|
| Encryption | Yes, with a key | Recover protected data | Usually no; a stolen decryption key exposes every password |
| General cryptographic hash | No | Integrity, signatures, content addressing | No; it is deliberately fast |
| MAC / HMAC | No, keyed | Authenticity and integrity | Not by itself |
| Password hash / KDF | No | Make each password guess expensive | Yes, when configured and implemented correctly |
A password verifier normally stores a derived value, not a password it can recover. At login, it runs the same password-hashing scheme with the stored salt and parameters, then compares the result using the library’s verification API.
“One-way” does not mean “uncrackable.” An attacker who steals the database can guess a candidate password, run the same public algorithm, and compare the output offline. Password hashing is therefore an economics problem: make every guess costly enough that a database leak does not immediately turn common passwords into plaintext.
The controls around the algorithm
An algorithm name is only one part of the stored credential.
- Salt: a unique, random value for each password. It stops one precomputed table from working across many accounts and prevents equal passwords from producing equal stored values. The salt is not secret and is normally stored beside the hash.
- Work factor: a tunable time or iteration cost. It lets defenders increase verification cost as hardware improves.
- Memory cost: working memory required for one guess. Memory-hard designs make massively parallel GPU or ASIC guessing more expensive than CPU-only iteration does.
- Parallelism: how much work the implementation can perform concurrently. Its meaning and security effect differ by algorithm.
- Pepper: an optional secret held separately from the password database, ideally behind a key-management or hardware boundary. It can add defense in depth, but rotation is operationally harder because a lost or changed pepper cannot be reconstructed from the database. NIST’s current verifier guidance recommends an additional keyed operation when practical [5].
- Version and parameters: the stored record must say how it was produced. Without that metadata, gradual upgrades become guesswork.
The salt handles precomputation and cross-account reuse. It does not make a weak password strong, and it does not need to be hidden. A pepper solves a different problem: an attacker who steals only the database still lacks a separate secret.
The algorithm map
Fast general-purpose hashes: useful, but not here
MD5, SHA-1, SHA-2, SHA-3, BLAKE2, and BLAKE3 are designed to process data efficiently. SHA-2 and SHA-3 are standardized general-purpose families, while BLAKE2 and BLAKE3 explicitly emphasize efficient hashing [10] [11] [12] [13]. That is desirable for file integrity, signatures, Merkle trees, content addressing, and many protocol constructions. It is exactly the wrong property for offline password guessing.
| Family | Current role | Password-storage verdict |
|---|---|---|
| MD5 | Legacy checksum; collision-broken | Never use, salted or unsalted |
| SHA-1 | Legacy compatibility; collision-broken | Never use directly |
| SHA-2 / SHA-3 | Modern general-purpose hashing | Secure primitives, but far too fast alone |
| BLAKE2 / BLAKE3 | High-performance general hashing | Fast by design; not password hashes |
Collision resistance is not the main password problem. The attacker usually wants a preimage for one stored value by testing likely passwords. Even a collision-resistant fast hash still lets the attacker test enormous numbers of guesses. Adding a salt to SHA-256 fixes rainbow-table reuse, but not the speed. Manually running SHA-256 thousands of times creates a home-grown KDF with unclear encoding, parameters, upgrade behavior, and hardware resistance. Use a reviewed password-hashing construction instead.
Historical password formats
These formats matter during audits and migrations even when they should not receive new passwords.
| Scheme | What to recognize | Engineering status |
|---|---|---|
Traditional DES crypt | Short password and salt limits, cost frozen in early Unix hardware | Obsolete; reset or migrate on login |
| LM hash | Case-insensitive chunks with severe length and structure weaknesses | Obsolete and dangerous |
| NT hash / NTLM password hash | Unsalted MD4 of UTF-16LE password | Legacy Windows compatibility only; protect and migrate where possible |
MD5-crypt ($1$) | Salted, iterated MD5 Unix format | Obsolete |
SHA-crypt ($5$, $6$) | Tunable SHA-256/SHA-512 Unix formats | Unix compatibility; CPU-hard but not memory-hard |
| phpass portable hashes | Iterated MD5 format used by older PHP applications | Legacy migration input, not a new choice |
Do not judge these schemes only by whether their underlying digest still has collision resistance. Their low cost, small salts, truncation rules, and attacker-optimized implementations are the practical problem. libxcrypt still calls SHA-256/512-crypt acceptable for new Unix hashes when rounds are raised, while OWASP’s application guidance prefers Argon2id and scrypt [14]. That is a context difference, not a reason to treat SHA-crypt as equivalent to a memory-hard application KDF. LM and NT hashes remain Windows compatibility credentials, not models for a new password database [15] [16]. Openwall likewise keeps phpass’s portable MD5 fallback on life support and directs new PHP applications to the native password API [19].
PBKDF2
PBKDF2 applies a pseudorandom function, usually HMAC, repeatedly. It is standardized in PKCS #5 and widely available in platform cryptography APIs [4]. Its strengths are interoperability, mature implementations, and availability inside validated cryptographic modules. Its weakness for password storage is that it mainly spends CPU work and little memory, so parallel attackers can optimize it effectively.
Use PBKDF2 when a real platform or compliance constraint selects it, not because “NIST” is a magic label. A FIPS requirement applies to the deployed cryptographic module, approved mode, configuration, and system boundary—not merely to the spelling of an algorithm in source code [17]. OWASP currently gives PBKDF2-HMAC-SHA-256 with 600,000 iterations as a baseline when FIPS-140 compliance is required [1]. That number is a starting point to measure, not a permanent universal constant. NIST SP 800-132 dates from 2010 and is formally under revision, another reason not to present it as a durable 2026 iteration table [6].
PBKDF1 is retained in PKCS #5 for compatibility and is not recommended for new applications [4].
bcrypt
bcrypt was designed in 1999 around Blowfish’s expensive key schedule. It introduced a logarithmic cost parameter and a 128-bit salt, giving Unix systems a hash whose cost could grow with hardware [7].
Its long deployment history is valuable, but bcrypt has two modern limitations:
- It uses only a small, fixed amount of memory, so it is not memory-hard in the Argon2 or scrypt sense.
- Most compatible formats only use the first 72 bytes of password input. Bytes are not characters; a UTF-8 password can cross that boundary well before 72 visible characters. Library behavior beyond the limit varies between truncation and rejection.
Never silently accept two different long passwords as equivalent. If an existing application uses bcrypt, test the exact library and version, define input encoding and length behavior, and use the library’s verifier. OWASP treats bcrypt as a legacy choice and recommends a work factor of at least 10 for systems that must keep it [1]. Pre-hashing before bcrypt is a protocol design, not an improvised fix: it needs domain separation, safe encoding, and migration metadata to avoid null-byte, collision, or password-shucking mistakes.
scrypt
scrypt combines computation with a large memory requirement. Its parameters are commonly written as N, r, and p: CPU/memory cost, block size, and parallelization. RFC 7914 specifies the construction and test vectors [3].
scrypt remains a reasonable fallback when Argon2id is not available. The catch is operational: parameter triples are easy to copy without understanding their memory use. OWASP currently lists several equivalent trade-offs, beginning with N=2^17, r=8, p=1 at about 128 MiB [1]. A service must test the chosen combination under peak authentication concurrency, not only in a one-request benchmark.
Argon2d, Argon2i, and Argon2id
Argon2 won the Password Hashing Competition and was later specified in RFC 9106. It exposes memory size m, pass count t, and parallelism p, and its encoded form can carry the variant, version, salt, and parameters [2].
- Argon2d uses data-dependent memory access. It is strong against time-memory trade-offs but can expose side-channel information, so it is aimed at cases without side-channel concerns rather than ordinary password databases.
- Argon2i uses data-independent access to reduce side-channel risk, but needs more passes for comparable protection against some trade-offs.
- Argon2id starts with Argon2i-style access and then uses Argon2d-style access. It is the balanced variant recommended for password hashing by RFC 9106 and OWASP [1] [2].
There is no contradiction when two reputable documents show different parameters. RFC 9106’s two suggested defaults are 2 GiB with one pass and p=4, or 64 MiB with three passes and p=4 for memory-constrained environments; both use a 128-bit salt and 256-bit output. OWASP offers lower-memory, equivalent-cost profiles beginning at 19 MiB with two iterations and p=1. They optimize for different deployment assumptions. Select a defensible floor, then benchmark the real service and record why the chosen profile fits.
yescrypt and Balloon Hashing
yescrypt builds on scrypt and adds modes intended to improve resistance to large-scale cracking. It is a real deployed format—Openwall lists it as the default password scheme in several Linux distributions—but its strongest ecosystem is Unix account hashing rather than general application frameworks [8]. It can be a sound platform choice when the operating system and libxcrypt own the format. For a portable web service, Argon2id usually has broader guidance and library support.
Balloon Hashing is a data-independent memory-hard design with a formal security analysis. Its paper is useful because it studies provable memory hardness and the limits of sequential attack models [9]. It has much less mainstream authentication-framework support than Argon2id, scrypt, PBKDF2, or bcrypt, and the authors explicitly label their public prototype unsafe for production [18]. Treat it as an important design, not the default application choice.
Other Password Hashing Competition candidates—such as Catena, Lyra2, Makwa, and POMELO—belong in a research catalog. A comprehensive engineering map should acknowledge them without pretending that equal academic interest means equal operational support.
How to choose
| Situation | Default direction | Why |
|---|---|---|
| New application, normal server environment | Argon2id | Memory-hard, current guidance, self-describing format |
| Argon2 unavailable but memory-hard KDF supported | scrypt | Standardized and broadly implemented |
| Validated/FIPS-constrained platform | PBKDF2-HMAC in the approved module | Fits common validated boundaries; verify the actual module and policy |
| Existing well-operated bcrypt database | Keep, raise measured cost, rehash toward Argon2id | Avoids risky flag-day migration while addressing legacy limits |
| Unix login managed by a modern distribution | Follow the OS-supported yescrypt/Argon2 policy | Keeps PAM, crypt, recovery tools, and system formats aligned |
| MD5, SHA, LM/NTLM-only, DES-crypt, or other weak records | Prioritized migration or password reset | Offline cracking cost is too low or format weaknesses are severe |
The algorithm decision is also a capacity decision. A login endpoint that spends 64 MiB per verification can exhaust memory if an attacker opens thousands of concurrent requests. Rate limits, request queues, timeouts, account-abuse controls, and capacity tests are part of the password-hashing design. Reducing the KDF to an unsafe cost is not a substitute for controlling online abuse.
Tune parameters on the system that will verify them
Use authoritative profiles as a floor and a test input, then measure.
- Choose a target verification latency that users can tolerate and the service can sustain. OWASP suggests keeping a hash under roughly one second, but many services deliberately target much less.
- Benchmark production-like CPU and memory, including containers and serverless memory limits.
- Test peak concurrent logins, not only median single-request time.
- Test the failure path and rate limiter so invalid-password floods incur controlled work.
- Store the algorithm, variant, version, parameters, salt, and output. Prefer a standard self-describing encoding such as the PHC string format where the library supports it.
- Set a policy for re-benchmarking and rehashing as hardware and guidance change.
Do not invent salts with usernames or timestamps. Generate them with the password-hashing API or a cryptographically secure random generator. Do not compare derived values with ordinary string equality when the framework provides a constant-time verifier.
Also define input handling. Preserve the password the user chose unless the product has an explicit normalization policy. Do not silently trim spaces, change case, or normalize Unicode in a way that can make distinct inputs authenticate as the same password. NIST allows only limited, documented allowances and requires long-password support [5].
Migration without a flag day
A stored hash cannot normally be “converted” into Argon2id. The old record does not contain the password. The clean migration point is a successful login, when the application temporarily has the submitted password.
read algorithm and parameters from stored record
verify with the old scheme
if verification succeeds and policy says rehash is needed:
hash the submitted password with the current scheme
replace the stored record atomically
discard the submitted password
This supports a gradual migration:
- Make the verifier understand every currently stored format.
- Mark one scheme and parameter profile as current.
- After successful legacy verification, create the new hash and replace the record.
- Force a reset for inactive accounts after a defined deadline.
- Monitor format counts until the legacy verifier can be removed.
Transitional double hashing—feeding an old hash into a new KDF—can sometimes add cost when plaintext is unavailable, but it does not recover entropy, repair truncation, or remove old-format collisions. It also creates a custom format that must be identified and tested. Use it only as a documented bridge, not as the final state.
Pepper rotation has a similar constraint. If verification requires the old pepper, removing it immediately invalidates every remaining record. Keep versioned keys during a controlled migration or require password resets; never pretend a database-only rewrite can recreate a missing secret.
Review checklist
- Is the function designed for password hashing, rather than merely being a cryptographic hash?
- Does every password get a unique random salt?
- Are algorithm, version, parameters, salt, and output stored unambiguously?
- Were cost and memory measured under peak concurrency on production-like hardware?
- Are bcrypt byte limits, input encoding, Unicode handling, and maximum request length explicit?
- Is a pepper stored outside the password database and backed by a rotation/recovery plan?
- Does verification use the maintained library API and constant-time comparison?
- Can the application recognize stale parameters and rehash after successful login?
- Are rate limiting and capacity controls strong enough that the KDF does not become a denial-of-service tool?
- Is the compliance claim about the deployed validated module and mode, not just the algorithm name?
The durable rule is not “pick one hash forever.” Choose a reviewed password-hashing scheme, encode its parameters, measure it in the real system, and keep a migration path open. Argon2id is today’s normal starting point; the versioned record and rehash workflow are what let the system survive tomorrow’s change.
References
[1] OWASP Foundation. “Password Storage Cheat Sheet.” https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
[2] A. Biryukov et al. “Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications.” RFC 9106. https://www.rfc-editor.org/rfc/rfc9106.html
[3] C. Percival and S. Josefsson. “The scrypt Password-Based Key Derivation Function.” RFC 7914. https://www.rfc-editor.org/rfc/rfc7914.html
[4] K. Moriarty et al. “PKCS #5: Password-Based Cryptography Specification Version 2.1.” RFC 8018. https://www.rfc-editor.org/rfc/rfc8018.html
[5] National Institute of Standards and Technology. “SP 800-63B: Authentication and Authenticator Management.” https://pages.nist.gov/800-63-4/sp800-63b.html
[6] National Institute of Standards and Technology. “SP 800-132: Recommendation for Password-Based Key Derivation, Part 1.” https://csrc.nist.gov/pubs/sp/800/132/final
[7] Niels Provos and David Mazières. “A Future-Adaptable Password Scheme.” USENIX ATC 1999. https://www.usenix.org/conference/1999-usenix-annual-technical-conference/future-adaptable-password-scheme
[8] Openwall. “yescrypt — scalable KDF and password hashing scheme.” https://www.openwall.com/yescrypt/
[9] Dan Boneh, Henry Corrigan-Gibbs, and Stuart Schechter. “Balloon Hashing: A Memory-Hard Function Providing Provable Protection Against Sequential Attacks.” https://eprint.iacr.org/2016/027
[10] National Institute of Standards and Technology. “Secure Hash Standard (SHS).” FIPS 180-4. https://csrc.nist.gov/pubs/fips/180-4/upd1/final
[11] National Institute of Standards and Technology. “SHA-3 Standard: Permutation-Based Hash and Extendable-Output Functions.” FIPS 202. https://csrc.nist.gov/pubs/fips/202/final
[12] M-J. Saarinen and J-P. Aumasson. “The BLAKE2 Cryptographic Hash and Message Authentication Code.” RFC 7693. https://www.rfc-editor.org/rfc/rfc7693.html
[13] BLAKE3 Team. “BLAKE3 specification and implementations.” https://github.com/BLAKE3-team/BLAKE3
[14] libxcrypt Project. “crypt(5): password hashing methods and prefixes.” https://man.archlinux.org/man/crypt.5.en
[15] Microsoft. “Prevent Windows from storing an LM hash of your password.” https://learn.microsoft.com/en-us/troubleshoot/windows-server/windows-security/prevent-windows-store-lm-hash-password
[16] Microsoft Open Specifications. “MS-NLMP: NTLM v1 Authentication.” https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/464551a8-9fc4-428e-b3d3-bc5bfb2e73a5
[17] NIST Cryptographic Module Validation Program. “FIPS 140-3 standards and approved security functions.” https://csrc.nist.gov/Projects/cryptographic-module-validation-program/fips-140-3-standards
[18] Stanford Applied Cryptography Group. “Balloon Hashing project and prototype.” https://crypto.stanford.edu/balloon/
[19] Openwall. “Portable PHP password hashing framework.” https://www.openwall.com/phpass/