Skip to content

Lesson 1 of 9 · 18m

Choosing a Hash Function: SHA-256, BLAKE2, and the MD5 Trap

What collision resistance actually means, which hashes to use today, and why MD5 keeps showing up where it shouldn't.

You hash things every day: deduplicating uploads, verifying a download, salting a password field you inherited from 2009. The trouble is that "hash" means two different things — an integrity checksum for corrupt disks, and a security primitive that must withstand an adversary with a GPU cluster. Confusing the two is how MD5 ends up authenticating firmware updates.

What a cryptographic hash actually guarantees

A cryptographic hash function takes arbitrary input and produces a fixed-size digest. For it to matter in a security context, three properties must hold:

  • Preimage resistance — given H(x), you can't recover x.
  • Second-preimage resistance — given x, you can't find x' with the same digest.
  • Collision resistance — you can't find any pair x, x' where H(x) = H(x').

Collision resistance is the first property to die as hash functions age. MD5 has been trivially broken since 2004 — two colliding PDFs take seconds on a laptop. SHA-1 fell in 2017 when Google demonstrated two colliding PDFs in the wild. Both are still fine as checksums against accidental corruption; both are disqualifying for anything an attacker touches.

What to reach for today

  • SHA-256 — the default. Hardware-accelerated on modern CPUs (SHA-NI), ubiquitous, and what TLS, git's new object format, and TLS certificates assume.
  • BLAKE2b-256 — faster than SHA-256 in software, no length-extension quirk, and the hash behind Argon2 and modern password schemes. Use it when hashing is hot code.
  • SHA-3 (Keccak) — a different construction (sponge), worth knowing as a hedge if SHA-2 falls, but rarely required.
# Same file, three different guarantees
sha256sum firmware.bin
b2sum firmware.bin
openssl dgst -sha3-256 firmware.bin

Digest truncation is a real design choice, not a stylistic one: using only the first 12 bytes of a SHA-256 digest gives you 96 bits of collision resistance — strong — but Git's 7-char short IDs are only ~28 bits and are collision-prone, which is why git checks context, not just the prefix.

Try this

Benchmark the difference yourself:

dd if=/dev/urandom of=/tmp/blob bs=1M count=512
time b2sum /tmp/blob
time sha256sum /tmp/blob

On most machines BLAKE2b wins noticeably when SHA-NI isn't in play. Then find a place in your codebase where an MD5 or SHA-1 digest is used to authenticate data — not just checksum it — and mark it for migration.

Field notes

  • A hash of secret || data is not a MAC. Merkle-Damgård hashes (MD5, SHA-1, SHA-256) are length-extendable, which is the whole subject of the next module lesson on HMAC. BLAKE2 and SHA-3 don't have that bug.
  • Git trusted SHA-1 for object identity for 15 years and got away with it until sha1collisiondetection became a real threat model; git now supports SHA-256 object format. Don't trust your system to outrun its hash the same way.
  • If you're hashing secrets (tokens, passwords), this lesson's primitives are the wrong tool — that's lesson two.