Skip to main content
Hash Functions Beginner

MD5

Ronald Rivest designed MD5 in 1991. By 2004 it was broken. Here's the algorithm, and why real collisions ended its cryptographic career.

PL
Pashalis Laoutaris
July 16, 2025
8 min read

Interactive MD-5 Hash Visualizer

🔐 MD5 Hash Visualizer

5
Enter text and click a button to start!

MD5

Ronald Rivest designed MD5 at MIT in 1991, publishing it as RFC 1321. It quickly became the internet’s default hash function. Digital signatures, password storage, file checksums, and early SSL certificates all leaned on it.

That run ended in 2004. Wang, Feng, Lai, and Yu published a practical collision attack that year. By 2008, Flame malware used forged MD5 collisions to fake a Microsoft code-signing certificate. NIST deprecated MD5 for cryptographic use the following year.

MD5 is still worth understanding. It’s a Merkle-Damgård construction, processing 512-bit blocks through four rounds of bitwise operations. That same structure is the direct ancestor of SHA-1 and SHA-2. Its collapse is also the clearest real-world lesson in cryptographic agility.

Table of Contents

How MD5 Works

MD5 takes an input of any length and produces a 128-bit digest. It processes the message in 512-bit blocks.

  1. Pad the message. Append a single 1 bit, then zeros, stopping 64 bits short of a 512-bit multiple. Append the original length as a 64-bit integer.
  2. Initialize four 32-bit registers, A, B, C, D, with fixed constants.
  3. Process each block through 4 rounds of 16 operations. Each round uses a different bitwise function of B, C, and D:
    • Round 1: F(B,C,D) = (B ∧ C) ∨ (¬B ∧ D)
    • Round 2: G(B,C,D) = (B ∧ D) ∨ (C ∧ ¬D)
    • Round 3: H(B,C,D) = B ⊕ C ⊕ D
    • Round 4: I(B,C,D) = C ⊕ (B ∨ ¬D)
  4. Each operation mixes in a message word and a constant. It then rotates and adds the result back into the registers: A = B + ((A + F(B,C,D) + X[k] + T[i]) <<< s). The constants T[i] come from the sine function, ⌊2³² × |sin(i)|⌋, chosen to avoid hidden structure.
  5. Concatenate the final registers. After all blocks are processed, A || B || C || D is the 128-bit digest.

Python Implementation

The four round functions and the sine-derived constants above translate directly into a working implementation:

import math

S = [7,12,17,22, 7,12,17,22, 7,12,17,22, 7,12,17,22,
     5, 9,14,20, 5, 9,14,20, 5, 9,14,20, 5, 9,14,20,
     4,11,16,23, 4,11,16,23, 4,11,16,23, 4,11,16,23,
     6,10,15,21, 6,10,15,21, 6,10,15,21, 6,10,15,21]

K = [int(abs(math.sin(i + 1)) * 2**32) & 0xFFFFFFFF for i in range(64)]

def left_rotate(x, c):
    return ((x << c) | (x >> (32 - c))) & 0xFFFFFFFF

def md5(message: bytes) -> bytes:
    a0, b0, c0, d0 = 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476

    msg = bytearray(message)
    orig_len_bits = (len(message) * 8) & 0xFFFFFFFFFFFFFFFF
    msg.append(0x80)
    while len(msg) % 64 != 56:
        msg.append(0)
    msg += orig_len_bits.to_bytes(8, 'little')

    for offset in range(0, len(msg), 64):
        chunk = msg[offset:offset + 64]
        M = [int.from_bytes(chunk[i:i+4], 'little') for i in range(0, 64, 4)]

        A, B, C, D = a0, b0, c0, d0
        for i in range(64):
            if i < 16:
                F = (B & C) | (~B & D); g = i
            elif i < 32:
                F = (D & B) | (~D & C); g = (5 * i + 1) % 16
            elif i < 48:
                F = B ^ C ^ D; g = (3 * i + 5) % 16
            else:
                F = C ^ (B | ~D); g = (7 * i) % 16

            F = (F + A + K[i] + M[g]) & 0xFFFFFFFF
            A, D, C = D, C, B
            B = (B + left_rotate(F, S[i])) & 0xFFFFFFFF

        a0 = (a0 + A) & 0xFFFFFFFF
        b0 = (b0 + B) & 0xFFFFFFFF
        c0 = (c0 + C) & 0xFFFFFFFF
        d0 = (d0 + D) & 0xFFFFFFFF

    return b''.join(v.to_bytes(4, 'little') for v in (a0, b0, c0, d0))

if __name__ == "__main__":
    for text in (b"Hello", b"Hello!"):
        print(f"MD5({text!r}) = {md5(text).hex()}")

