Skip to content
Lesson 6 of 10

Step 1 of 5 · Reading · ~4 min

Read

Cryptography & Handshake

AEAD Encryption: ChaCha20-Poly1305

After this lesson your program seals and opens packets exactly as WireGuard does: it builds the ChaCha20 keystream itself, authenticates with Poly1305, derives the nonce from the packet counter, and drops anything whose tag does not verify. It matches RFC 8439 bit for bit.

Two primitives, one construction

ChaCha20 is a stream cipher. From a 256 bit key, a 96 bit nonce and a 32 bit block counter it produces 64 bytes of keystream per block. Encryption is XOR with the keystream, so decryption is the same operation. Poly1305 is a one-time authenticator: a 32 byte key and a message give a 16 byte tag, and the key must never be used twice. The AEAD construction (RFC 8439, section 2.8) wires them together:

  1. The Poly1305 key is the first 32 bytes of the ChaCha20 block with block counter 0.
  2. The plaintext is XORed with the keystream from block counter 1 onwards.
  3. The tag covers the ciphertext, not the plaintext (encrypt-then-MAC): aad, zero padding up to a multiple of 16 bytes, ciphertext, zero padding again, then the length of aad and the length of ciphertext, each as 8 little-endian bytes. The lengths are always there, even when the AAD is empty.
  4. The output is ciphertext followed by the 16 byte tag.

AAD (additional authenticated data) is data that travels in the clear but must not be alterable. WireGuard transport packets use none; the counter is protected anyway because it is the nonce.

The ChaCha20 block

The state is a 4 by 4 grid of 32 bit words, read from bytes in little-endian order:

 c0  c1  c2  c3        constants  61707865 3320646e 79622d32 6b206574
 k0  k1  k2  k3        key words 0 to 3
 k4  k5  k6  k7        key words 4 to 7
 ctr n0  n1  n2        block counter, then the three nonce words

Twenty rounds are ten repetitions of eight quarter rounds: four on the columns (words 0 4 8 12, then 1 5 9 13, 2 6 10 14, 3 7 11 15) and four on the diagonals (0 5 10 15, 1 6 11 12, 2 7 8 13, 3 4 9 14). A quarter round on words a b c d does: a += b; d ^= a; d <<<= 16; c += d; b ^= c; b <<<= 12; a += b; d ^= a; d <<<= 8; c += d; b ^= c; b <<<= 7, with additions modulo 2^32 and <<< a left rotation. After the last round add the original state back word by word and write the words little-endian.

Check your quarter round against RFC 8439 section 2.1.1: with a = 11111111, b = 01020304, c = 9b8d6f43 and d = 01234567 (hex) it must leave a = ea2a92f4, b = cb1cf8ce, c = 4581472e, d = 5881c4bb.

The WireGuard nonce

The nonce is 12 bytes: four zero bytes followed by the 64 bit packet counter, little-endian. Counter 1 is 00000000 0100000000000000. A key and nonce pair must never repeat, which is why the counter only goes up and a session is rekeyed long before it could wrap. If it ever repeated, XORing the two ciphertexts would reveal the XOR of two plaintexts, and the Poly1305 key stream would leak too.

On a bad tag the packet is dropped silently: no error message and no reply. Nothing is decrypted before the tag verifies, and the tag comparison must not stop at the first differing byte.

Common mistakes

  • Block counter 0 for the data. Counter 0 is spent on the Poly1305 key; the data starts at counter 1.
  • MAC over the plaintext, or without the padding and the two length fields. The tag then differs from every other implementation.
  • Counter in the wrong place or big-endian in the nonce. Bytes 0 to 3 are zero and the counter fills bytes 4 to 11.
  • Forgetting to add the input state after the rounds. The output is then a bare permutation.
  • Decrypting before verifying, or returning plaintext on a bad tag.

Your exercise

The starter gives you Poly1305 and the command loop. You write the ChaCha20 block, the seal and open functions and the WireGuard nonce. The most likely mistake is starting the data keystream at block counter 0.

Up nextPeer ConfigurationTunneling & Routing

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…