MD5 turns 30 this decade and it still refuses to die. You'll find it in file checksums, cache keys, ETag headers, database sharding logic, and — unfortunately — the occasional password table that should have been burned years ago. It helps to know exactly what MD5 is good for so you can use it without shooting yourself in the foot.
What MD5 actually is
MD5 is a cryptographic hash function that takes any input and produces a fixed 128-bit output, usually written as 32 hexadecimal characters. Feed it one byte or a 4GB file, you always get 32 hex chars back.
Two properties define a good hash: the same input always gives the same output, and a tiny change in input produces a wildly different output. MD5 nails both of those.
echo -n "hello" | md5sum
5d41402abc4b2a76b9719d911017c592 -
echo -n "hellp" | md5sum
2b6a26c1efceec44ea3d3f56c5f75c39 -One letter changed and the entire digest is unrecognisable. That avalanche effect is the whole point — it makes MD5 great for detecting whether two things are identical.
When MD5 is genuinely the right tool
Despite its reputation, MD5 is fine for a whole class of non-security jobs. The key question is always: are you defending against an attacker who controls the input? If not, MD5 is fast, tiny, and everywhere.
- Integrity checks against accidental corruption. Verifying a download wasn't mangled in transit, or that a file survived a copy operation.
- Cache keys. Hashing a request URL or a set of parameters into a compact, deterministic key.
- Deduplication. Spotting duplicate files or records quickly by comparing digests instead of full contents.
- Sharding and bucketing. Distributing keys across partitions where you just need an even, deterministic spread.
- ETags and change detection. A cheap fingerprint of content to decide whether something needs re-fetching or re-rendering.
For all of these, MD5's cryptographic weaknesses simply don't matter — nobody is trying to forge a cache key.
When MD5 will absolutely burn you
MD5 is broken for anything security-sensitive. Researchers demonstrated practical collision attacks back in 2004, and they've only gotten cheaper since. A collision means two different inputs produce the same digest — and MD5 collisions can now be generated in seconds on ordinary hardware.
Never use MD5 for passwords
This is the big one. MD5 is fast, which is exactly what you don't want for password storage. A modern GPU can compute billions of MD5 hashes per second, so an attacker with a leaked table can brute-force weak passwords almost instantly. Use a purpose-built password hash like bcrypt, scrypt, or Argon2 with a per-user salt. If you see plain md5(password) in a codebase, treat it as a bug.
Never use MD5 for digital signatures or certificates
Because collisions are cheap, an attacker can craft two documents with the same MD5 — one benign, one malicious — and get a signature that validates both. This isn't theoretical; it's been used in real certificate forgery attacks. For signatures, use SHA-256 or better.
Be careful with untrusted content deduplication
If someone can control the files you're deduplicating, they can deliberately craft two different files that hash to the same value, tricking your system into treating them as identical. If the input is adversarial, reach for SHA-256.
A short worked example
Say you're building a response cache and you want a compact key derived from the request. Here's a typical, perfectly reasonable use in Python:
import hashlib
def cache_key(method: str, url: str, params: dict) -> str:
# Deterministic, order-independent representation
parts = [method.upper(), url]
for k in sorted(params):
parts.append(f"{k}={params[k]}")
raw = "|".join(parts).encode("utf-8")
return hashlib.md5(raw).hexdigest()
key = cache_key("GET", "/api/reports", {"year": 2024, "region": "eu"})
print(key) # e.g. 8f14e45fceea167a5a36dedd4bea2543Sorting the params keeps the key stable regardless of dictionary ordering. The result is a fixed-length, filesystem-safe, URL-safe string that's ideal for Redis keys or filenames. No security assumptions, no problem.
Contrast that with what you should never do:
# DON'T. This is a security hole.
def store_password(pw: str):
return hashlib.md5(pw.encode()).hexdigest()
# DO. Use a slow, salted algorithm.
from argon2 import PasswordHasher
ph = PasswordHasher()
hashed = ph.hash(pw)Common gotchas
- Encoding matters.
md5("café")gives different results depending on whether the string is UTF-8 or Latin-1. Always encode explicitly and consistently before hashing. - Trailing newlines. A frequent "why don't my hashes match?" moment:
echoadds a newline unless you pass-n. The digest of"hello\n"is not the digest of"hello". - Hex vs binary output. Some libraries return raw bytes by default, others return hex. Comparing a hex string to raw bytes silently fails.
- Uppercase vs lowercase hex. The digest value is the same, but string comparisons will fail if one system uppercases the hex and another doesn't.
Quick verification
When you need to confirm a checksum, sanity-check an encoding assumption, or just eyeball what a string hashes to, a quick tool beats spinning up a REPL. Our MD5 Hash Generator runs in the browser and gives you the digest instantly — handy for double-checking that trailing-newline gotcha before you go debugging your application code.
The bottom line
MD5 isn't "bad" — it's a fast, deterministic fingerprint that's genuinely useful for cache keys, corruption detection, and dedup where nobody's attacking you. It's just not a security primitive, and hasn't been for two decades. Keep those two worlds separate: MD5 for convenience, SHA-256 and Argon2 for anything an adversary might want to break. Get that distinction right and MD5 will keep quietly earning its keep in your codebase.
Photo by Mohammad Rahmani on Unsplash