Running this gives MD5(b'Hello') as 8b1a9953c4611296a827abf8c47804d7 and MD5(b'Hello!') as 952d2c56d0485958336747bcdd98590d. Adding one character flips roughly half the output bits, the avalanche effect covered below. I checked this against Python’s own hashlib.md5 first, on several inputs. Those included the empty string and the standard “quick brown fox” pangram.

Limitations

This is a genuine, complete implementation of the real (broken) algorithm, not a simplified stand-in:

  • No streaming interface. This function takes the entire message as one bytes object. hashlib’s incremental .update() interface is more practical for large inputs or data arriving in chunks.
  • Not hardened against timing side-channels. This is a direct translation of the specification, for readability. It isn’t audited for constant-time behavior.
  • Reproduces MD5’s brokenness faithfully, by design. This code can’t patch over the collision vulnerabilities described below. It exists to show which arithmetic produces those weaknesses, not to offer a “safer” MD5.
  • Never use this, or any MD5 implementation, for security-critical purposes. As the rest of this article documents at length, that applies regardless of implementation quality.

Security Properties and the Avalanche Effect

A hash function like MD5 is meant to satisfy three properties. Preimage resistance: given a hash, you can’t find a message that produces it. Second-preimage resistance: given a message, you can’t find a different one with the same hash. Collision resistance: you can’t find any two messages with the same hash at all.

MD5 also exhibits a strong avalanche effect. A tiny input change should flip roughly half the output bits. The Python Implementation above demonstrates this directly. Adding a single ! to “Hello” changes the digest completely. Nothing visibly ties the two outputs together.

Collision resistance is the property that actually broke. A generic birthday attack needs about 2⁶⁴ attempts to find any collision in a 128-bit hash. Wang et al.’s 2004 attack found real collisions far faster than that. It runs in seconds on ordinary hardware.

Cryptanalytic Attacks

  • Collision attacks. Wang, Feng, Lai, and Yu’s 2004 breakthrough exploited differential characteristics in MD5’s compression function. That made it possible to construct two different messages with identical hashes. This directly compromises digital signatures. Sign one message, and the signature is valid for its colliding twin too.
  • Chosen-prefix attacks. Stevens et al. extended this in 2007. An attacker picks two arbitrary, meaningful prefixes. Suffix blocks are then computed to make the final hashes collide. In 2012, Flame malware used exactly this to forge a Microsoft code-signing certificate.
  • Length extension. Because of its Merkle-Damgård structure, knowing MD5(secret || message) and the secret’s length is enough. An attacker can compute MD5(secret || message || extension), without ever learning the secret. HMAC exists specifically to close this gap; see the HMAC guide.
  • Rainbow tables. For password hashing specifically, MD5’s speed works against it. Billions of hashes per second on consumer GPUs, plus precomputed tables, recover unsalted passwords trivially.

Current Uses and Alternatives

MD5 still shows up in non-cryptographic roles. Git object identifiers (paired with SHA-1), cache keys, corruption checksums, and load balancers. None of these depend on collision resistance. Nobody is deliberately trying to forge a Git object.

For anything security-critical, the replacement depends on the job:

Use case Use instead
Digital signatures, certificates SHA-256 or SHA-3
Password hashing Argon2, bcrypt, or scrypt
File integrity, general hashing SHA-256 or BLAKE2
Message authentication HMAC-SHA256

FAQ

Why is MD5 no longer considered secure?

Wang et al. demonstrated practical collision attacks in 2004, letting attackers generate different inputs with the same hash. It’s also vulnerable to chosen-prefix attacks, length extension, and rainbow tables for password hashing.

Can MD5 still be used for anything?

Yes, for non-cryptographic purposes: file integrity checksums in trusted environments, caching, deduplication, or load balancing. None of these depend on collision resistance. It should never be used for signatures or password hashing.

SHA-256 or SHA-3 for signatures and general hashing. Argon2, bcrypt, or scrypt for passwords. HMAC-SHA256 for message authentication.

How does MD5 compare to SHA-256 or BLAKE2?

MD5 is faster but broken. SHA-256 is slower but secure. BLAKE2 matches MD5’s speed while offering SHA-256-level security, making it the better modern default.

Why is MD5 still worth learning about?

Its simple design and thoroughly documented break make it an ideal case study. It teaches hash function properties, cryptanalysis, and why systems need cryptographic agility from the start.

References

  • RFC 1321: “The MD5 Message-Digest Algorithm.” rfc-editor.org/rfc/rfc1321
  • Wang, X., et al. (2004). “Collisions for Hash Functions MD4, MD5, HAVAL-128 and RIPEMD.”
  • Stevens, M., et al. (2007). “Chosen-prefix collisions for MD5 and applications.”
  • RFC 6151: “Updated Security Considerations for the MD5 Message-Digest Algorithm.” rfc-editor.org/info/rfc6151
  • Wikipedia. “MD5.” en.wikipedia.org/wiki/MD5