Symmetric Encryption
ChaCha20-Poly1305
The modern stream cipher alternative to AES-GCM.
ChaCha20-Poly1305 is the other AEAD you should know. It pairs the ChaCha20 stream cipher with the Poly1305 authenticator, and it is the default choice whenever a device lacks AES hardware acceleration. It powers a large share of modern TLS, all of WireGuard, and the fast path in OpenSSH, so it is far from a niche option.
Stream ciphers and where ChaCha20 wins (no AES hardware)
A stream cipher generates a pseudorandom keystream from a key and a nonce, then XORs that keystream with the plaintext. ChaCha20 does this with 20 rounds of simple additions, rotations, and XORs (an “ARX” design) operating on a 512-bit state. Because it uses only these cheap operations on 32-bit words, it runs fast in plain software and is naturally constant-time, which resists the cache-timing side channels that threaten software AES.
This is exactly where ChaCha20 wins. On a server with AES-NI, AES-GCM is superb. But on hardware without AES instructions (many phones, embedded devices, and older or low-power CPUs), a software AES is both slower and harder to keep constant-time. ChaCha20 has no such dependency: it is fast and side-channel-resistant everywhere.
| Situation | Prefer |
|---|---|
| Server or desktop with AES-NI | AES-256-GCM |
| Mobile, embedded, or no AES hardware | ChaCha20-Poly1305 |
| Want one algorithm that is fast and safe everywhere | ChaCha20-Poly1305 |
Because it is a stream cipher, ChaCha20 shares the CTR-mode rule from Modes Of Operation: the nonce must never repeat under a given key, or two ciphertexts leak the XOR of their plaintexts. And like raw CTR, ChaCha20 on its own provides no integrity, which is why it is always paired with Poly1305.
Poly1305 authentication
Poly1305 is a one-time message authentication code. It evaluates the message as coefficients of a polynomial over the prime field 2^130 - 5, using a secret key derived per message from the cipher, and produces a 128-bit tag. When combined with ChaCha20, the construction (specified in RFC 8439) derives a fresh Poly1305 key from the ChaCha20 keystream for each nonce, so the authenticator key is never reused as long as the nonce is unique.
The result is a full AEAD: ChaCha20 provides confidentiality, Poly1305 provides integrity and authenticity, and both cover the associated data. Decryption recomputes the tag and, on any mismatch, refuses to return plaintext, exactly the tamper-detection behavior described in Authenticated Encryption. As with GCM, nonce reuse is catastrophic: it both leaks plaintext XORs and undermines the authenticator, so treat the nonce with the same discipline you would for AES-GCM.
You can confirm the cipher is available in your OpenSSL build:
mkdir -p ~/crypto-lab && cd ~/crypto-lab
openssl list -cipher-algorithms | grep -i "ChaCha20-Poly1305"
Expected output:
ChaCha20-Poly1305
You can also drive the raw ChaCha20 keystream to see its stream-cipher nature (this is ChaCha20 without Poly1305, for illustration only; never ship unauthenticated encryption):
cd ~/crypto-lab
echo "wireguard handshake payload" > wg.txt
KEY=$(openssl rand -hex 32) # 256-bit key
IV=$(openssl rand -hex 16) # 128-bit iv/counter as OpenSSL expects for raw chacha20
openssl enc -chacha20 -K $KEY -iv $IV -in wg.txt -out wg.enc
echo "same length in and out: in=$(wc -c < wg.txt) out=$(wc -c < wg.enc)"
openssl enc -d -chacha20 -K $KEY -iv $IV -in wg.enc
Expected output:
same length in and out: in=28 out=28
wireguard handshake payload
The equal input and output lengths are the signature of a stream cipher: no block padding, one keystream byte per plaintext byte. In production you use the ChaCha20-Poly1305 AEAD through a library so the Poly1305 tag is generated and checked for you.
Where it is used: TLS, WireGuard, SSH
ChaCha20-Poly1305 is a first-class citizen of modern protocols:
- TLS 1.3 lists
TLS_CHACHA20_POLY1305_SHA256as one of its core cipher suites. Browsers on phones frequently negotiate it because mobile chips may lack fast AES. - WireGuard uses ChaCha20-Poly1305 as its only data-channel AEAD, part of its deliberately minimal, opinionated cipher choices.
- OpenSSH offers
chacha20-poly1305@openssh.com, often the negotiated default, giving strong performance without relying on AES hardware.
Its ubiquity in these protocols is the practical proof that it stands shoulder to shoulder with AES-GCM. Choosing between them is about hardware and policy, not about one being weaker.
Practical Guidance
- Choose ChaCha20-Poly1305 when AES hardware acceleration is absent or uncertain (mobile, embedded, low-power). Choose AES-256-GCM when AES-NI is present, per
AES. - Always use the AEAD (ChaCha20-Poly1305), never bare ChaCha20. Unauthenticated stream encryption is malleable, exactly like raw CTR.
- Never reuse a nonce under one key. Use a counter or a large random nonce with a per-key message budget, and rotate keys well before the limit.
- Verify the Poly1305 tag on every decryption and reject any ciphertext that fails. Do not process plaintext from a failed authentication.
- Bind headers and metadata as associated data so they are authenticated in the clear, just as with AES-GCM.
- Follow rule zero: use libsodium or your language’s audited ChaCha20-Poly1305 binding rather than wiring the cipher and authenticator together yourself.