04 / 04 Designing for failure
  1. ← Authenticated encryption: why encryption alone is never enough
  2. 01 Encryption is not enough
  3. 02 The nonce: the detail that breaks everything
  4. 03 Binding ciphertext to its context
  5. 04 Designing for failure
Authenticated encryption: why encryption alone is never enough · 04 / 04

Designing for failure

Authenticate before parsing, opaque errors, trust boundaries: how the way a system fails determines its security.

In module 1, you saw that a padding oracle Padding oracle An attack that exploits any observable signal (error message, response time, detectable behavior) revealing whether the padding of an encrypted block is valid. By iterating ciphertext modifications and observing responses, an attacker can decrypt the message without knowing the key. It is one of the classic reasons why encryption without authentication is dangerous. allowed an adversary to reconstruct a plaintext byte by byte, without ever knowing the key, simply by observing whether the server accepted or rejected a message. What the adversary exploited was not the algorithm. It was the system’s behaviour on failure: two distinct responses, depending on the cause of the rejection. This observable distinction was the oracle.

AEAD eliminates padding oracles by removing from the adversary any information about the result of decryption. But a system that correctly uses an AEAD algorithm can still recreate an oracle, through the way it fails, parses, or validates incoming bytes. Design errors at this level annul cryptographic guarantees.

This module examines the design rules that close these flaws. It is the final module of the course: the principles that follow build on the integrity and confidentiality from module 1, the nonce uniqueness from module 2, and the context binding from module 3.

By the end of this module, you will be able to explain why operation order (verify before parsing) is a security property, define what an opaque error is and why it is necessary, apply the trust boundary principle to a processing pipeline, and identify cases where a correctly encrypted system remains vulnerable through its failure logic.

Authenticate before parsing

The rule is absolute: verify the tag before touching a single byte of the ciphertext.

The order seems obvious once stated, but violations are common. A system that parses encrypted bytes before verifying their authenticity exposes its parsing logic to data entirely controlled by the adversary. Every conditional branch in that parser becomes a potential leak: the system’s behaviour (response time, error message, return code) can vary based on the structure of the bytes, and this variation is observable.

Consider an example. A service receives an encrypted API request whose first field is a payload length. If the service reads this field and allocates a buffer of the announced size before verifying the tag, an adversary can send an arbitrary length value and observe whether the allocation succeeds or fails. The parser has processed unauthenticated data. The adversary has gained information.

The same reasoning applies to any conditional logic executed before verification: decoding a format, reading a version field, validating an identifier. If these operations run on unauthenticated bytes, they contribute to the oracle.

Tag verification is a precondition, not one step among others in the processing pipeline.

A correct AEAD scheme already applies this principle in its open operation: the tag is verified first, and if verification fails, no plaintext byte is returned. The rule extends to the application layer: even after a successful open, the obtained plaintext must be treated as data from an external source until its content is validated.

Parse, don’t validate

Once the tag is verified, the plaintext bytes are authentic, but they can still be malformed. The “parse, don’t validate” rule says: transform raw bytes into well-typed values at the input boundary, once, so that the rest of the code cannot receive malformed data.

The distinction is precise. To validate is to check that data satisfies a condition (for example: “this field is a positive integer”) and continue manipulating the raw data if the condition is true. To parse is to construct a typed representation from the raw bytes, and return an error if construction fails.

The advantage of parsing is structural: once the boundary is crossed, types guarantee data consistency. A “length” field that exists only as a bounded unsigned integer cannot be negative elsewhere in the code, by construction of the type, not by repeated checking.

This principle applies to every layer that receives bytes from the outside: token decryption, network packet reading, message decoding. The parsing boundary coincides with the trust boundary. What crosses it is usable. What fails to cross it is rejected, without partial processing.

The opaque error

A system that fails in multiple observable ways is an oracle. The design consequence is direct: a single failure mode, with no exploitable information.

What this means in practice:

The opaque error applies to internal logs as well: a detailed log accessible via an external interface (a metrics endpoint, a diagnostic page) can become a leak if the adversary can consult it. Internal logs can be precise; what is returned to the client must not be.

Trust boundaries

A trust boundary is the point where uncontrolled bytes enter the system. Everything that comes from outside this boundary is hostile by default.

In a system using AEAD, the trust boundary is the open operation: what arrives before the open is unauthenticated, what comes out of a successful open is authenticated. But this boundary has practical implications for system design.

Validate in both directions. The seal must verify that the fields included in the AAD are well-formed, not merely pass them as-is. If the sender includes a recipient identifier in the AAD without validating that it corresponds to an existing recipient, the ciphertext can be forged with an invalid identifier and the recipient will only detect the error after decryption. Validating context fields at seal time reduces the exploitable surface at the point of emission.

Treat everything from the outside as hostile. This includes the encrypted bytes (obvious), but also the metadata accompanying the ciphertext: an HTTP header, a session identifier transmitted separately, a URL parameter. These external values must be validated independently, before being used to select the key, the algorithm, or the recipient.

The cryptographic layer is not the only line of defence. AEAD guarantees that an authenticated message has not been altered. It does not guarantee that the message was correctly constructed by a legitimate sender. An internal adversary (a compromised component, a sender forging a valid but malicious context) can produce an authentic ciphertext with malicious content. Application-level validations after the open remain necessary.

Correct order versus incorrect order

Left: tag verification precedes all parsing. The error is opaque. Right: the parser processes unauthenticated bytes before verification, creating an observable oracle.

The diagram illustrates the structural difference. In the correct order, the adversary cannot influence the parser’s behaviour, because the parser only receives data whose authenticity has already been established. In the incorrect order, every branch of the parser is potentially observable by the adversary, who entirely controls the bytes presented.

The pattern to retain

Touch no byte you have not authenticated. A single failure mode, with no exploitable detail.

These two rules reinforce each other. The first closes the oracle the parser could create. The second closes the oracle the error mechanism could create. Together, they ensure that the system’s observable behaviour on failure is constant: a rejection, a single way, with no information about the cause.

Quiz

Quiz
  1. 1. A service receives a message encrypted with AES-GCM. Before verifying the tag, it reads the first field of the ciphertext to allocate a buffer. Why is this a vulnerability?

  2. 2. In module 2, you saw that tag comparison must be performed in constant time. What general principle does this illustrate for failure design?

  3. 3. A system seals a token with AES-GCM, including in the AAD the user identifier and the message direction (module 3). During open, it returns distinct error messages depending on whether the tag is invalid or the AAD does not match. What is the problem?

  4. 4. Bringing together all four modules: a system uses ChaCha20-Poly1305, generates random one-hundred-and-ninety-two-bit nonces, includes the domain identifier and recipient in the AAD, verifies the tag before parsing, and returns a single error on failure. What property remains to be verified for a complete implementation?

Key takeaways