HMAC and the pitfalls
Why not Hash(key || message): HMAC
The most naive idea for building a MAC would be to stick the key in front of the message and hash everything:
tag = H(key || message) (|| = concatenation)
It is appealing, but dangerous with the most widespread hash functions.
The length extension attack
Functions like MD5, SHA-1 and SHA-256 are built on the so-called Merkle-Damgård construction: they process the message block by block, and the final fingerprint is exactly the internal state of the machine after the last block.
Consequence: knowing H(key || message) amounts to knowing the internal state at that point. An attacker can then resume the computation where it stopped and append data of their choosing, without knowing the key:
They know: tag = H(key || message)
They compute: tag' = H(key || message || padding || APPEND)
... resuming from the state "tag", without ever seeing the key!
They obtain a valid tag for a message they have extended. The secret key did not protect it. This is the length extension attack.
The countermeasure: HMAC
HMAC (Hash-based MAC) elegantly solves the problem by applying the hash function twice, with two derivatives of the key:
HMAC(K, m) = H( (K XOR opad) || H( (K XOR ipad) || m ) )
where ipad and opad are two fixed constants (the bytes 0x36 and 0x5c repeated).
message m
|
K XOR ipad |
\ |
v v
H( (K^ipad) || m ) <--- inner hash
|
v intermediate result
K XOR opad |
\ |
v v
H( (K^opad) || ... ) <--- outer hash
|
v
HMAC
Why it works
The outer hash wraps the inner result. An attacker only sees the output of the outer layer; they cannot recover the internal state needed to extend the message. Length extension becomes impossible.
HMAC also has the advantage of being provably secure under reasonable assumptions about the hash function, and of working with any of them (HMAC-SHA256, HMAC-SHA512...).
In summary
The naive construction H(key || message) is vulnerable to the length extension attack on Merkle-Damgård type hashes (MD5, SHA-1, SHA-256): the fingerprint reveals the internal state, which makes it possible to extend the message without the key. HMAC fixes this with a double application of the hash — H((K XOR opad) || H((K XOR ipad) || m)) — whose outer layer masks the internal state. It is the standard construction, secure and independent of the chosen hash function.

