Step 1 of 5 · Reading · ~2 min
Read
Padding & Length
Length-Extension Attack
You hashed a secret-prefix MAC: tag = SHA-256(secret || message). Anyone who knows tag and the message length can forge SHA-256(secret || message || glue || suffix) for ANY suffix they choose — without ever learning secret.
This is why bare SHA-256 (and SHA-1, SHA-512) is unsafe as a MAC, and why HMAC exists.
Why it works
The output of SHA-256 IS the internal state after the last block was absorbed. Given tag = SHA(M), an attacker can:
- Parse
taginto 8 big-endian uint32s — that's the state(a,b,c,d,e,f,g,h). - Compute the glue padding that was applied to
M: the0x80byte, zero padding to 448 mod 512 bits, plus|M|in bits. - Continue the SHA-256 algorithm from the recovered state with ANY suffix, padding it as if
|M| + |glue|bytes had already been processed. - The resulting hash equals
SHA(M || glue || suffix).
A concrete attack
secret = "supersekret" (unknown to attacker, 11 bytes)
message = "user=guest&role=user" (visible to attacker)
tag = SHA-256(secret || message) (visible — published as the MAC)
The attacker:
state = parse(tag) # 8 uint32 words
glue = pad_for_length(11 + 20) # 0x80 + zeros + 64-bit (31*8)
forged = continue(state, "&role=admin", processed = 11 + 20 + |glue|)
# Server then computes:
real = SHA-256(secret || message || glue || "&role=admin")
# real == forged. The attacker promoted themselves to admin.
Defenses
| Construction | Length-extendable? |
|---|---|
SHA-256(secret || M) | Yes — broken |
SHA-256(M || secret) | No — the secret is absorbed last |
| HMAC-SHA-256 | No |
| SHA-3 (Keccak) | No (sponge) |
| BLAKE2/BLAKE3 | No (keyed mode) |
Suffix-MAC — SHA-256(M || secret) — is not length-extendable, because the attacker cannot append past the secret. It is still a bad choice: a single collision SHA-256(M) == SHA-256(M') transfers straight to the tags, so its security rests on collision resistance rather than the much weaker assumption HMAC needs.
Always use HMAC when you need a MAC from SHA-2. The double-hash structure of HMAC prevents the attacker from continuing from the published tag.
Real-world incidents
- Flickr API (2009): used
MD5(secret || params)for signing — patched by switching to HMAC. - Ruby on Rails / Rack cookie signing (early versions): signed session cookies with
hash(secret || data), so a forged cookie could be extended without the secret; fixed by moving to HMAC. - Many home-grown auth schemes: any time you see
hash(key || ...)in API signing, it's worth checking.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…