Two constructions
Most hashes are built one of two ways:
- Merkle-Damgard: process the message in fixed blocks, chain each block's output into the next. Used by MD5, SHA-1, SHA-2. Internal state at the end becomes the digest. Property: if you know
H(m)and the length ofm, you can computeH(m || padding || m')for anym'without knowingm. - Sponge: absorb the message into a large internal state, then squeeze out the digest. Used by SHA-3 (Keccak) and BLAKE3. The internal state is bigger than the output, so length extension does not apply.
Length extension, demonstrated
Server stores a secret k and validates messages with tag = H(k || msg). Client sends (msg, tag). Server recomputes and checks.
An attacker who sees (msg, tag) and knows len(k) can use SHA-256's resumability to compute the correct tag for (msg || padding || arbitrary_extension) without knowing k. This is real. It happened to Flickr's API in 2009.
The fix is HMAC, or switching to SHA-3, BLAKE2, or BLAKE3 (none of which have length extension).
HMAC, in one diagram
HMAC computes H((K' XOR opad) || H((K' XOR ipad) || msg)), where ipad and opad are fixed constants and K' is the key padded to block size. The double hash means an attacker who extends the inner message still cannot extend through the outer hash. It works even with vulnerable underlying hashes (HMAC-SHA-1 is still safe today because of this, though SHA-1 itself is collision-broken).
The birthday paradox
To find a collision in an n-bit hash you need about 2^(n/2) attempts on average, not 2^n. A 128-bit hash gives 64-bit collision resistance, which is reachable today (2^64 ~ 10^19 operations).
That is why SHA-1's 160-bit output is dead (80-bit collisions are easily computable now; Google demonstrated SHA-1 collisions in 2017) and why modern systems use 256-bit hashes for 128-bit collision resistance, which is comfortably out of reach.
From hash to MAC to AEAD
The progression of authentication primitives:
- Hash: nobody can find a different message with the same digest. No key.
- MAC: keyed. HMAC is one construction; CMAC (block-cipher-based) is another.
- Polynomial MAC: GMAC, Poly1305. Very fast on modern hardware. Built on multiplication in a finite field.
- AEAD: encryption plus MAC in one primitive. AES-GCM = AES-CTR + GMAC. ChaCha20-Poly1305 = ChaCha20 stream + Poly1305.
The progression reflects what production systems actually need: confidentiality and integrity together, computed in one pass over the data, with a single tag to verify.
Practical advice
- For general-purpose hashing: SHA-256 (Merkle-Damgard, length-extension-vulnerable in raw form) or SHA-3 / BLAKE3 (sponge / tree, no length extension).
- For MACs: HMAC-SHA-256 is the safe boring choice. Poly1305 paired with a counter is the fast modern choice (used inside ChaCha20-Poly1305).
- Never roll your own MAC.
H(secret || message)is the most common foot-cannon in this area. - Use constant-time comparison for tags. A loose
==leaks timing.