Why MD5 Should Never Be Used for Password Storage
Article • 03. July 2026

Why MD5 Should Never Be Used for Password Storage

Understand why MD5 is unsafe for password storage and why modern systems should use Argon2id or another dedicated password-hashing function.

MD5 is a fast general-purpose hash function created for message digests. It is not a password-hashing algorithm. Storing passwords as MD5 hashes exposes users to rapid offline guessing if the database is stolen.

The correct replacement is not plain SHA-256. Password storage requires a dedicated, salted, configurable password-hashing function such as Argon2id. Other established choices include scrypt, bcrypt, and PBKDF2 when they fit platform or compliance requirements and are configured correctly.

What happens when a password database leaks

A typical login system does not need to recover a password. It stores a derived value and calculates the same derivation when the user signs in.

If an attacker obtains the stored hashes, the attacker can work offline:

  1. Guess a candidate password.
  2. Hash the guess using the same algorithm and parameters.
  3. Compare the result with the stolen value.
  4. Repeat until a match is found.

The attacker does not need to contact the website for every guess. Rate limits, account lockouts, CAPTCHA, and monitoring no longer help. The cost of each offline guess becomes the central security control.

MD5 is designed to be fast, so each guess is cheap. Modern hardware can calculate enormous numbers of MD5 hashes in parallel. Weak and reused passwords can therefore be recovered quickly.

Hashing is not the same as password hashing

A cryptographic hash such as MD5 or SHA-256 aims to process data efficiently. That is useful for checking large files, indexing content, and building other cryptographic constructions.

A password-hashing function has the opposite performance goal. It intentionally consumes time, memory, or both. Legitimate login verification remains acceptable because the server processes one password attempt at a time. An attacker testing millions or billions of guesses pays the cost repeatedly.

A secure password hash also supports:

  • A unique random salt for every stored password.
  • A configurable work factor.
  • Parameters that can be increased as hardware improves.
  • A standard encoded format that stores the algorithm, salt, and settings with the result.
  • Implementations designed and reviewed specifically for password storage.

Why MD5 fails as a password-storage algorithm

It is far too fast

Speed is the main practical problem. Attackers can test large password dictionaries, common substitutions, leaked-password lists, and brute-force combinations at high rates.

Even if a password is not in a standard dictionary, users often choose predictable patterns. A fast hash lets attackers explore those patterns cheaply.

Unsalted MD5 reveals password reuse

A plain MD5 scheme often stores:

MD5(password)

Every user with the same password receives the same hash. An attacker can immediately identify accounts that share a password and crack all of them after finding one matching guess.

Unsalted hashes are also vulnerable to precomputed lookup databases. These are often called rainbow tables, though direct hash lookup collections and precomputed dictionaries are also common.

A salt does not make MD5 slow

A better-looking legacy design might store:

MD5(salt + password)

A unique random salt is important because it prevents identical passwords from producing identical stored values and makes precomputation less useful. However, it does not fix MD5’s speed. The attacker reads the salt from the database and includes it in each guess.

Salts are not secret. Their purpose is uniqueness, not confidentiality.

Repeated MD5 is still a poor design

Some systems apply MD5 thousands of times to slow guessing. That is better than one iteration but remains an obsolete custom construction. It lacks the memory-hard design of modern algorithms, may be implemented incorrectly, and usually provides weaker upgrade and interoperability options.

Use a standard password-hashing function instead of inventing a loop around a general-purpose hash.

MD5 also has broken collision resistance

Practical collision attacks are another reason not to select MD5 for new cryptographic systems. Password cracking normally targets preimages rather than collisions, so the collision weakness is not the main reason MD5 password databases fail. The speed and common lack of proper salting are usually more directly exploitable.

Still, an algorithm with known structural weaknesses and no password-specific defenses has no place in a modern password-storage design.

Why plain SHA-256 is not the answer

Replacing MD5 with SHA-256 may sound like an upgrade because SHA-256 has much stronger collision resistance. For file integrity and many cryptographic uses, it is an upgrade.

For passwords, plain SHA-256 remains too fast. This design is not adequate:

SHA256(password)

Neither is this:

SHA256(salt + password)

The salt solves only the uniqueness and precomputation problem. It does not impose enough cost on every guess.

SHA-256 may appear inside a password-hashing construction such as PBKDF2-HMAC-SHA-256, where the construction adds a configurable iteration count and defined processing rules. That is different from hashing a password once with SHA-256.

What should be used instead

Argon2id

Argon2id is the preferred option in current OWASP guidance when it is available. It is designed to consume configurable memory and processing time. Memory cost makes large-scale parallel attacks more expensive than attacks on fast hashes.

Argon2id combines characteristics intended to resist different classes of attacks. Use a maintained library and select parameters based on current guidance and performance testing on your production hardware.

A stored Argon2id value commonly includes the algorithm identifier, version, memory setting, iteration count, parallelism, salt, and derived output in one encoded string.

scrypt

scrypt is a memory-hard password-based key derivation function. It remains a valid choice where Argon2id is unavailable and a reliable scrypt implementation is supported.

bcrypt

bcrypt is widely supported and has a configurable cost factor. It is still used in many established systems, but it has limitations, including a maximum password input length in common implementations and less memory hardness than Argon2id or scrypt.

Applications using bcrypt must handle long passwords carefully and should not silently truncate them. Follow the behavior of the selected library and current platform guidance.

PBKDF2

PBKDF2 applies a pseudorandom function repeatedly and supports a configurable iteration count. It is common in environments with formal compliance constraints or broad library support.

