MD5 is one of those algorithms you bump into constantly — in file checksums, cache keys, legacy database columns, Git's older plumbing cousins, and a hundred config files. It's fast, it's everywhere, and it's widely misunderstood. Half the internet will tell you "MD5 is broken, never use it," and the other half quietly uses it for cache keys without a second thought. Both can be right, depending on the job.

What MD5 actually is

MD5 (Message-Digest Algorithm 5) is a cryptographic 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 gives the same output, and even a tiny change in the input produces a completely different hash.

A couple of properties worth internalising:

  • Deterministic: hashing "hello" today gives the same result as hashing it next year.
  • Fixed length: a 2-byte input and a 2-gigabyte file both produce a 32-character hex digest.
  • One-way: you can't reverse the hash back to the original input.
  • Avalanche effect: change one character and roughly half the output bits flip.

A quick look at the output

md5("hello")       = 5d41402abc4b2a76b9719d911017c592
md5("hello.")      = 88f0d5049c9636e9c57f85cce64f1d5e
md5("")            = d41d8cd98f00b204e9800998ecf8427e

Notice how adding a single full stop produces a totally unrelated digest. That empty-string hash, d41d8cd9..., is one you'll start recognising on sight once you've debugged enough caching bugs.

When MD5 is a reasonable choice

MD5's speed and short, convenient output make it genuinely useful for non-security tasks where you just need a quick fingerprint:

  • Cache keys: hashing a normalised request URL or a set of query params into a short, stable key.
  • Deduplication: spotting identical files or records by comparing digests instead of full contents.
  • Integrity checks against accidental corruption: verifying a download finished without bit errors (not against a malicious attacker — more on that below).
  • Sharding and bucketing: using the first few hex characters to distribute items across partitions.

In all of these cases you care about "is this the same thing I saw before?" and nothing more. MD5 is fine there.

When MD5 is the wrong tool

Here's the part people get wrong. MD5 is cryptographically broken. Researchers demonstrated practical collision attacks years ago — you can construct two different inputs that produce the same MD5 hash. That kills it for any security-sensitive use:

  • Password storage: never. Use bcrypt, scrypt, or Argon2, which are deliberately slow and salted.
  • Digital signatures or certificates: no. Collisions let an attacker swap content without changing the hash.
  • Integrity verification against a motivated attacker: no. Use SHA-256 or better.

The simple rule: if an adversary benefits from forging a matching hash, MD5 is out. If you're only guarding against random accidents or need a cheap fingerprint, it's acceptable.

Common gotchas

Encoding matters

MD5 hashes bytes, not characters. If you hash the string "café" as UTF-8 you'll get a different digest than if you hash it as Latin-1. When two systems disagree on a hash, mismatched text encoding is the usual culprit. Always be explicit about encoding.

Line endings and whitespace

Hashing a file with Windows CRLF line endings produces a different result than the same file with Unix LF endings. Trailing newlines, trimmed whitespace, and BOM markers all change the digest. Normalise before you hash if you expect cross-platform consistency.

Treating it like encryption

Hashing is not encryption. There's no key and no "decrypt" step. If you see code trying to "decode" an MD5 hash, someone's confused — the only way back is a lookup table or brute force, which is exactly why MD5 is unsafe for passwords.

Salting myths

Adding a salt doesn't magically make MD5 safe for passwords. Salting prevents rainbow-table reuse, but MD5 is still far too fast — an attacker can try billions of guesses per second on commodity GPUs. Slowness is the point of a proper password hash, and MD5 has none.

A short worked example

Say you're caching the rendered output of a report that depends on a few parameters. You want a stable cache key. Here's a tidy Node.js version:

const crypto = require('crypto');

function cacheKey(params) {
  // Normalise so key order doesn't change the result
  const normalised = JSON.stringify(
    Object.keys(params).sort().reduce((acc, k) => {
      acc[k] = params[k];
      return acc;
    }, {})
  );

  return crypto
    .createHash('md5')
    .update(normalised, 'utf8')
    .digest('hex');
}

cacheKey({ year: 2024, region: 'EU' });
// -> "a1f3..." (stable regardless of key order)

The sorting step matters: { year, region } and { region, year } serialise differently as JSON, so without normalising you'd get two different keys for the same logical request. This is the kind of subtle bug that only shows up as a mysteriously low cache hit rate.

Python does the same thing with barely more ceremony:

import hashlib, json

def cache_key(params):
    normalised = json.dumps(params, sort_keys=True)
    return hashlib.md5(normalised.encode('utf-8')).hexdigest()

Checking quick values by hand

When you're debugging and just want to confirm what a given string hashes to, you don't need to spin up a REPL. A browser-based MD5 Hash Generator will give you the digest instantly, which is handy for sanity-checking a value your code produced or comparing against a checksum someone sent you.

The takeaway

MD5 isn't dead — it's just misfiled. Think of it as a fast, compact fingerprinting tool for non-adversarial work: cache keys, dedup, rough integrity checks. The moment security enters the picture — passwords, signatures, anything an attacker wants to forge — reach for SHA-256 for integrity and Argon2 or bcrypt for passwords instead. Keep that distinction clear and MD5 will serve you well for exactly the jobs it's good at, without quietly becoming a liability in the ones it isn't.

Photo by Jake Walker on Unsplash