MD5 has been around since 1992, and it refuses to die. You'll find it in checksums, cache keys, database dedup logic, legacy auth systems, and about half the tutorials you stumbled through when you were learning to code. It's fast, it's everywhere, and it's dangerously easy to reach for in situations where it'll quietly undermine your security.
So let's sort out when MD5 is a perfectly reasonable tool and when it's a liability you'll regret.
What MD5 actually is
MD5 (Message-Digest Algorithm 5) takes any input — a string, a file, a blob of bytes — and produces a fixed 128-bit value, usually shown as a 32-character hexadecimal string. The same input always produces the same output. Change a single bit and the whole digest changes.
A quick example of what the output looks like:
"hello" -> 5d41402abc4b2a76b9719d911017c592
"hello." -> 203ad5ffa1d7c650ad681fdff3965cd2
"" -> d41d8cd98f00b204e9800998ecf8427eNotice how adding one full stop completely scrambles the result. That avalanche effect is exactly what you want from a hash function.
When MD5 is fine
The key distinction: MD5 is broken as a security primitive, but it's still perfectly serviceable for non-adversarial work where you just need a deterministic fingerprint. Cases where I'd happily use it:
- Cache keys. Hashing a long URL or query parameters into a short, filesystem-safe key. Nobody's attacking your cache keys.
- Detecting accidental changes. Comparing a file before and after transfer to catch corruption. Not malicious tampering — just "did the bytes survive the trip".
- Deduplication. Grouping identical content together, with a byte-level comparison as a fallback for collisions.
- ETags and content versioning. A quick "has this changed?" signal.
In all of these, the threat model assumes nobody is deliberately crafting inputs to fool you. That's the line.
When MD5 will burn you
MD5 is cryptographically broken. Researchers can generate collisions — two different inputs that produce the same digest — on commodity hardware in seconds. That makes it unsuitable for anything where an attacker benefits from forging a match.
Do not use MD5 for:
- Password storage. This is the big one. MD5 is fast, which is the opposite of what you want for passwords. Attackers can compute billions of MD5 hashes per second on a GPU. Use
bcrypt,scrypt, orargon2instead. - Digital signatures or certificates. Collision attacks have been used to forge certificates in the wild.
- Integrity verification against tampering. If someone can swap a file and craft a matching hash, your check is worthless. Use SHA-256 for anything security-relevant.
- HMACs where you have better options. HMAC-MD5 is still reasonably resistant to the known attacks, but there's no reason to pick it over HMAC-SHA256 for new code.
Common gotchas
Encoding matters
MD5 operates on bytes, not characters. Hash the string "café" as UTF-8 and you get a different result than if you hash it as Latin-1. If two systems disagree on encoding, their hashes won't match even for "identical" text. Always be explicit about encoding.
Line endings in files
Hashing a text file on Windows (CRLF) and the same file on Linux (LF) gives different digests. If you're verifying file integrity across platforms, hash the raw bytes and don't let an editor or Git silently rewrite line endings.
Hex case and formatting
Some libraries output uppercase hex, others lowercase. Both represent the same value, but a naive string comparison will fail. Normalise before comparing.
It is not encryption
I still see people say "encrypt it with MD5". Hashing is one-way — there's no key and no way to reverse it to get the original input. What people call "cracking" MD5 is really just looking up precomputed hashes or brute-forcing, which works precisely because MD5 is fast and salt-free in naive implementations.
A worked example
Say you're building a simple asset pipeline and want content-based filenames so browsers cache aggressively but always pick up new versions. Here's the idea in Node.js:
const crypto = require('crypto');
const fs = require('fs');
function contentHash(filePath) {
const data = fs.readFileSync(filePath);
return crypto.createHash('md5').update(data).digest('hex');
}
const hash = contentHash('./styles.css');
const fingerprinted = `styles.${hash.slice(0, 8)}.css`;
console.log(fingerprinted);
// styles.9f86d081.cssWe take the first 8 hex characters as a short fingerprint. When the file contents change, the hash changes, the filename changes, and the browser fetches the fresh copy. This is a textbook good use of MD5: deterministic, fast, and completely non-adversarial. Nobody gains anything by colliding your CSS filename.
The same thing in Python is just as short:
import hashlib
with open('styles.css', 'rb') as f:
digest = hashlib.md5(f.read()).hexdigest()
print(f'styles.{digest[:8]}.css')Note the 'rb' — reading in binary mode avoids the line-ending gotcha above.
Checking a hash quickly
When I'm debugging an integrity mismatch, I don't want to spin up a script just to confirm what a string hashes to. A quick browser-based check saves time — you can paste a value into our MD5 Hash Generator and immediately see the digest, which is handy for comparing against whatever your code or a third-party API produced. It's also useful for sanity-checking encoding issues: if the tool's output doesn't match yours, you've probably got a bytes-vs-characters problem.
The short version
MD5 is a fine deterministic fingerprint and a terrible security control. If your use case involves an attacker who benefits from two inputs matching, reach for SHA-256 or a dedicated password hashing function. If you just need a fast, repeatable ID for caching, dedup, or change detection, MD5 is still pulling its weight decades later.
Know which situation you're in before you type createHash('md5'), and you'll stay out of trouble.
Photo by Pankaj Patel on Unsplash