A service decrypts attacker-supplied ciphertext and returns one error for "bad padding" and a different one for "bad content." Explain the general class of attack this enables, and state the ordering rule for encryption and authentication that prevents it.
answer
- one bit per query is enough — that's an oracle
- padding check → ~128 queries per byte, no key
- generic error alone leaves timing and side effects
- encrypt-then-MAC: verify before decrypt, generically secure
- tag must cover IV, header, version, record id
basics
~20 sAny behaviour that differs based on decrypted bytes turns the service into a decryption oracle; a padding check leaks enough to recover plaintext byte by byte without the key. Fix: authenticate the ciphertext and reject before decrypting — encrypt-then-MAC, or an authenticated mode.
solid answer
~50 sThe class is a **decryption oracle**: the attacker submits modified ciphertexts and learns one bit per query — was the decrypted result well-formed? With CBC padding that bit is enough to recover plaintext one byte at a time, roughly 128 queries per byte on average, with no key knowledge. The general rule the bug violates is: *nothing derived from an unauthenticated ciphertext may influence observable behaviour.* The ordering that enforces it is **encrypt-then-MAC** — compute the tag over the ciphertext, including the IV and any unencrypted header — because verification happens before the cipher ever touches attacker data, and it is provably secure for any secure encryption and MAC. **MAC-then-encrypt** forces you to decrypt attacker input in order to reach the MAC, which is exactly what creates the oracle. **Encrypt-and-MAC** leaks plaintext equality through a deterministic tag. Also verify tags with a constant-time comparison and return one generic error; suppressing the error text alone leaves timing, response size and side effects as the oracle.
code
text · 10 linesWRONG (mac-then-encrypt shape)
plaintext = decrypt(key, ciphertext) # attacker data enters the cipher
if unpad(plaintext) fails: return "bad padding" <-- oracle
if mac(plaintext) invalid: return "bad mac"
RIGHT (encrypt-then-mac)
if not constant_time_equal(tag, mac(k_mac, header || iv || ciphertext)):
return GENERIC_FAILURE # cipher never ran
plaintext = decrypt(k_enc, ciphertext)
# from here on the bytes are known-authenticgo deeper
Know that returning different errors for different decryption failures lets an attacker recover plaintext without the key, and that the fix is to check an authentication tag first.
Explain the padding-oracle mechanism at the level of "attacker controls the previous CBC block, so one bit of feedback yields one byte," and name encrypt-then-MAC as the correct order.
Generalise from padding to any secret-dependent observable, argue why encrypt-then-MAC is generically secure while the other two orders are conditionally secure, and cover constant-time comparison and what the tag must cover.
Treat it as a protocol-design rule — unauthenticated bytes never reach a parser — and review formats for authenticated headers, key separation, version fields and downgrade paths rather than reviewing individual call sites.
## What an oracle is An *oracle* is any interface that answers a question about a secret. The attacker does not need it to answer "what is the plaintext?" — a single bit of feedback per query is enough, provided that bit depends on the decrypted data and the attacker can query repeatedly. The service in the question is a textbook one: it decrypts whatever the attacker sends and behaves differently depending on whether the decrypted bytes had valid padding. It has thereby delegated a secret-dependent decision to an untrusted party. ## Why the padding case is so powerful Block modes that need whole blocks pad the final block with a deterministic, self-describing pattern (for instance, *n* bytes each of value *n*). Decryption strips the padding and rejects the message if the pattern is not well-formed. In CBC, each plaintext block is the block-cipher decryption of the ciphertext block, XORed with the *previous* ciphertext block. The attacker fully controls that previous block. So they can take a target block, prepend a block of their own choosing, and vary it until the service stops complaining about padding — which tells them that the last byte of the intermediate value XORed with their chosen byte equalled `0x01`. That one equation gives them the intermediate byte, and the intermediate byte plus the *real* previous ciphertext byte gives them the real plaintext byte. Move to the next byte, target `0x02 0x02`, and repeat. About 128 queries on average per byte; no key, no cryptanalysis. And because they now know the intermediate values, they can *also* encrypt arbitrary plaintext of their choosing. ## The general lesson, which is bigger than padding Padding is the classic instance, but the class is broader. Any of these is an oracle: - a distinguishable error code or message ("malformed" versus "unauthorized") - a timing difference, because the code path after a successful unpad does more work - a difference in response size, or in whether a subsequent request succeeds - a side effect — a log line, a metric, a retry, an email, a row written - a difference visible only to the attacker as a *behaviour*, such as a connection being closed early So "return the same error message" is a fix on the *detection* rung, not the structural one. Attackers who lost the error text have historically switched straight to timing. The invariant to state in an interview is: **no value derived from an unauthenticated ciphertext may influence anything the attacker can observe** — not the response, not the timing, not a side effect. The only way to honour that is to establish authenticity *before* decrypting. ## Composition order Three ways to combine an encryption scheme and a MAC: **Encrypt-then-MAC.** Encrypt the plaintext, then compute the tag over the ciphertext — including the IV/nonce and any unencrypted header such as a version or key identifier. On receipt: check the tag first and abort on failure without invoking the cipher at all. This composition is *generically* secure: if the encryption is IND-CPA and the MAC is unforgeable, the result gives integrity of ciphertexts and hence chosen-ciphertext security, for any such pair. That genericity is the property you are buying — the security does not depend on happening to pick a compatible pair. **MAC-then-encrypt.** Compute the tag over the plaintext, then encrypt plaintext-and-tag together. To verify, you must first decrypt attacker-supplied data — which means running the cipher, stripping padding, and parsing, all before you know the message is authentic. This is precisely the shape that produces padding oracles. It is not universally insecure, but it is secure only for particular combinations and requires great care to implement without a timing side channel; that is an unattractive bargain when a generic construction exists. **Encrypt-and-MAC.** Encrypt the plaintext and separately tag the plaintext, sending both. The tag is a deterministic function of the plaintext, so identical plaintexts produce identical tags — an equality leak that undoes the randomization the mode provided. It also still requires decryption before verification. The practical conclusion: prefer a standard authenticated mode, which bakes the ordering in. If you must compose by hand, use encrypt-then-MAC, cover every byte that is not encrypted, and use a separate key (or properly derived subkeys) for encryption and authentication. ## Constant-time comparison Once you are verifying a tag, *how* you compare matters. A comparison that returns on the first differing byte leaks, through timing, how many leading bytes matched. An attacker who can measure that turns forging a 16-byte tag from an infeasible 2^128 search into roughly 256 attempts per byte — a few thousand queries. Compare tags with a fixed-time routine that examines every byte and accumulates the difference, and make the failure path indistinguishable from the success path in observable time and behaviour. This is the same principle as above, applied to the tag rather than to the padding. ## What the associated data must cover A correctly ordered construction still fails if the tag does not cover everything an attacker can influence. The IV/nonce, the key identifier, the format version, the record or row identifier, the tenant — if any of these travels in the clear and is not authenticated, it can be swapped. Authenticating the ciphertext but not the header means the attacker can redirect a valid ciphertext to a different record, or downgrade the version field to select a weaker parser. ## The ladder Structural separation: use an authenticated mode, or encrypt-then-MAC, so that unauthenticated input never reaches a parser. Escaping/transformation-equivalent: careful hand-built verification with constant-time comparison — correct in principle, but you now own the details. Validation: sanity-checking decrypted plaintext, which happens too late. Detection: uniform error messages and alerting on decryption-failure rates, which raises the cost of an attack without preventing it.
- If we return an identical error message for every failure, is the oracle closed?No. The attacker can fall back on timing — the code path that successfully strips padding then parses does measurably more work than one that bails immediately — and on any other observable difference such as response size, connection behaviour, a retry, a log entry or a metric. Uniform errors raise the cost but sit on the detection rung; the structural fix is to reject unauthenticated ciphertext before decrypting.
- Should the encryption key and the MAC key be the same value?No. Reusing one key across two primitives means the security argument depends on the two constructions not interacting badly, which is not something you can assume. Use two independent keys, or derive two subkeys from a single master key with a key-derivation function, so that each primitive is analysed on its own key. Standard authenticated modes handle this internally.
- Does using an authenticated mode remove the need for constant-time comparison?The mode's own implementation still has to compare the tag in constant time, and reputable implementations do. Where you must stay careful is any comparison you write yourself — session tokens, HMAC-signed webhooks, API signatures, reset codes — since the same byte-at-a-time leak turns an infeasible forgery into a few thousand queries.
saying these in an interview costs you the question
- "We changed the error to a generic message, so the oracle is gone" — timing and side effects remain.
- Believing the attacker needs the key or a cryptanalytic break to exploit a padding oracle.
- Choosing MAC-then-encrypt because "the MAC is protected by encryption, so it is stronger."
- MACing only the ciphertext body and leaving the IV, version or key identifier unauthenticated.
- Comparing authentication tags with a normal equality function that short-circuits.