MD5 is one of those algorithms that refuses to die. It's been cryptographically broken for years, yet you'll still find it in checksums, cache keys, ETags, and the odd legacy auth system that someone really should have replaced by now. The trick is knowing when it's a sensible tool and when it's a liability.

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. Same input, same output, every time. Change a single byte and the result looks completely different.

md5("hello")       = 5d41402abc4b2a76b9719d911017c592
md5("hello.")      = 7a3c7050be61c0f5f0f0a2f8c8c72f35
md5("")            = d41d8cd98f00b204e9800998ecf8427e

That last one — the hash of an empty string — is worth memorising. If you ever see d41d8cd98f00b204e9800998ecf8427e in your logs, something handed MD5 nothing when it expected data.

Where MD5 is still fine

The important distinction: MD5 is broken as a cryptographic hash, not as a general-purpose hash. If you're not relying on it to resist a motivated attacker, it's quick, widely supported, and perfectly reasonable.

  • Cache keys and deduplication. Hashing a normalised request or a config blob to produce a short, stable key is a classic, legitimate use. Nobody's attacking your Redis key.
  • Non-security file integrity. Verifying that a download wasn't corrupted in transit is fine when you control both ends and aren't worried about tampering. Lots of mirrors still publish MD5 checksums for exactly this.
  • Partitioning and sharding. Hash a user ID, take it modulo N, and you've got a cheap, deterministic shard assignment.
  • Quick fingerprinting during development. "Did this file change?" is answered faster by comparing MD5s than by diffing bytes.

Where MD5 will burn you

Do not use MD5 for anything a human might attack. It's been possible to generate collisions — two different inputs with the same hash — in seconds for well over a decade.

  • Password storage. This is the big one. MD5 is fast, which is exactly wrong for passwords. Attackers can compute billions of MD5s per second on commodity GPUs. Use bcrypt, scrypt, or Argon2 instead — algorithms designed to be deliberately slow and salted.
  • Digital signatures or certificates. Collision attacks have been used to forge certificates. This is a solved-for-you problem: use SHA-256 or better.
  • Deduplication where inputs are adversarial. If someone can craft two files with the same MD5, your "unique" check is defeated.
  • Any new security feature. If the requirement mentions "security" and you're reaching for MD5, stop.

A short worked example

Say you're building a thumbnail cache. You want a stable filename for each source image URL plus the requested dimensions. MD5 is a good fit here because the input isn't secret and there's no attacker to worry about.

import hashlib

def cache_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(cache_key("https://cdn.example.com/cat.jpg", 200, 200))
# thumb_9f1a... .jpg

Notice two small but important details. First, I encode the string to bytes explicitly with UTF-8 — MD5 operates on bytes, not strings, and leaving encoding to chance is how you get different hashes on different platforms. Second, I use a delimiter (|) between fields. Without it, cache_key("ab", 1, ...) and cache_key("a", 11, ...) could collide after concatenation. Always separate variable-length fields.

Doing it in other languages

The API is boringly consistent across ecosystems, which is part of MD5's staying power.

// Node.js
const crypto = require("crypto");
crypto.createHash("md5").update("hello").digest("hex");

// PHP
md5("hello");

// Bash
echo -n "hello" | md5sum

That -n flag on echo trips people up constantly. Without it, the shell appends a newline and you hash "hello\n" instead of "hello", producing a completely different digest than your application code. If a hash doesn't match across tools, stray whitespace is the first thing to check.

Common gotchas

  • Encoding mismatches. As above — UTF-8 vs Latin-1 vs UTF-16 produce different hashes for the same "text". Pin your encoding everywhere.
  • Trailing newlines and whitespace. The number-one cause of "why don't these two MD5s match?" tickets.
  • Uppercase vs lowercase hex. 5D41... and 5d41... represent the same value but won't compare equal as strings. Normalise before comparing.
  • Assuming it's secure because it "looks random". It looks random to you. It doesn't to a GPU.
  • Treating it as unique. Collisions are rare by accident but trivial on purpose. Fine for caches, not fine for security guarantees.

Quick verification without writing code

When you just need to sanity-check a value or confirm your encoding assumptions, it's faster to paste a string into a browser than to open a REPL. Our MD5 Hash Generator gives you the digest instantly, which is handy for confirming that your app's output matches what a command-line tool produces — a great way to catch those sneaky newline bugs.

The bottom line

MD5 isn't evil; it's just misunderstood. As a fast, deterministic fingerprint for non-adversarial data, it's a perfectly good tool that's supported literally everywhere. The moment security enters the picture, reach for SHA-256 for integrity and Argon2 or bcrypt for passwords. Keep those two buckets straight in your head and you'll never get MD5 wrong.

One last rule of thumb: if the consequence of a collision is "the wrong thumbnail loads," MD5 is fine. If the consequence is "someone gets into an account," it isn't. Choose accordingly.

Photo by Pankaj Patel on Unsplash