Block ciphers vs. stream ciphers
AES is a block cipher: it transforms fixed 128-bit chunks. A stream cipher instead generates a pseudorandom keystream from the key and a nonce, then combines it with the plaintext one bit or byte at a time (almost always with XOR). Encryption and decryption are the identical operation — XOR the data with the same keystream again.
Encryption and decryption are literally the same XOR operation against the keystream Kᵢ.
How ChaCha20 builds its keystream
ChaCha20, designed by Daniel J. Bernstein, generates its keystream by repeatedly mixing a 256-bit key, a counter, and a nonce through a sequence of addition, rotation, and XOR operations (ARX). Unlike AES, none of this depends on table lookups, which sidesteps a class of cache-timing side-channel attacks that have affected some AES software implementations on hardware without dedicated AES instructions.
ChaCha20-Poly1305 AEAD
- 1
Mix key + nonce + counter
A 256-bit key, a nonce, and a block counter seed ChaCha20's internal state.
- 2
Generate keystream
20 rounds of add-rotate-XOR (ARX) turn that state into a pseudorandom keystream block.
- 3
XOR with plaintext
The keystream is XORed with the plaintext to produce ciphertext.
- 4
Poly1305 tag
A one-time authenticator keyed from the same session computes a tag over the ciphertext, detecting any tampering.
Poly1305 and the AEAD pairing
ChaCha20 alone only provides confidentiality. Paired with the Poly1305 message authentication code, it becomes ChaCha20-Poly1305 — an AEAD (Authenticated Encryption with Associated Data) construction, functionally equivalent in purpose to AES-GCM: it encrypts and authenticates in one pass, producing a tag that detects any tampering.
Where it's actually used
ChaCha20-Poly1305 is a standard cipher suite in TLS 1.3, the default cipher for the WireGuard VPN protocol, and widely used on mobile devices and older or low-power hardware that lacks AES hardware acceleration (AES-NI), where ChaCha20 in pure software runs significantly faster and in constant time.
Nonce reuse: the one mistake that breaks every stream cipher
Every stream cipher, ChaCha20 included, shares one absolute rule: the same key-and-nonce pair must never generate keystream for two different messages. Reuse it, and the keystream itself cancels out of the math entirely, no key-breaking required — because C₁ ⊕ C₂ = (P₁ ⊕ K) ⊕ (P₂ ⊕ K) = P₁ ⊕ P₂, an equation that involves only the two plaintexts, with K gone. Knowing (or guessing) either plaintext instantly reveals the other.
This is the same two-time-pad weakness that affects any XOR-based keystream cipher, and it's exactly why protocols built on ChaCha20 or AES-CTR are so strict about nonce uniqueness — a repeated nonce (from a buggy counter, a restarted process reusing state, or a nonce that's too short and collides by chance) is a full break, not a partial weakening.
Practice
A nonce was accidentally reused, so two plaintext bytes were XORed with the same keystream byte K, giving C₁ = 0xD8 and C₂ = 0xC3. You know the first plaintext byte is the ASCII letter 'A' (0x41). Recover the second plaintext byte as an ASCII letter.