MD5 has been around since 1991, and despite being cryptographically broken it refuses to die. You'll still find it in checksums, cache keys, ETags, and countless legacy systems. That means every developer eventually has to work with it — and know when reaching for it is fine versus when it's a genuine liability.

What MD5 actually is

MD5 (Message-Digest Algorithm 5) is a hash function. You feed it any input — a string, a file, a byte stream — and it spits out a fixed 128-bit value, usually shown as a 32-character hexadecimal string. The same input always produces the same output, and even a one-byte change produces a wildly different hash.

Here's what that looks like:

md5("hello")  = 5d41402abc4b2a76b9719d911017c592
md5("hello.") = 8f2c73f83e2f9c8f7c3ba1e1c9f0d3a4  (illustrative)

That avalanche effect — tiny input change, totally different digest — is the useful property. What MD5 is not is reversible. There's no "un-hash" function. Given a digest, you can't compute the original input by design.

Fixed size, arbitrary input

One thing people forget: the output is always 32 hex characters, whether you hashed one letter or a 4GB video file. That predictability is exactly why it's handy for things like fixed-length keys and identifiers.

When MD5 is still a reasonable choice

The honest answer is: for non-security use cases where you just need a fast, compact fingerprint. Some examples where I still use it without any guilt:

  • Cache keys. Hash a set of query parameters into a short, stable key. You don't care about attackers here — you care about a deterministic string.
  • Detecting accidental changes. Comparing whether two files or config blobs are identical. If the MD5s match, they're almost certainly the same content.
  • Deduplication. Grouping identical uploads or records by their digest before doing expensive work.
  • ETags and content fingerprints. Plenty of CDNs and build tools still use MD5-based hashes in asset filenames like app.9f8c2a.js.

In all of these, the threat isn't a malicious adversary crafting a collision — it's random corruption or simple comparison. MD5 is fast, well-supported in every language, and produces short output. That's a fine fit.

Where MD5 will burn you

Now the warnings, because this is where careers get dented.

Never use MD5 for passwords

This is the big one. Storing password hashes with MD5 — even salted — is not acceptable in 2024. MD5 is fast, which is the opposite of what you want for password storage. An attacker with a stolen database can compute billions of MD5s per second on commodity GPUs. Use bcrypt, scrypt, or argon2 instead. Those are deliberately slow and tunable.

Never use MD5 for security signatures

MD5 is vulnerable to collision attacks — two different inputs producing the same digest. This isn't theoretical; practical collisions have been demonstrated for years, including forged certificates. If you're verifying that content came from a trusted source and hasn't been tampered with by an adversary, use SHA-256 or better, ideally within an HMAC.

Don't confuse hashing with encryption

I've seen "we encrypt the data with MD5" in a real ticket. MD5 doesn't encrypt anything — it's one-way. There's no key and no way to get the original back. If you need to protect data you'll later read, you want actual encryption (AES, for example), not a hash.

Rainbow tables exist

Because MD5 is unsalted and fast, common inputs have been precomputed into massive lookup tables. Searching 5f4dcc3b5aa765d61d8327deb882cf99 online instantly tells you it's password. Any unsalted hash of predictable data offers essentially no protection.

A quick worked example

Let's generate a cache key from request parameters. In Node.js:

const crypto = require('crypto');

function cacheKey(params) {
  // Sort keys so ordering doesn't change the hash
  const canonical = JSON.stringify(
    Object.keys(params).sort().reduce((acc, k) => {
      acc[k] = params[k];
      return acc;
    }, {})
  );
  return crypto.createHash('md5').update(canonical).digest('hex');
}

cacheKey({ page: 2, sort: 'name', filter: 'active' });
// => a stable 32-char hex string, same every time

Note the canonicalisation step. {page:2, sort:'name'} and {sort:'name', page:2} represent the same logical request but serialise differently, producing different hashes. Sorting the keys first fixes that. This bug bites people constantly — the hash is deterministic, but only if your input is deterministic.

The same in Python:

import hashlib

def md5_hex(text: str) -> str:
    return hashlib.md5(text.encode('utf-8')).hexdigest()

print(md5_hex("hello"))
# 5d41402abc4b2a76b9719d911017c592

Watch the encoding. md5 operates on bytes, so you must encode your string first. Hashing the same text as UTF-8 versus UTF-16 gives different results — a classic source of "the hashes don't match between our two services" bugs.

Verifying and testing quickly

When I'm debugging a mismatch, I don't reach for a REPL first — I paste both values into a quick online tool to confirm the expected digest. Our MD5 Hash Generator is handy for exactly that: check what a known string should hash to, then compare against what your code produces. It's usually faster than writing a throwaway script, and it rules out silly mistakes like a trailing newline or wrong encoding.

The short version

  • MD5 is a fast, one-way, 128-bit fingerprint — great for non-security fingerprinting.
  • Use it for cache keys, dedup, and detecting accidental changes.
  • Never use it for passwords, digital signatures, or anything an attacker could exploit.
  • Mind your input: canonicalise data and be explicit about text encoding.

Treat MD5 as a tool with a narrow, legitimate niche. Inside that niche it's dependable and quick. Step outside it, and you're one security review away from a very awkward conversation.

Photo by Chris Ried on Unsplash