MD5 vs SHA-256 vs bcrypt: Which Hash Should You Use?

MD5 and SHA-256 are for checksums and signatures; bcrypt, scrypt and Argon2 are for passwords. What each one is for, with real speed numbers.

"Which hash should I use?" has no single answer, because the word covers two jobs that want opposite things. A checksum should be as fast as possible. A password hash should be as slow as you can tolerate. Most hashing mistakes in real code come from using a tool built for one job to do the other.

You can compute MD5, SHA-1, SHA-256 and SHA-512 side by side below. Everything runs in your browser, so the text you hash is never uploaded.

Hash & HMAC GeneratorType or paste text to see its MD5, SHA-1, SHA-256 and SHA-512 hashes at once, or switch to HMAC to sign it with a secret key. Everything runs in your browser, so neither your text nor your key leaves your device.

Two jobs, two families

JobUseNever use
File checksum, cache key, deduplicationSHA-256 (MD5 only for legacy compatibility)bcrypt (pointlessly slow)
Digital signatures, certificates, Git-style content IDsSHA-256 or SHA-512MD5, SHA-1
Verifying a message with a shared secretHMAC-SHA256A plain hash of secret + message
Storing user passwordsArgon2id, scrypt, bcrypt, or PBKDF2MD5, SHA-1, SHA-256, even with a salt

The first three rows want a fast cryptographic hash. The last row wants a password hashing function, which is deliberately slow and configurable.

What every hash does

A hash function turns any input into a fixed-length fingerprint. Change one character and the output changes completely:

SHA-256("hello")  = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA-256("hello.") = 1589999b0ca6ef8814283026a9f166d51c70a910671c3d44049755f07f2eb910

Adding a full stop produced an unrelated result. This is called the avalanche effect, and it is why a hash works as a fingerprint: if the hashes match, the inputs almost certainly match too.

A cryptographic hash also promises two harder things: you cannot work backwards from the output to the input, and you cannot find two different inputs with the same output (a collision). The second promise is the one MD5 and SHA-1 have broken.

MD5: broken, but not useless

MD5 produces a 128-bit hash, written as 32 hex characters. Researchers published practical MD5 collisions in 2004, and by 2008 they had used them to forge a certificate authority certificate. In 2012 the Flame malware used an MD5 collision to pass as signed Microsoft code. Anything where an attacker benefits from two inputs sharing a hash, such as signatures, certificates and integrity checks against tampering, must not use MD5.

MD5 is still fine for non-adversarial jobs:

  • Detecting accidental corruption in a download or a backup
  • ETags and cache keys
  • Matching a checksum published by a legacy system that only offers MD5

The question to ask is: would anyone gain from crafting a collision? If yes, use SHA-256. If the only enemy is a flaky network, MD5 works, although SHA-256 costs almost nothing extra, so new systems may as well use it.

SHA-1: retired

SHA-1 (160 bits, 40 hex characters) lasted longer, but in 2017 the SHAttered project produced two different PDF files with the same SHA-1 hash, and in 2020 a cheaper chosen-prefix attack followed. Browsers stopped trusting SHA-1 certificates years ago. Treat it like MD5: acceptable for compatibility, not for anything an attacker could target.

SHA-256 and SHA-512: the default

SHA-256 (64 hex characters) and SHA-512 (128 hex characters) belong to the SHA-2 family and have no known practical attacks. Use SHA-256 unless you have a reason not to; it is what TLS certificates, package managers, Docker image digests and most checksum files use.

One subtlety: SHA-2 is vulnerable to length extension. If you compute SHA-256(secret + message) to authenticate a message, an attacker who sees the hash can append data and compute a valid hash for the longer message without knowing the secret. The fix is HMAC, which is built for exactly this job and avoids the problem. Webhook signatures from Stripe, GitHub and Slack all use HMAC-SHA256 for this reason.

Why SHA-256 is wrong for passwords

The property that makes SHA-256 great for checksums, raw speed, is what makes it terrible for passwords. Here are numbers measured on an Apple M4 Pro laptop, one CPU core, through Node.js:

AlgorithmSpeed
MD5about 2,300,000 hashes per second
SHA-256about 2,600,000 hashes per second
PBKDF2-SHA256, 600,000 iterations44 ms per hash, about 23 per second
scrypt, N=2^17, r=8158 ms per hash, about 6 per second

The fast hashes were limited mostly by JavaScript overhead. A GPU running a dedicated cracking tool manages billions of SHA-256 guesses per second, so a leaked table of SHA-256 password hashes can be tested against huge lists of common passwords in minutes.

A salt, a random value stored next to each hash, stops precomputed lookup tables. That matters: the MD5 of password is 5f4dcc3b5aa765d61d8327deb882cf99, and searching that string online finds it instantly. But a salt does nothing about speed. Each salted guess is still one fast hash.

Password hashing functions fix the speed problem. They are built to take tens or hundreds of milliseconds, and the cost can be raised as hardware gets faster. A login that takes 50 ms to check is unnoticeable to a user; that same 50 ms per guess is about 100,000 times slower for an attacker than SHA-256 in the table above.

Choosing a password hash

All four of these are acceptable. The OWASP Password Storage Cheat Sheet lists them in this order of preference, with these minimum settings:

  1. Argon2id: 19 MiB of memory, 2 iterations, parallelism 1. It is memory-hard, which makes GPU and ASIC attacks expensive. Use it for new systems if your platform has a good library.
  2. scrypt: N=2^17, r=8, p=1. Also memory-hard, and widely available; Node.js includes it in the standard library.
  3. bcrypt: cost factor 10 or more. It has been battle-tested since 1999 and is supported everywhere. Note that bcrypt only uses the first 72 bytes of the password, so very long passphrases are silently cut off.
  4. PBKDF2-HMAC-SHA256: 600,000 iterations. It is not memory-hard, but it is the one to use when compliance rules (FIPS 140) require a NIST-approved algorithm.

Whichever you choose, use a maintained library rather than putting the pieces together yourself. These functions generate the salt, store it and the cost settings inside the output string, and compare hashes in constant time. A bcrypt hash, for example, looks like $2b$12$ followed by the salt and hash. The 12 is the cost, so you can raise it later and rehash each password the next time its user logs in.

Quick reference

  • Is this download intact? SHA-256. MD5 if that is all the publisher provides.
  • Is this file or message really from who it claims? A signature over SHA-256, or HMAC-SHA256 with a shared secret.
  • How do I store this password? Argon2id, scrypt or bcrypt, through a well-maintained library.
  • Can I "decrypt" a hash? No. Hashing is one-way. A site that claims to reverse MD5 is looking it up in a table of previously hashed common strings, which is exactly why passwords need salts and slow hashes.

A slow hash cannot protect a weak password. Long, random passwords are what make cracking impractical. The generator below shows the entropy of what it creates, so you can see how much each extra character helps.

Password GeneratorGenerate strong random passwords in your browser, with the actual entropy in bits shown rather than a vague strength meter. Nothing is transmitted or stored.

Try the tools