02 / 04 The nonce: the detail that breaks everything
  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 · 02 / 04

The nonce: the detail that breaks everything

Vital uniqueness, random versus counter, birthday bound and constant time: why the least secret number in the system is also the most dangerous.

In the previous chapter, you saw that AEAD guarantees confidentiality and integrity in a single operation, provided its parameters are used correctly. The nonce is one of those parameters. And its usage constraint is absolute.

In 2016, researchers published the “Forbidden attack” against AES-GCM. The attack does not break the algorithm. It exploits a single usage error: reusing the same nonce with the same key. The result: not only are both messages partially exposed, but the internal authentication key (the GHASH key, derived from the hashing polynomial of GCM) can be reconstructed by the adversary. Once the GHASH key is known, integrity collapses entirely: the adversary can forge valid tags for any message of their choice.

By the end of this chapter, you will be able to explain why nonce uniqueness is a security constraint and not a convention, demonstrate what reuse reveals via the two-time pad intuition, distinguish between random and counter strategies for nonce generation, and understand why tag comparison must be performed in constant time.

The nonce

The nonce Nonce A value used exactly once with a given key. Uniqueness, not secrecy, is the critical property: reusing a nonce with the same key completely breaks the scheme. A 192-bit random nonce (XChaCha20) makes collisions negligible, while a 96-bit counter (AES-GCM, ChaCha20-Poly1305) requires careful management to never exceed 2^32 messages per key. Source: RFC 8439 (short for “number used once”) is a value transmitted in plaintext with each encrypted message. Its role is precise: to guarantee that two encryptions of the same plaintext with the same key produce different ciphertexts.

What matters is not its confidentiality, but its uniqueness. A nonce can be read by anyone on the network without affecting security. However, using the same nonce twice with the same key, even for two distinct messages, breaks fundamental guarantees.

The size and generation strategy vary by primitive. AES-GCM uses a ninety-six-bit nonce. XChaCha20-Poly1305 uses a one-hundred-and-ninety-two-bit nonce. These differences have concrete consequences for how nonces are generated, as we will see shortly.

Reuse: the catastrophe

The two-time pad intuition

To understand why nonce reuse is catastrophic, it is enough to look at what a stream cipher does under the hood.

A stream cipher (such as the keystream in ChaCha20 or CTR mode under AES-GCM) generates a pseudo-random stream KsK_s from the key kk and the nonce NN. The plaintext PP is XORed with this stream:

C=PKs(k,N)C = P \oplus K_s(k, N)

If two messages P1P_1 and P2P_2 are encrypted with the same kk and the same NN, their ciphertexts are:

C1=P1KsandC2=P2KsC_1 = P_1 \oplus K_s \quad \text{and} \quad C_2 = P_2 \oplus K_s

An adversary who has both ciphertexts computes:

C1C2=(P1Ks)(P2Ks)=P1P2C_1 \oplus C_2 = (P_1 \oplus K_s) \oplus (P_2 \oplus K_s) = P_1 \oplus P_2

The keystream KsK_s cancels out. The adversary directly obtains the XOR of the two plaintexts. If one of the two messages is known (or guessable, such as a fixed protocol header), the other is immediately recoverable. This is what is called the “two-time pad”, by analogy with the one-time pad whose fundamental invariant it breaks.

The component below illustrates this mechanism. Edit the two messages and observe how the XOR of the ciphertexts directly reveals the combination of the plaintexts. Modify the first message to resemble a known protocol header, and watch what the “recovered” field displays for the second.

C1 XOR C2 (observed by the adversary)
00 00 00 00 00 00 14 17 08 1b
Message 2 recovered (knowing message 1)
auth: admi

The Forbidden attack: when integrity collapses too

Plaintext leakage is only the first consequence. In AES-GCM, the authentication tag is computed via a polynomial function (GHASH) parameterised by an internal key HH, derived from the main key. If two messages encrypted with the same nonce and the same key are observed, an adversary can construct a system of equations over HH and solve it. Once HH is known, they can forge a valid tag for any ciphertext of their choice.

Nonce reuse does not degrade AES-GCM security: it annihilates it.

Nonce reuse: the keystream cancels out (two-time pad), and in AES-GCM the GHASH key can be reconstructed, enabling tag forgery.

Random versus counter

Two main strategies exist for generating unique nonces. They have very different risk profiles.

Random nonce

Drawing a nonce uniformly at random from a sufficiently large space makes collisions negligible. XChaCha20-Poly1305 uses a one-hundred-and-ninety-two-bit nonce: it would take on the order of two to the sixty-four messages encrypted under the same key for the collision probability to reach only two to the minus sixty-five, a negligible risk at any real-world scale. These nonces can be generated with a secure cryptographic generator without any coordination between parties.

