cipher.ink Write a message

Learn · Technologies we use

Authenticated encryption

Flip one bit and "pay $100" becomes "pay $900". Then watch AES-GCM refuse it: encrypt-then-MAC, AEAD and the rules for using them.

Authenticated encryption is encryption that also tells you whether anyone changed the ciphertext. Encryption alone hides a message but can't stop someone altering it. Run a cipher in a mode like CTR or OFB and an attacker who has never seen the key can change the ciphertext so it decrypts to something they picked. "It'll decrypt to garbage" is no defense, because the attacker often controls exactly which bytes change and what they change into. Authenticated encryption, written AEAD (authenticated encryption with associated data), adds a tag to the ciphertext. A recipient whose tag check fails refuses the message before producing any plaintext.

See it break

The demo below encrypts the sentence "Pay $100 to Alice" under a fresh random 256-bit key. The "attacker" has no key and knows only where the amount sits. They want it to read 900. In CTR mode the ciphertext is the message XORed with a keystream, so XORing the ciphertext at that spot with the difference between "100" and "900" makes the same change in the plaintext. Only one byte has to change, and by a single bit, since the characters "1" and "9" differ only in the bit worth 8. Press the button, then switch the mode to GCM and press it again.

A fresh random key is made each time and thrown away. Nothing leaves your browser.

In CTR mode the recipient decrypts the altered message to "Pay $900 to Alice" with no error at all. In GCM mode the same change is caught and the message is refused. The attack never needed the key. It needed the position of the digits and a guess at the plaintext, which is often easy to get because formats are predictable: an HTTP header, a JSON field, a payment message.

Why a MAC is the answer

The cure is a message authentication code, a short tag computed from the message and a secret key (HMAC is the standard one). Without the key, an attacker can't compute a valid tag for an altered message. The recipient recomputes the tag, compares it in constant time, and throws the message away if it's wrong. There are three ways to combine a cipher with a MAC, and they aren't equal.

  • Encrypt-then-MAC: encrypt, then compute the tag over the ciphertext. The recipient checks the tag first and decrypts only what passes. Hugo Krawczyk showed in 2001 that this order is secure whenever the cipher and the MAC are. IPsec uses it, and it's the right default.
  • MAC-then-encrypt: compute the tag over the plaintext and encrypt both together. The recipient has to decrypt before it can check anything, so how it reacts to bad padding or a bad tag can leak information. Older SSL and TLS did this with CBC, and padding-oracle and timing attacks grew out of it (Vaudenay in 2002, Lucky Thirteen against TLS in 2013, POODLE against SSL 3.0 in 2014).
  • Encrypt-and-MAC: encrypt the plaintext and send a tag of the plaintext beside it, as SSH once did. The tag can leak facts about the plaintext, such as whether two messages are equal.

AEAD: one primitive that does both

Getting the combination right is hard, so modern designs use a single construction that gives both guarantees through a standard interface (RFC 5116). You hand it a key, a unique nonce, the plaintext and optionally some associated data, and you get back ciphertext plus a tag. Opening takes the same inputs and either returns the plaintext or fails. The associated data is authenticated but not encrypted: headers, a message number, the version of the format. If anyone changes it, or presents the ciphertext with different associated data, opening fails.

AEADBuilt fromTagNotes
AES-GCMAES in CTR mode, with the GHASH polynomial hash128 bitsFast with hardware support; a repeated nonce is catastrophic
ChaCha20-Poly1305The ChaCha20 stream cipher with the Poly1305 authenticator (RFC 8439)128 bitsFast without hardware support
AES-CCMCTR plus CBC-MACConfigurableUsed in constrained devices and Wi-Fi
AES-GCM-SIVA nonce-misuse-resistant variant (RFC 8452)128 bitsA repeated nonce only reveals that two messages are equal

Rules for using one

  • Never reuse a nonce with the same key. The AEADs above are only as safe as that rule. The simplest way to keep it is a fresh key for every message, as cipher.ink does, or a random 96-bit nonce when there are few messages per key.
  • Verify the tag before using any plaintext. Libraries do this for you. The point is that you must never look at a partial result.
  • Treat all failures the same. A failed message should give one error, with the same timing, whatever the reason. Telling the attacker why it failed is how padding oracles work.
  • Put the context in the associated data. Anything that decides how the message is read (a version, a recipient, a type) belongs there, so a valid ciphertext can't be replayed in a different context.
  • A tag proves that someone with the key made the message, not who, and it won't stop a replay of an old valid message. Protocols add sequence numbers for that.

How big is the tag, and what does it miss?

A tag of t bits means an attacker with no key has a 1 in 2t chance of guessing a valid tag for a message of their choice on each try. For a 128-bit tag that's 1 in about 3.4 × 1038, and no number of attempts changes that. A 32-bit tag, once common in constrained protocols, is 1 in about 4.3 billion per try. That's why the standards that allow shorter tags attach conditions to them, and why we wouldn't use one. Longer isn't the point. The tag has to be long enough that guessing is hopeless, and 128 bits is.

What a tag misses is anything that leaves each message intact. An attacker can replay an old valid message, delete messages from a stream, or reorder them, and every surviving message still verifies. So protocols number their messages, bind the number into the associated data, and end a stream with an explicit marker, which makes a missing or repeated piece an error in itself. A single sealed message, like the one in a cipher.ink link, is complete on its own and has none of these problems. But the same sealed bytes would open again if someone put them back after a deletion, and nothing in the ciphertext can say it's stale.

How cipher.ink uses this

Every message and attachment is sealed with AES-256-GCM, and the browser's Web Crypto checks its 128-bit tag before returning any plaintext. That's how the site tells a wrong passphrase from a right one. There's no "garbage" outcome and no padding to inspect, only a message that opens or one that's refused (AES-GCM, explained). The server can't read, alter or forge a message either, because any change it made would fail the tag when the recipient's browser opened it. What the tag can't protect against is the server sending different JavaScript to the recipient, a limit we spell out in client-side encryption. Messages sealed before 2026 used OFB, which has no authentication, and we keep that format only so old links open (the OFB article).

Further reading

  1. RFC 5116: An Interface and Algorithms for Authenticated Encryption (2008)
  2. Bellare and Namprempre: Authenticated Encryption: Relations among Notions and Analysis of the Generic Composition Paradigm (2000)
  3. Krawczyk: The Order of Encryption and Authentication for Protecting Communications (2001)
  4. RFC 8439: ChaCha20 and Poly1305 for IETF Protocols (2018)
  5. RFC 8452: AES-GCM-SIV: Nonce Misuse-Resistant Authenticated Encryption (2019)