PBKDF2 is primarily CPU-hard rather than memory-hard, so it generally needs a high iteration count. Use the hash function and work factor recommended for the relevant environment instead of copying old parameters.

Salt, pepper, and work factor

Salt

A salt is a unique random value generated for each password. It is stored with the password hash. A secure password-hashing library normally creates and encodes it automatically.

A salt prevents two users with the same password from having the same stored hash. It also forces an attacker to perform work separately for each salt.

Do not use usernames, email addresses, timestamps, or a single application-wide constant as salts. Use a cryptographically secure random generator and an established library.

Pepper

A pepper is an optional secret value kept separately from the password database, usually in a secret manager, hardware security module, or similarly protected location.

A pepper can add protection when an attacker obtains only the database. It also creates operational responsibilities: rotation, backup, availability, access control, and incident response. Losing the pepper can make all password verification impossible.

A pepper does not replace a salt or a strong password-hashing function.

Work factor

The work factor controls how expensive verification is. Argon2id exposes memory, time, and parallelism settings. bcrypt uses a cost parameter. PBKDF2 uses an iteration count.

Choose settings by testing on the actual server class. Verification should be expensive enough to slow attacks but fast enough to support expected login traffic and avoid denial-of-service risks. Reassess the settings periodically as hardware changes.

How secure password verification works

A safe implementation usually delegates formatting and verification to a mature library.

At registration or password change:

  1. Accept the user’s full password without silent truncation.
  2. Generate a unique random salt.
  3. Run the selected password-hashing function with approved parameters.
  4. Store the library’s encoded output.
  5. Do not store the plaintext password.

At login:

  1. Retrieve the stored encoded value.
  2. Pass the submitted password and stored value to the library’s verification function.
  3. Use the result to accept or reject the login.
  4. If the stored parameters are outdated, rehash the password after a successful login.

Use constant-time verification functions provided by the library. Do not manually compare derived byte strings with ordinary string operations when the framework already offers a safe verifier.

How to migrate an existing MD5 password database

You usually cannot recover the original passwords from MD5 hashes, and you should not attempt to decrypt them because hashes are not encrypted values. Migration normally happens when users authenticate or reset their passwords.

Option 1: Rehash after a successful login

  1. Keep a field or prefix that identifies the legacy MD5 format.
  2. When the user logs in, verify the submitted password against the old MD5 value.
  3. If it matches, immediately hash the submitted password with Argon2id or the selected modern method.
  4. Replace the legacy value.
  5. Remove the MD5 verification path after the migration window.

This method gradually upgrades active accounts without knowing their plaintext passwords in advance.

Option 2: Force password resets

For high-risk systems, known breaches, or very old unsalted databases, require users to set new passwords. Send reset links through a secure, time-limited process rather than emailing passwords.

Invalidate active sessions and review other account-recovery controls when the migration follows a security incident.

Option 3: Layer a modern hash temporarily

Some systems wrap an existing MD5 value in a modern password hash as an interim measure:

Argon2id(MD5(password))

This can reduce immediate offline attack speed after a database-only migration, but it preserves limitations of the legacy input and complicates verification. Treat it as a temporary bridge, then replace it with a direct modern hash after the user next supplies the password.

Do not expose which accounts use legacy hashes

Migration code should avoid user enumeration and inconsistent error messages. Login responses should not reveal whether an account exists, whether it still uses MD5, or which verification step failed.

Apply rate limiting and multi-factor authentication as additional protections, but do not use them as excuses to keep weak stored hashes.

Password-storage mistakes to avoid

  • Storing plaintext passwords.
  • Using reversible encryption so the application can retrieve passwords.
  • Using MD5, SHA-1, or a single round of SHA-256.
  • Using one shared salt for every account.
  • Creating salts from predictable user data.
  • Writing a custom password-hashing algorithm.
  • Using outdated work factors indefinitely.
  • Silently truncating long passwords.
  • Logging passwords or including them in analytics events.
  • Emailing users their existing passwords.
  • Assuming a secret pepper alone fixes a weak hash.

Password policy still matters

A strong storage method limits damage after database theft, but it does not make weak passwords strong. Allow long passwords and password-manager-generated values. Screen new passwords against known-compromised-password lists where appropriate. Avoid arbitrary composition rules that encourage predictable substitutions.

Multi-factor authentication can reduce account takeover risk when a password is compromised, but it does not remove the obligation to store passwords safely.

A practical decision guide

For a new application

  • Use the framework’s current password-hashing API.
  • Prefer Argon2id when supported and recommended for the platform.
  • Store the complete encoded hash string returned by the library.
  • Test and document work-factor settings.
  • Plan for future rehashing.

For an existing application using MD5

  • Treat the database as high risk.
  • Identify whether hashes are salted and how they are formatted.
  • Add versioned verification logic.
  • Upgrade hashes after successful logins.
  • Force resets for inactive or high-risk accounts.
  • Set a deadline to remove legacy verification.

For file checksums

Password-storage guidance does not mean MD5 can never calculate a file checksum. It means MD5 should not protect passwords. For modern file-integrity use, SHA-256 is usually a better choice, and authenticity requires a trusted checksum source, digital signature, or keyed authentication mechanism.

Key takeaway

MD5 fails for password storage because it lets attackers test guesses far too quickly and is often deployed without unique salts. Plain SHA-256 does not solve the speed problem. Use a dedicated password-hashing function, preferably Argon2id where supported, with unique salts, suitable parameters, maintained libraries, and a clear upgrade path.

References