Skip to content

MD5, and the speed that ruins it.

MD5 turns any input into 128 bits, and it does so fast enough that a laptop manages millions of them per second. For checking a downloaded file that speed is the feature. For storing a password it is the whole problem.

Everything below this line runs locally

0 bytes in · 128 bits out · 0 network requests

The hash updates as you type. There is no button to press.

How fast is too fast?

This measures your own browser rather than quoting a benchmark. It hashes two hundred thousand different strings and reports the rate. A graphics card built for the job runs several orders of magnitude beyond whatever number you get here.

Where MD5 is still fine

It is worth being precise about this, because the blanket advice to never use MD5 is not quite right. The failures are specific.

File integrity
Fine. If you are checking that a download was not corrupted in transit, MD5 does the job and does it quickly. It stops being enough the moment you need to prove the file was not swapped deliberately.
Deduplication and cache keys
Fine. Nobody is constructing collisions to poison your cache of thumbnail images, and the speed is welcome.
Digital signatures and certificates
Broken. Collisions have been practical since 2004, and a real certificate forgery was demonstrated in 2008. Use SHA-256.
Password storage
Broken, for a different reason. Not because of collisions but because it is fast, unsalted by default, and every common password already sits in a lookup table somewhere.

If you are stuck with MD5 password hashes

You cannot recover the passwords, and you should not wait for everyone to log in before those rows get better protection. Hash the existing MD5 values with bcrypt and store the result. Every account is covered as soon as the batch job finishes.

// one pass over the table, no user involvement
user.passwordHash = await bcrypt.hash(user.md5Hash, 12);
user.scheme = 'bcrypt-over-md5';

// then at login
if (user.scheme === 'bcrypt-over-md5') {
  const ok = await bcrypt.compare(md5(candidate), user.passwordHash);
  if (ok) {
    user.passwordHash = await bcrypt.hash(candidate, 12);
    user.scheme = 'bcrypt';
  }
}

The migration write up goes through the same pattern in more detail, including what to widen before you start.

Questions people ask

Can MD5 be decrypted?
No, it is a one way function. What tools advertise as MD5 decryption is a lookup in a table of previously computed hashes, which works because MD5 is fast enough that somebody could afford to compute billions of them in advance.
Is MD5 broken?
For anything security related, yes. Practical collisions have been produced since 2004, meaning two different inputs with the same hash, and they can be generated in seconds. That rules it out for signatures and certificates.
So what is MD5 still good for?
Checking that a file arrived intact, deduplicating content, and cache keys. Anywhere the risk is accidental corruption rather than someone deliberately constructing a collision, MD5 is fast and adequate.
What should I use instead for passwords?
Bcrypt, Argon2id, scrypt or PBKDF2. All four are deliberately slow, which is the property MD5 lacks and the reason it fails at this job.
I inherited a database of MD5 password hashes. Now what?
Do not wait for people to log in. Hash the existing MD5 values with bcrypt and store that, which protects every account immediately, then unwrap each one to a plain bcrypt hash as its owner next signs in.

Next