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.

Jul 14, 2026
password securitypassword hashingauthenticationArgon2cryptography

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

PrimitiveReversible?Typical purposeSuitable for password storage?
EncryptionYes, with a keyRecover protected dataUsually no; a stolen decryption key exposes every password
General cryptographic hashNoIntegrity, signatures, content addressingNo; it is deliberately fast
MAC / HMACNo, keyedAuthenticity and integrityNot by itself
Password hash / KDFNoMake each password guess expensiveYes, 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.

FamilyCurrent rolePassword-storage verdict
MD5Legacy checksum; collision-brokenNever use, salted or unsalted
SHA-1Legacy compatibility; collision-brokenNever use directly
SHA-2 / SHA-3Modern general-purpose hashingSecure primitives, but far too fast alone
BLAKE2 / BLAKE3High-performance general hashingFast 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.

SchemeWhat to recognizeEngineering status
Traditional DES cryptShort password and salt limits, cost frozen in early Unix hardwareObsolete; reset or migrate on login
LM hashCase-insensitive chunks with severe length and structure weaknessesObsolete and dangerous
NT hash / NTLM password hashUnsalted MD4 of UTF-16LE passwordLegacy Windows compatibility only; protect and migrate where possible
MD5-crypt ($1$)Salted, iterated MD5 Unix formatObsolete
SHA-crypt ($5$, $6$)Tunable SHA-256/SHA-512 Unix formatsUnix compatibility; CPU-hard but not memory-hard
phpass portable hashesIterated MD5 format used by older PHP applicationsLegacy 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:

  1. It uses only a small, fixed amount of memory, so it is not memory-hard in the Argon2 or scrypt sense.
  2. 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

SituationDefault directionWhy
New application, normal server environmentArgon2idMemory-hard, current guidance, self-describing format
Argon2 unavailable but memory-hard KDF supportedscryptStandardized and broadly implemented
Validated/FIPS-constrained platformPBKDF2-HMAC in the approved moduleFits common validated boundaries; verify the actual module and policy
Existing well-operated bcrypt databaseKeep, raise measured cost, rehash toward Argon2idAvoids risky flag-day migration while addressing legacy limits
Unix login managed by a modern distributionFollow the OS-supported yescrypt/Argon2 policyKeeps PAM, crypt, recovery tools, and system formats aligned
MD5, SHA, LM/NTLM-only, DES-crypt, or other weak recordsPrioritized migration or password resetOffline 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.

  1. 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.
  2. Benchmark production-like CPU and memory, including containers and serverless memory limits.
  3. Test peak concurrent logins, not only median single-request time.
  4. Test the failure path and rate limiter so invalid-password floods incur controlled work.
  5. 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.
  6. 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:

  1. Make the verifier understand every currently stored format.
  2. Mark one scheme and parameter profile as current.
  3. After successful legacy verification, create the new hash and replace the record.
  4. Force a reset for inactive accounts after a defined deadline.
  5. 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/