MAC Forgery — the construction, not the primitive, decides the attack

verified · provenanceused 0× by assistantshash

Producing a valid (message, tag) pair without knowing the key — or under a relaxed game where the message doesn't even have to be fully attacker-chosen (existential vs selective forgery). This is a hub: there is no single "MAC forgery technique", there are half a dozen, and picking the wrong one wastes hours. The right attack depends entirely on *which construction* is behind the tag.

The signal that gives it away

Before touching an exploit, identify the construction — it's usually visible from the challenge source or the API description, not something you have to infer blind:

- `H(secret || message)` — a plain hash function called on the concatenation of a secret and the message, no HMAC-style nesting. Look for source that literally does hashlib.sha256(secret + msg).hexdigest() or a protocol doc that says "the signature is the SHA-1 of the shared secret plus the request". This is the single most common home-rolled MAC mistake in CTFs. - "AES-CBC, keep the last ciphertext block as the tag" — raw CBC-MAC, no separate finalization step, no second key. Route to CBC-MAC Forgery — splice a new message onto a known tag without the key rather than trying to solve it here; the exploit details (splice, XOR correction, IV-as-input variant) live on that page. - Poly1305 or GMAC where the nonce is reused, predictable, or attacker-influenced. These are *one-time* authenticators — the "one-time" is load-bearing. A repeated nonce is a full authentication-key break, not a partial leak. - A verification endpoint that returns different timing (or even just different error codes) for "tag correct so far" vs "tag wrong from byte 0". Any hand-rolled == on a hash/HMAC digest in a language without built-in constant-time compare is suspect. - A short tag (4, 8, 16 bits truncated from a real MAC, or a custom scheme that clearly doesn't need the full digest). If blind forgery attempts are not rate-limited, the tag length alone tells you whether brute force is the actual intended path. - Contrast check that saves the most time: if the construction is genuinely HMAC (nested, two compression-function passes, RFC 2104 shape) or a sponge-based MAC (Keccak/SHA-3 based), length-extension does *not* apply — see "What does NOT work" below. Don't spend an hour on it before confirming which family you're looking at.

How to exploit it

Route by construction — each of these is documented in depth on its own page; this hub exists so you don't guess:

- `H(secret || m)` over a Merkle–Damgård hash (MD5, SHA-1, SHA-2/256/512 unmodified) → length-extension forgery. You need the digest, the message length, and *an estimate or brute-forceable range* of the secret length (often leaked by the challenge, or just brute-force a handful of plausible lengths). See Hash Length Extension — forge H(secret‖msg‖extra) from a digest alone and Merkle–Damgård Construction — the digest IS the internal state for the padding-reconstruction mechanics. This is the case that broke Flickr's API signature scheme in the wild — same bug, same fix (switch to HMAC). - Raw CBC-MAC over variable-length messages → splice two known (message, tag) pairs into a forged third. See CBC-MAC Forgery — splice a new message onto a known tag without the key for the exact XOR-correction construction and the IV-as-input variant. - Poly1305/GMAC with a repeated nonce → the tag difference between two messages under the same nonce gives a polynomial equation in the unknown authentication key; solving it (or accumulating enough equations) recovers the key outright, after which you can tag anything. See Poly1305 Nonce Issues — nonce reuse turns a one-time MAC into a solvable polynomial for the algebra. - Timing-oracle byte-at-a-time forgery, when the only leak is comparison timing on a non-constant-time ==. Field-tested skeleton — works whether the target is a raw hash MAC, HMAC, or anything else with a naive verifier, because the attack targets the *comparison*, not the MAC construction itself:

``python guess = bytearray(TAGLEN) for i in range(TAGLEN): best = max(range(256), key=lambda b: time_request(guess[:i] + bytes([b]) + guess[i+1:])) guess[i] = best `` In practice this needs many repeated timing samples per byte (median-of-N, not single-shot) to beat network jitter — treat one noisy sample as worthless and budget for statistical averaging, not a single request per candidate byte.

- Truncated or short tags with an unthrottled oracle: blind submission succeeds with probability 2^-n for an n-bit tag per attempt. If the challenge allows many trials (no lockout, no rate limit), this is a legitimate primary attack, not a last resort — combine with Birthday Attack — the square-root speedup that governs any collision search reasoning once you're colliding against a *set* of acceptable tags rather than one exact value.

What does NOT work

- Length-extension against HMAC. HMAC's nested construction (H(key_outer || H(key_inner || m))) means the outer hash never processes attacker-visible internal state — there's no chaining value to resume from, so the entire length-extension trick has nothing to grab onto. This is *the* textbook mitigation, and it's why "is this HMAC or a hand-rolled H(secret||m)" is the first question worth answering, not an afterthought. If you see HMAC-SHA256 or HMAC-SHA1 explicitly named, stop chasing length extension and look at key recovery / timing / nonce angles instead. - Length-extension against sponge-based hashes (SHA-3/Keccak, and hence anything using them as a secret-prefix MAC). The sponge construction never exposes the full internal state as output — only a squeezed portion — so there's no way to reconstruct the state needed to continue the computation. Truncated SHA-2 variants (SHA-384, SHA-512/256) get the same immunity for a different reason: the truncated bits of internal state are simply never revealed. - Splicing/forging raw-CBC-MAC style against CMAC, ECBC-MAC, or any "encrypt-last-block-under-a-second-key" variant. These exist specifically to close the raw CBC-MAC gap; treat "CMAC" as a stop sign, not a synonym for CBC-MAC. Confirm which one is actually implemented before spending effort — CTF challenge text is sometimes loose with the naming. - Poly1305/GMAC key-recovery when nonces are genuinely unique per message. As a one-time authenticator used correctly, Poly1305 has no known practical forgery; the entire attack surface here is the nonce-reuse case. Don't assume "it's Poly1305, so nonce reuse must be present somewhere" — verify the nonce derivation (counter, random-but-large, etc.) before committing to this path. - Assuming byte-at-a-time timing forgery will work against any modern crypto library's default compare. Constant-time comparison primitives (hmac.compare_digest in Python, crypto.timingSafeEqual in Node, etc.) exist precisely to kill this attack, and most mainstream libraries use them by default for MAC verification. The realistic target is home-rolled ==/memcmp comparison in application code wrapping an otherwise-fine MAC, not the MAC primitive itself. If the verification path is out of your control (pure black-box oracle) and looks well-behaved under repeated timing, this is a dead end — pivot to construction-level attacks above rather than grinding noisier and noisier timing samples. - Treating a short/truncated tag as automatically brute-forceable. If the endpoint rate-limits or locks out after failed attempts, 2^-n per try does not translate into a practical attack — check for throttling before committing to blind guessing as the plan.

Related

CBC-MAC Forgery — splice a new message onto a known tag without the key Hash Length Extension — forge H(secret‖msg‖extra) from a digest alone Merkle–Damgård Construction — the digest IS the internal state HMAC Fundamentals — the nested MAC that tells you when to STOP trying length-extension Poly1305 Nonce Issues — nonce reuse turns a one-time MAC into a solvable polynomial Birthday Attack — the square-root speedup that governs any collision search Timing Attack — recovering a secret from how long a secret-dependent operation takes Sponge Construction — why length-extension does NOT work on SHA-3/Keccak

Verified against

65 claims checked against these sources

Source: Sinapsi — verified compositional memory, queryable by LLMs. Query this wiki live from your assistant over MCP, or build your own verified wiki (public, or private for your team). CC BY 4.0 — reuse with attribution to Sinapsi.