Secret-prefix hashing builds a message authentication code (MAC) by hashing a secret key before the message: MAC(key, message) = H(key || message). A verifier repeats the hash with its copy of the secret, so a valid tag is meant to prove that the sender knew the key. section 7.2 section 7.3
Figure 7.5 Secret-prefix MACs: MAC(key, message) = H(key || message)
The construction is simply a keyed prefix followed by the complete message input to the hash. section 7.3
This simple design is unsafe with Merkle–Damgård hash functions such as MD5, SHA-1, and SHA-2. Their digest exposes the hash state needed to continue processing: an attacker who captures a tag for an unknown key-prefixed message can compute a valid tag for that message plus correctly formed padding and appended data. section 7.2 section 7.3.2
Figure 7.9 Because the internal state of the hash function is available to the attacker in the digest, the hash calculation can be “resumed.”
A captured digest can act as the starting state for hashing attacker-chosen continuation data. section 7.3.2
Forging a bank transaction
A bank API signs sorted request parameters with SHA-256 after prefixing a client secret. Starting from an intercepted signed transaction, an attacker estimates the original key-prefixed length, adds SHA-256 padding, resumes hashing from the captured digest, and appends parameters for a larger transaction. The modified request passes MAC verification without revealing the client secret. section 7.3.1 section 7.3.2
Do not treat a raw keyed hash prefix as a general-purpose MAC:
- Avoid H(key || message) with Merkle–Damgård hashes; knowing a valid tag can enable extension of the authenticated input. section 7.3.2
- Prefer HMAC for message authenticity because it resists both length-extension and collision attacks. section 7.6
- A sponge-based hash such as Keccak avoids length extension in this construction, but HMAC remains the recommended choice. section 7.6 section 10.1.5