AES-GCM with its ninety-six-bit nonce is more constrained. The birthday bound tells us that the collision probability grows approximately as the square of the number of nonces generated divided by the space size: after two to the thirty-two messages (roughly four billion), the collision probability with a random ninety-six-bit nonce exceeds two to the minus thirty-three, which becomes concerning in high-throughput systems. The NIST recommendation limits the use of a single AES-GCM key to two to the thirty-two messages with random nonces.

Counter nonce

A monotone counter guarantees uniqueness without a probabilistic limit: each value is distinct by construction. This is the recommended strategy for AES-GCM in deterministic systems (a single producer, a single key, persistent state).

The fragility is operational. A counter must be:

  • Persistent: a restart must not reset the counter to zero. If the value is not saved to durable storage, nonce zero will be reused after every restart.
  • Unique per instance: in a distributed system, two nodes with the same key each maintaining their own counter will inevitably produce the same values. You need either centralised coordination or a unique node identifier included in the nonce.
  • Non-recyclable: a key whose counter has reached its limit can no longer be used. Key rotation must be planned for.

The resulting design rule: a guarantee that rests on a usage condition (nonce uniqueness) must be made impossible to violate by construction, not entrusted to human discipline. Prefer primitives with large nonces (XChaCha20-Poly1305) when state management is difficult. When a counter is necessary, make it inaccessible to the application layer and have it managed by the cryptographic layer.

Constant time

Tag verification at open must be performed in constant time Constant-time A property of an implementation whose execution duration does not depend on secret values, eliminating timing side channels. It is essential for authentication tag comparison and sensitive cryptographic operations. ChaCha20-Poly1305 is naturally constant-time in software, whereas AES requires hardware instructions (AES-NI) to achieve this property. : the execution time must not depend on the values being compared.

Why? Because a naive comparison (byte by byte, returning on the first difference) creates a timing side channel. An adversary who can measure a server’s response time gains information: if the rejection arrives faster, the first bytes of the tag are incorrect. If the rejection takes slightly longer, the first bytes match. By repeating enough attempts, they can reconstruct the valid tag byte by byte, without ever knowing the key.

The defence is simple to state: compare both tags via an XOR of all bytes, then check that the cumulative result is zero, with no early conditional branching.

The constant-time question extends to the primitive itself. ChaCha20 is designed to depend only on additions, rotations, and XOR, operations whose execution time does not vary with the data. AES in pure software (without AES-NI instructions) uses substitution tables (S-box) accessed in memory: the processor cache can reveal which entries were accessed, creating a cache side-channel leak. With AES-NI, the hardware instructions are constant time by construction.

Naive comparison: rejection time varies with the position of the first differing byte, creating a timing channel. Constant-time comparison: all bytes are processed before any branching.

Choosing a primitive

The choice between AES-GCM and (X)ChaCha20-Poly1305 depends on the deployment context, not on the absolute superiority of one over the other.

AES-GCM is preferable when the environment has AES-NI (x86-64 servers, ARM Cortex-A with ARMv8-A crypto). Performance is excellent and constant time is guaranteed by hardware. The main constraint is managing the ninety-six-bit nonce: a well-managed counter is necessary in high-throughput or distributed systems.

ChaCha20-Poly1305 is preferable in environments without hardware AES acceleration: older mobile devices, microcontrollers, WASM, native JavaScript. Performance is comparable or better, and constant time is ensured by algorithmic construction, without relying on hardware. The one-hundred-and-ninety-two-bit nonce of XChaCha20 considerably simplifies management by allowing random generation without practical limits.

Cryptographic agility Crypto-agility The ability of a format or protocol to migrate to new cryptographic primitives without breaking existing data. It is typically implemented via a version byte at the head of the ciphertext, allowing old records to be decoded and new ones encrypted with the current algorithm. It is essential for preparing a post-quantum migration. recommends not coupling application code to a specific primitive: encapsulating the choice in an abstraction layer allows migration without rewriting if a vulnerability is discovered.

Quiz

Quiz
  1. 1. Why is nonce uniqueness critical, and not its confidentiality?

  2. 2. What can an adversary do who observes two AES-GCM messages encrypted with the same nonce and the same key, in the worst case?

  3. 3. In a distributed system where two nodes use the same AES-GCM key and each maintains a local counter starting at zero, what is the problem?

  4. 4. Why must tag comparison be performed in constant time?

Key takeaways