arthrop0d

Brief articles on varied topics.

AES-GCM Nonces, Counters, and Authentication Tags

cryptographycomputer security

AES-GCM is an authenticated-encryption scheme: it encrypts data so outsiders cannot read it and produces an authentication tag so they can detect modification. Its nonce selects a unique encryption stream for a key; its counter mode generates that stream; and its tag authenticates both the ciphertext and optional unencrypted metadata.

The central rule is simple:

Never reuse a nonce with the same AES key.

A repeated nonce does not merely weaken AES-GCM. It can expose relationships between plaintexts and can eventually let an attacker create ciphertexts that pass authentication.

The problem AES-GCM solves

Encryption alone provides confidentiality, but not integrity. An attacker might be unable to read an encrypted request while still changing a field, reordering bytes, or replaying altered data. Older systems often combined a confidentiality mode with a separate message authentication code, or used encryption without authentication at all. Those designs were difficult to compose correctly: the receiver had to authenticate exactly the right bytes, in the right order, before acting on the plaintext.

GCM packages the two jobs together. The sender supplies:

The result is ciphertext and an authentication tag, commonly 128 bits long. The receiver needs the same key, nonce, associated data, ciphertext, and tag. It verifies the tag before accepting the plaintext.

You have probably encountered the rule in a library error such as “nonce reused,” “invalid tag,” or “authentication failed.” Those messages are describing the safety boundary of GCM, not an incidental API preference.

How the nonce and counter produce encryption

AES itself encrypts one 128-bit block at a time. GCM uses AES in counter mode: it encrypts a sequence of counter values to produce a pseudorandom keystream, then combines that keystream with the plaintext using XOR.

Conceptually, for each block:

keystream_block = AES(key, counter_block)
ciphertext_block = plaintext_block XOR keystream_block

XOR is reversible, so the receiver generates the same keystream and computes:

plaintext_block = ciphertext_block XOR keystream_block

For the common 96-bit nonce format, GCM constructs an initial counter block from the nonce and a fixed starting value. It then increments the counter for each plaintext block. The nonce therefore chooses the stream, while the counter makes successive blocks in that stream different.

The nonce is not secret. It is normally sent beside the ciphertext. Its purpose is to ensure that, under one key, GCM does not generate the same keystream for two different encryption operations. A 96-bit nonce is common because it has an efficient, standardized construction, but the important property is uniqueness per key, not secrecy.

Applications usually meet this requirement in one of two ways:

A counter must not reset after a crash if the key remains in use. If nonce management cannot guarantee uniqueness, rotate to a new key as part of the recovery or key-lifecycle design.

How the authentication tag works

GCM authenticates more than the ciphertext. It also authenticates the associated data and encodes lengths into the calculation, so an attacker cannot silently alter either encrypted content or protected headers.

Internally, GCM derives a hash key from AES and feeds the associated data and ciphertext through GHASH, a hash-like calculation designed for 128-bit blocks. It then combines that result with an AES-generated value associated with the nonce. In simplified form:

hash_key = AES(key, all_zero_block)
mask     = AES(key, initial_counter_for_nonce)
tag      = mask XOR GHASH(hash_key, associated_data, ciphertext)

The exact block formatting and padding are part of the GCM specification, but the structure explains the nonce rule. The tag is not just a checksum: it is keyed, and it covers the complete authenticated message.

On decryption, the receiver recomputes the expected tag and compares it with the supplied tag using a constant-time comparison. If they differ, the ciphertext must be rejected. Code should not use unauthenticated plaintext before that check succeeds.

What nonce reuse reveals

Suppose two messages use the same AES key and the same nonce. Their counter sequences are then identical, so corresponding plaintext blocks use the same keystream blocks:

C1 = P1 XOR S
C2 = P2 XOR S

C1 XOR C2 = P1 XOR P2

The keystream cancels. This does not immediately reveal both plaintexts when both are completely unknown, but it leaks their XOR, which is often enough to recover useful information. If an attacker knows one plaintext—perhaps a predictable JSON prefix, protocol header, or standard message—then they can calculate the keystream and decrypt the other ciphertext wherever the streams overlap. Even without known plaintext, natural-language text and structured formats provide strong clues.

The authentication side also breaks. Reusing a nonce reuses the same mask in the tag formula. Comparing two tags cancels that mask and exposes a relationship between their GHASH results. Because GHASH is a structured polynomial calculation, several messages with a repeated nonce can give an attacker equations about its secret hash key. In suitable cases, the attacker can recover enough information to calculate valid tags for modified or entirely chosen ciphertexts.

That is a forgery: the receiver accepts attacker-controlled data as authentic. The attacker may alter a request, change associated headers, or construct a new message that passes GCM verification. The exact amount of reuse and message structure affects how quickly this becomes practical, but nonce reuse has already destroyed GCM’s security guarantee.

Treat the nonce as part of the encryption state, not as an arbitrary per-message decoration. Generate it with a construction that guarantees uniqueness under the key, transmit it with the ciphertext, and never reuse the key-and-nonce pair.

← All articles