MD5 is one of those algorithms every developer bumps into eventually. You'll see it in file checksums, ETags, legacy databases, and the occasional stubborn API that still expects an MD5 signature. It has a reputation as "broken," which is true in one specific sense and misleading in others. Let's sort out what it actually does and where it still earns its keep.
What MD5 actually is
MD5 (Message-Digest Algorithm 5) is a cryptographic hash function designed by Ron Rivest back in 1991. You feed it any amount of data and it spits out a fixed 128-bit value, usually shown as a 32-character hexadecimal string. The same input always produces the same output, and even a one-byte change produces a completely different hash.
A quick example — notice how a single character flip scrambles the entire result:
echo -n "hello" | md5sum
# 5d41402abc4b2a76b9719d911017c592
echo -n "hellp" | md5sum
# a8bf0d31f14e0135c92f52c9c5aec0a5
That avalanche effect is the useful property. There's no way to predict how the output changes, so you can't nudge your input to steer the result.
Where MD5 is still fine to use
The important distinction: MD5 is broken for security, not for integrity against accidental corruption or as a fast non-cryptographic fingerprint. If an attacker isn't in your threat model, MD5 is fast, widely supported, and perfectly adequate.
- File checksums for accidental corruption. Verifying a download didn't get truncated or garbled in transit. If the bytes flip randomly, MD5 will catch it.
- Deduplication. Hashing file contents to spot duplicates before storing them. Collisions from random data are astronomically unlikely.
- Cache keys. Turning a long, messy request URL or a set of parameters into a short, stable key.
- ETags and change detection. Cheap way to tell whether a resource has changed since last time.
For a quick one-off — checking a value, generating an ETag by hand, or sanity-checking output from your code — a browser tool like the MD5 Hash Generator saves you from spinning up a shell. Paste the text, get the digest, done.
Where MD5 will burn you
Never use MD5 for anything an adversary can attack. The failures are well documented and practical, not theoretical:
- Password storage. MD5 is fast, which is exactly wrong for passwords. A modern GPU can compute billions of MD5 hashes per second, making brute-force and rainbow-table attacks trivial. Use bcrypt, scrypt, or Argon2.
- Digital signatures and certificates. Collision attacks against MD5 are real and cheap. Two different inputs can be crafted to produce the same hash, which breaks any signature scheme built on it.
- Deduplication where content is attacker-controlled. If someone can supply files and benefit from a collision, they can forge one. Use SHA-256 there.
- Message authentication. Don't roll your own MAC with MD5. Use HMAC with a strong hash if you must, though newer options are better.
The rule of thumb I use: if the answer to "what happens if two inputs collide?" is "someone gets to do something they shouldn't," MD5 is out. If it's "we accidentally treat two files as one, in a system where nobody's trying to trick us," MD5 is fine.
Common gotchas
Encoding matters
The hash is computed over bytes, not characters. If you hash the string "café" as UTF-8 you get a different result than Latin-1. This trips people up constantly when comparing hashes across languages or systems. Always be explicit about encoding.
import hashlib
data = "café"
print(hashlib.md5(data.encode("utf-8")).hexdigest())
# 84e5c6c3b3e5b3f... (utf-8 bytes)
print(hashlib.md5(data.encode("latin-1")).hexdigest())
# different result
Trailing newlines
The classic mismatch: echo "hello" adds a newline, so its hash differs from echo -n "hello". When your hashes "don't match" between two tools, whitespace is the first thing to check.
Hex casing and formatting
Most tools output lowercase hex, but some emit uppercase or Base64. Normalize before comparing. And when you compare, compare the full string — don't get clever with substring checks.
Don't hand-roll salting
If you inherit a legacy system that stores md5(password + salt), plan to migrate it. Rehash on next login into a proper password hash. Bolting more MD5 onto MD5 doesn't fix the underlying speed problem.
A short worked example
Say you're building a caching layer. You want a stable key derived from a request's method, path, and sorted query parameters. MD5 is a reasonable choice here — there's no security angle, you just need a compact, deterministic string.
import hashlib
def cache_key(method, path, params):
# sort params so key is stable regardless of order
parts = [method.upper(), path]
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/users", {"page": 2, "sort": "name"})
print(key)
# stable 32-char key you can use in Redis, a filename, etc.
Notice the sorting. Without it, ?page=2&sort=name and ?sort=name&page=2 would produce different keys for the same logical request, silently doubling your cache misses.
Should you just use SHA-256 instead?
Honestly, for new code where you don't have a compatibility constraint, SHA-256 is a fine default. It's only marginally slower for most workloads and it doesn't come with MD5's baggage or the need to explain to a security reviewer why you chose it. Reach for MD5 when something else requires it — an existing protocol, a legacy database column, an API that demands an MD5 signature — or when you specifically want the shorter 32-character output and have zero security concerns.
MD5 isn't dangerous because it's bad at what it does. It's dangerous because people use it for jobs it was never suited to. Keep it in the "fast fingerprint for trusted data" box and you'll be fine.
Photo by Chris Ried on Unsplash