Merkle-Damgard State Leakage: Exploiting Secret Concatenation in API Auth
Years ago, during an API security audit for a payment gateway integration, I found an authentication header constructed as SHA256(secret + request_body). The backend engineering team assumed that since the secret key never traversed the wire, forged signatures were mathematically impossible. The API passed unit tests, handled production traffic, and seemed bulletproof. It was broken by design due to how Merkle-Damgard hash functions handle internal padding.
The Mechanics of Merkle-Damgard State Leakage
Standard cryptographic hash functions like MD5, SHA-1, and SHA-256 process incoming data streams in fixed-size blocks, typically 512 bits. When input text ends, the algorithm appends bit padding containing a trailing 1 bit, zeros, and an 8-byte integer representing total message length. The final state vector output becomes the digest you see in hexadecimal.
The security flaw stems from state continuity. Because the final hash digest is simply the internal state of the compression function after processing the final block, an attacker who intercepts a valid signature already holds the complete state of the hash engine. If the secret key prepends the message, the attacker does not need to know the secret bytes to extend the data.
Length extension attacks allow an attacker to take a valid signature H(secret || message) and compute H(secret || message || padding || extra_data) without ever knowing the secret key.
By calculating the expected bit length of the original secret + payload, an attacker appends the necessary binary padding locally, injects malicious query parameters or JSON fields, and resumes the hashing algorithm using the intercepted hash as the initial vector. The server processes the entire payload, strips valid padding internally, and confirms the computed signature matches the forged header.
Demonstrating the Attack with HashPump
Tooling like hashpump automates initial vector injection and bit padding generation. Suppose a service sends an administrative request with a signature calculated over secret + "user=guest&role=user".
# Intercepted signature for user=guest&role=user (secret length 16 bytes)
# Original Hash: 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
hashpump -s "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" \
-data "user=guest&role=user" \
-add "&role=admin" \
-k 16
The command produces a modified payload string containing raw hexadecimal escapes for the padded null bytes alongside a brand new valid SHA-256 signature. When sent to a vulnerable endpoint, the application reads user=guest&role=user, skips the binary padding during parameter parsing, and accepts the last parameter role=admin, granting elevated privileges.
To eliminate length extension vulnerability, replace raw concatenation with Keyed-Hash Message Authentication Code (HMAC). HMAC runs two nested hash passes using distinct inner and outer keys derived from your secret:
import hmac
import hashlib
secret_key = b"supersecretkey123"
payload = b"user=guest&role=user"
# Correct: HMAC-SHA256 prevents state continuation
signature = hmac.new(secret_key, payload, hashlib.sha256).hexdigest()
print(signature)
The outer hashing pass scrambles the internal state vector, making it impossible for external actors to resume the state. Alternatively, modern hash algorithms like SHA-3, BLAKE2, or BLAKE3 use sponge structures or tree hashing architectures that naturally resist length extension. If you are still relying on raw secret prepending in legacy webhooks or API authentication tokens, refactor those signatures to HMAC-SHA256 immediately.