MD5 has been around since 1991, and despite being cryptographically broken for over a decade, it refuses to die. You'll still find it in file integrity checks, cache keys, ETags, and countless legacy systems. So it's worth knowing exactly what it does, where it's genuinely useful, and where using it will get you into trouble.

What MD5 actually is

MD5 (Message-Digest Algorithm 5) 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 yields the same output. Change a single byte and the result looks completely different.

That's the whole idea of a hash: a compact, deterministic fingerprint of some data. MD5 is fast, easy to compute, and available in basically every language's standard library.

$ echo -n "hello" | md5sum
5d41402abc4b2a76b9719d911017c592  -

Note the -n flag — without it, echo appends a newline and you'll get a different hash. That's the kind of small detail that causes hours of confused debugging when your hashes don't match across systems.

Where MD5 is still fine

MD5 is broken for anything security-sensitive, but it's perfectly serviceable for non-adversarial use cases where you just need a quick, stable fingerprint:

  • Cache keys and deduplication. Hashing a normalized request or a config blob to produce a short, consistent key is a classic use. If two inputs collide by accident, the odds are astronomically low for typical data.
  • Detecting accidental file changes. Comparing MD5 checksums to see whether a file changed during a copy or download works well against corruption or transmission errors.
  • Sharding and bucketing. Using the first few hex characters of an MD5 to distribute items across buckets is cheap and even.
  • Legacy compatibility. If an external system expects an MD5, you produce an MD5. No point fighting it.

The common thread: none of these assume an attacker is deliberately crafting inputs to break you.

Where MD5 will hurt you

Passwords

Never store passwords as MD5 hashes. MD5 is fast, which is exactly the wrong property for password hashing — attackers can compute billions of guesses per second on commodity GPUs. Rainbow tables for plain MD5 are freely available. Use a purpose-built password hash like bcrypt, scrypt, or Argon2, which are deliberately slow and salted.

Anything requiring collision resistance

MD5 is collision-broken. Researchers can generate two different inputs that produce the identical MD5 hash, and they can do it quickly. This means MD5 is unsafe for:

  • Digital signatures
  • TLS certificates (which is why CAs stopped using it)
  • Verifying that a file hasn't been maliciously tampered with

For integrity against a determined adversary, reach for SHA-256 or better.

Assuming a hash is "encryption"

Hashing isn't encryption. There's no key and no way to reverse it back to the original — that's the point. But it also means you can't "decrypt" an MD5. If you see a service claiming to decrypt MD5, it's really just looking the value up in a precomputed table of common inputs. Which, again, is exactly why you don't hash passwords with it.

A short worked example

Say you're building a simple asset cache and want a stable filename for a generated thumbnail based on its source URL and dimensions. MD5 is a reasonable fit here — no security requirement, just a deterministic key.

import hashlib

def thumbnail_key(url: str, width: int, height: int) -> str:
    raw = f"{url}|{width}x{height}"
    digest = hashlib.md5(raw.encode("utf-8")).hexdigest()
    return f"thumb_{digest}.jpg"

print(thumbnail_key("https://example.com/cat.png", 200, 200))
# thumb_9f4b1e2c... .jpg

A few things worth calling out:

  • Encoding matters. Always encode strings explicitly (utf-8 here). MD5 works on bytes, not characters, and relying on a default encoding is how you get mismatches between platforms.
  • Use a clear separator. Joining fields without a delimiter means ("ab", "c") and ("a", "bc") could produce the same input. The | avoids that ambiguity.
  • Normalize first. If the URL might arrive with trailing slashes or different casing, normalize it before hashing, or your cache will miss constantly.

When I need to sanity-check a hash by hand — verifying a checksum from a download page, or confirming my code produces what I expect — I'll paste the string into an MD5 Hash Generator rather than dropping into a terminal. It's quicker for one-offs and removes the newline gotcha entirely.

Verifying a file checksum

A concrete, legitimate MD5 use you'll hit often: confirming a downloaded file matches the published checksum.

$ md5sum ubuntu.iso
b1c2d3...e9f0  ubuntu.iso

Compare that against the checksum on the download page. If they match, the file arrived intact. Remember what this does and doesn't prove: it confirms no accidental corruption, but if an attacker controls both the file and the published checksum, they can swap both. For that threat model you'd want a signed checksum file (GPG) using a stronger hash.

Quick decision guide

  • Storing passwords? Argon2 or bcrypt. Never MD5.
  • Protecting against a malicious tamperer? SHA-256 minimum, ideally with a signature.
  • Need a fast, deterministic key for caching, dedup, or sharding? MD5 is fine.
  • Interoperating with a legacy system that demands MD5? Use it, and document why.

MD5 isn't evil — it's a tool that got pushed into jobs it was never meant for. Use it for cheap fingerprints of trusted data, keep it far away from anything security-critical, and mind the encoding and separator details. Get those right and it'll quietly do its job for years.

Photo by Chris Ried on Unsplash