MD5 has been declared dead more times than I can count, yet it keeps turning up in codebases, build systems, and cloud APIs. That's because "broken for cryptography" and "useless" are two very different things. If you understand what MD5 actually guarantees, you'll know exactly when reaching for it is fine and when it'll get you into trouble.
What MD5 actually is
MD5 is a hash function that takes any input — a string, a file, a byte stream — and produces a fixed 128-bit output, usually written as a 32-character hexadecimal string. The same input always produces the same output. Change a single byte and the output changes completely.
A couple of properties matter in practice:
- Deterministic: hash the same data twice, get the same digest. This is what makes it useful for verification and lookups.
- Fixed length: a 2 KB file and a 2 GB file both hash to 32 hex characters.
- Fast: MD5 is cheap to compute, which is a feature for checksums and a liability for password storage (more on that below).
Where MD5 is genuinely fine
Despite the reputation, plenty of legitimate uses remain. The key: none of them depend on an attacker being unable to forge a collision.
Cache keys and deduplication
Need a compact, deterministic key from a longer string or a set of parameters? MD5 works. Hash the normalised request, use the digest as your cache key. If two inputs collide by accident the odds are astronomically small, and even then a cache miss isn't a security event.
Checksums for accidental corruption
Verifying that a file downloaded or copied without bit-rot or truncation is a classic use. You're guarding against random corruption, not a malicious actor swapping the file. That's why some CDNs and object stores still expose MD5-based ETags.
Change detection
Store the MD5 of a config blob or a rendered template. If the hash changes, something changed. Simple, fast, and good enough for triggering a rebuild or invalidating a cache.
Where MD5 will burn you
MD5 is cryptographically broken. Researchers can generate two different inputs that produce the same digest on demand. That collapses any use case where trust depends on uniqueness being unforgeable.
- Password storage: never. MD5 is fast and unsalted by default, which is exactly wrong for passwords. Use bcrypt, scrypt, or Argon2.
- Digital signatures / certificates: forbidden in modern standards for good reason.
- Integrity against tampering: if you need to prove a file wasn't maliciously altered, use SHA-256 or better, ideally with an HMAC and a secret key.
- Any "is this the same as a known-bad value" security check where an attacker controls the input.
The rule of thumb I use: if the answer to "could someone benefit from forging a match?" is yes, don't use MD5.
Common gotchas
Encoding matters
MD5 operates on bytes, not characters. Hashing the string "café" as UTF-8 gives a different digest than Latin-1. If you get mismatches between two systems, encoding is the first thing to check.
Line endings and whitespace
File checksums differ if one machine wrote CRLF and another LF. Trailing newlines added by an editor will change the hash of a config file. Normalise before hashing if that matters.
Hex vs base64 vs raw
The same digest can be represented as lowercase hex, uppercase hex, or base64. Two systems that agree on the algorithm can still disagree on the string. Pin the representation explicitly.
Uppercase confusion
Some libraries emit uppercase hex, some lowercase. String comparisons will fail silently. Lowercase everything on both ends and compare that.
A worked example
Here's the same MD5 computed in a few languages, all producing the standard lowercase hex string.
# Python
import hashlib
print(hashlib.md5(b"hello").hexdigest())
# 5d41402abc4b2a76b9719d911017c592
# Node.js
const crypto = require('crypto');
console.log(crypto.createHash('md5').update('hello').digest('hex'));
// 5d41402abc4b2a76b9719d911017c592
# Shell (macOS / Linux)
echo -n "hello" | md5sum
# 5d41402abc4b2a76b9719d911017c592 -Note the -n in the shell version. Without it, echo appends a newline and you'll hash "hello\n", giving a completely different digest. That single character trips people up constantly.
When I just need to check a value quickly — say I'm comparing an ETag from an API response against what I expect, or sanity-checking that two teams are hashing the same normalised input — I don't bother writing a script. I paste it into the MD5 Hash Generator and compare. It's faster than opening a REPL, and it removes the "did I forget -n?" class of mistakes.
Hashing files vs strings
For files, stream the data rather than loading it all into memory:
# Python: hash a large file in chunks
import hashlib
def md5_file(path):
h = hashlib.md5()
with open(path, 'rb') as f:
for chunk in iter(lambda: f.read(8192), b''):
h.update(chunk)
return h.hexdigest()Reading a multi-gigabyte file into a single buffer just to hash it is a good way to OOM a container. The chunked pattern above is the one to reach for.
Bottom line
MD5 is a fine tool for the non-security jobs it's good at: cache keys, dedup, change detection, and guarding against accidental corruption. It's fast, ubiquitous, and every language ships it in the standard library. Just keep it far away from anything involving trust, passwords, or an adversary who might want to forge a match. For those, SHA-256, HMAC, or a proper password hash are the right calls.
Know which category your problem falls into before you pick the algorithm, and MD5 will keep doing quiet, useful work for years.
Photo by Mohammad Rahmani on Unsplash