Encryption

ChaCha20-Poly1305 Explained: When to Choose It Over AES

ChaCha20-Poly1305 is up to eight times faster than AES-GCM on devices without hardware acceleration. Learn when to choose it and why TLS 1.3 includes it.

Editorial Team ·
9 min read intermediate

Introduction

On a smartphone without hardware AES support, ChaCha20-Poly1305 encrypts data up to eight times faster than AES-GCM. That is why Google, WireGuard, and TLS 1.3 all include it as a first-class cipher — and why understanding when to choose ChaCha20-Poly1305 versus AES-GCM is a real engineering decision, not a theoretical one.

AES-GCM is the dominant symmetric AEAD cipher on modern servers and desktop CPUs. Intel and AMD have included AES-NI hardware acceleration since 2010; ARM’s Cortex-A series added it around 2012. On these platforms, AES-256-GCM achieves 3–5 GB/s in software — practical for any workload.

But billions of devices do not have AES acceleration: low-power IoT chips running MIPS or older ARM Cortex-M, embedded microcontrollers in industrial equipment, legacy Android devices, and any hardware that predates widespread AES-NI adoption. On these platforms, software-only AES-GCM achieves 100–300 MB/s — and worse, it is vulnerable to cache-timing side channels when implemented with lookup tables, because table access patterns leak information about the key.

ChaCha20-Poly1305 was designed to solve exactly this problem. Its core operations are addition, rotation, and XOR — integer operations available on every processor, requiring no lookup tables, and inherently constant-time. On hardware without AES acceleration, it outperforms AES-GCM by 3–8×. On hardware with AES acceleration, it performs comparably or slightly slower, but remains a valid and secure choice.

ChaCha20-Poly1305: What It Is and When to Use It

ChaCha20-Poly1305 is an AEAD (Authenticated Encryption with Associated Data) algorithm that combines two components:

  • ChaCha20: A native stream cipher designed by Daniel J. Bernstein (2008), based on the Salsa20 cipher. It generates a keystream of pseudorandom bytes using only integer addition, 32-bit rotation, and XOR — the ARX (Add-Rotate-XOR) construction.
  • Poly1305: A one-time message authentication code (MAC) that computes a 128-bit authentication tag over the ciphertext, ensuring any modification is detected during decryption.

The two components are specified together in RFC 8439 (2018) and are mandated as a single, inseparable unit — ChaCha20 without Poly1305, or Poly1305 with a different cipher, is not the same algorithm.

The combination produces AEAD output: encrypted data with a 16-byte authentication tag. Decryption verifies the tag first. If it fails, no plaintext is returned — the same guarantee as AES-256-GCM.

How ChaCha20-Poly1305 Works

The ChaCha20 Stream Cipher

ChaCha20 generates a keystream from a 512-bit state matrix, organized as a 4×4 grid of 32-bit words:

  • Row 0: Four fixed constants (“expand 32-byte k”)
  • Rows 1–2: Your 256-bit key (eight 32-bit words)
  • Row 3a: A 32-bit block counter (starts at 1 for message encryption)
  • Row 3b: A 96-bit nonce (three 32-bit words)

The quarter round is the algorithm’s only operation, applied 20 times:

  1. Add: Two words are added together.
  2. XOR: The sum is XORed with another word.
  3. Rotate: The XOR result is rotated left by a fixed number of bits.

This sequence — no lookup tables, no data-dependent branches — is what makes ChaCha20 naturally constant-time. There are no memory access patterns that vary based on key or plaintext values, which is how cache-timing attacks extract key material from AES table-based implementations.

After 20 rounds, the 512-bit result is added back to the original state, producing 64 bytes of keystream. The plaintext is XORed with keystream bytes — producing ciphertext.

The Poly1305 MAC

Poly1305 uses a one-time 256-bit key derived from ChaCha20’s output for block counter 0 (the first 64 bytes generated before encryption begins). It evaluates a polynomial over the message bytes modulo 2¹³⁰ − 5 (a prime), producing a 128-bit authentication tag.

The tag covers the ciphertext and any associated data (unencrypted metadata you want to authenticate alongside the ciphertext). During decryption, the receiver computes the same polynomial and compares. A mismatch — caused by any modification, however small — causes decryption to abort immediately.

The Critical Rule: Never Reuse a Nonce

ChaCha20-Poly1305 requires a 96-bit (12-byte) nonce that must be unique for every encryption with the same key. Reusing a nonce with the same key means two plaintexts are XORed with the same keystream — XORing the ciphertexts together cancels the keystream and reveals the XOR of both plaintexts. An attacker who can observe two encrypted messages under the same key and nonce recovers significant plaintext information.

Generate a fresh random 96-bit nonce for every encryption. With random nonces, the probability of a collision after one billion encryptions is approximately 1 in 4 billion — acceptable for most applications. High-volume applications should implement nonce counters or rekey more frequently.

This video explains AES, which is ChaCha20-Poly1305's main competitor in TLS 1.3. ChaCha20 uses a fundamentally different design — ARX operations instead of AES's substitution-permutation network — which is what makes it constant-time by default and faster on hardware without AES acceleration.
Trace the quarter round: Add → XOR → Rotate, repeated 20 times across a 4×4 state matrix. No lookup tables appear anywhere in this path — which is exactly why ChaCha20 is constant-time by construction.

ChaCha20-Poly1305 vs AES-256-GCM

FeatureChaCha20-Poly1305AES-256-GCM
Cipher typeStream cipher (ARX design)Block cipher in CTR mode
Key size256-bit128 or 256-bit
Nonce size96-bit96-bit
Authentication tag128-bit (Poly1305)128-bit (GHASH)
Constant-time by designYesOnly with AES-NI
Performance without AES-NIFast (1–2 GB/s in software)Slow (100–300 MB/s; timing risk)
Performance with AES-NISlower (~1.7 GB/s)Fast (3–5 GB/s)
TLS 1.3 supportMandatory cipher suiteMandatory cipher suite
WireGuardOnly cipher usedNot supported
Nonce reuse safetyCatastrophic — avoidCatastrophic — avoid

The table reveals the deployment decision framework: if you know your hardware has AES-NI, prefer AES-256-GCM for raw performance. If you cannot guarantee AES-NI availability — or if you prioritize constant-time security properties over peak throughput — ChaCha20-Poly1305 is the correct choice.

Real-World Use Cases

TLS 1.3 on mobile devices: TLS 1.3 includes both AES-256-GCM and ChaCha20-Poly1305 as mandatory cipher suites. When a TLS 1.3 connection is negotiated between a server with AES-NI and a client without it (an older Android device, for example), the client can signal its cipher preference. Google Chrome on Android uses ChaCha20-Poly1305 by default when it determines the device lacks AES acceleration — resulting in measurably better battery life on those devices.

WireGuard VPN: WireGuard is the modern VPN protocol included in the Linux kernel since 5.6. It uses ChaCha20-Poly1305 as its only symmetric cipher — no cipher negotiation, no configuration. The single-cipher approach eliminates an entire class of downgrade attacks and simplifies the codebase dramatically. WireGuard’s use of ChaCha20-Poly1305 is validated by the formal security analysis in the WireGuard whitepaper (2020).

Signal messaging: The Signal Protocol uses ChaCha20-Poly1305 in its Double Ratchet algorithm for per-message encryption. Signal targets the full range of Android hardware — including low-end devices without AES acceleration in markets like South Asia and sub-Saharan Africa. ChaCha20-Poly1305’s consistent performance across hardware generations makes it the natural choice for a privacy-critical application that must work well on budget hardware.

Common Mistakes to Avoid

Using ChaCha20 without Poly1305. ChaCha20 alone is a stream cipher — it provides confidentiality but no authentication. Unauthenticated encryption means an attacker can flip bits in the ciphertext and produce ciphertext that decrypts to modified plaintext with no error. Always use ChaCha20-Poly1305 together as a single AEAD algorithm. The RFC 8439 combined construction is the correct specification.

Implementing ChaCha20 from scratch. The algorithm’s simplicity is deceptive. Correct implementation requires careful handling of the counter, nonce, and key material. Use a vetted library: libsodium (secretbox API), the Go stdlib crypto/chacha20poly1305, or Rust’s chacha20poly1305 crate. Cryptographic implementation errors are subtle and often not detectable from functional tests.

Treating AES-GCM and ChaCha20-Poly1305 as interchangeable without testing. If you switch from AES-256-GCM to ChaCha20-Poly1305 in an existing system that stores encrypted data, old ciphertexts cannot be decrypted with the new algorithm. Cipher migration requires decrypting existing data with the old algorithm and re-encrypting with the new one, or maintaining algorithm metadata alongside ciphertexts. Plan this before changing algorithms in production.

Assuming all TLS libraries default to ChaCha20 on appropriate hardware. Browser behavior is well-documented (Chrome prefers ChaCha20 when client lacks AES-NI), but server-side TLS libraries vary. OpenSSL’s server preference ordering may put AES-GCM first regardless of the client’s hardware. Configure your TLS server to respect client cipher order preference (SSL_OP_CIPHER_SERVER_PREFERENCE off in OpenSSL) if your clients are primarily low-power hardware.

Getting Started

Determine whether ChaCha20-Poly1305 or AES-256-GCM is the right choice for your deployment context. If your application runs entirely on modern server hardware with guaranteed AES-NI (any Intel Ivy Bridge+ or AMD Zen+ processor), AES-256-GCM is typically faster. If your application reaches low-power ARM, IoT, or embedded hardware, ChaCha20-Poly1305 is faster and safer.

For TLS configuration, both cipher suites are included in the TLS 1.3 mandatory set — you do not need to explicitly enable ChaCha20-Poly1305. The negotiation is handled automatically based on client and server preferences. The important step is ensuring both cipher suites are allowed, not restricted, in your TLS configuration.

For application-level encryption (encrypting files, database fields, or messages outside of TLS), use libsodium’s crypto_secretbox_easy function, which uses XSalsa20-Poly1305 (a close relative), or use crypto_aead_chacha20poly1305_ietf_encrypt for the IETF-standard construction from RFC 8439. Both handle nonce management correctly when used with the library’s recommended API.

Verify your nonce generation. The nonce must be unique per encryption with the same key — use a cryptographically random nonce generator (OS getrandom, crypto.getRandomValues()) rather than a sequential counter starting at zero. If you encrypt at high volume (millions of messages per day with the same key), implement automatic key rotation to reduce nonce collision probability.

For a detailed comparison with AES-256-GCM and when each is the correct choice in practice, see AES-256-GCM: ChaCha20’s main alternative. To understand how TLS 1.3 selects between these two cipher suites during handshake negotiation, read TLS 1.3 vs TLS 1.2.

FAQ

Common questions — answered in plain English.

What is ChaCha20-Poly1305?
ChaCha20-Poly1305 is an AEAD (Authenticated Encryption with Associated Data) algorithm that combines the ChaCha20 stream cipher with the Poly1305 message authentication code. It simultaneously encrypts data and produces a 128-bit authentication tag, ensuring both confidentiality and integrity. It is one of the two mandatory cipher suites in TLS 1.3, alongside AES-256-GCM.
Is ChaCha20-Poly1305 more secure than AES-GCM?
They provide equivalent security at their respective key sizes. Both use 256-bit keys and 128-bit authentication tags, giving equivalent 128-bit security. The difference is performance and implementation safety: ChaCha20-Poly1305 is constant-time by design (no S-boxes or lookup tables), making it inherently resistant to timing side-channel attacks. AES-GCM achieves constant-time operation only with AES-NI hardware instructions.
When should I use ChaCha20 instead of AES?
Use ChaCha20-Poly1305 on hardware without AES acceleration: older ARM processors, IoT devices, MIPS chips, embedded systems, and any platform where you cannot verify AES-NI availability. On devices with AES-NI (most modern x86 and ARM-A CPUs), AES-GCM is typically faster. If you cannot determine hardware capabilities at deployment time, ChaCha20-Poly1305 is the safer choice — it performs well everywhere.
What is Poly1305 used for?
Poly1305 is the message authentication code (MAC) in ChaCha20-Poly1305. It evaluates a polynomial over the message bytes using a one-time key derived from ChaCha20's output. The result is a 128-bit authentication tag appended to the ciphertext. During decryption, the tag is verified first — if it does not match, decryption fails immediately, indicating tampering or corruption.
Does TLS 1.3 use ChaCha20?
Yes. TLS_CHACHA20_POLY1305_SHA256 is one of five mandatory cipher suites in TLS 1.3 (RFC 8446). TLS 1.3 servers that advertise both AES-256-GCM and ChaCha20-Poly1305 allow clients to express a hardware preference. Servers running on ARM hardware without AES acceleration prefer ChaCha20; servers with AES-NI prefer AES-GCM. Both achieve equivalent security.
Why does WireGuard use ChaCha20-Poly1305?
WireGuard's designer Daniel Bernstein chose ChaCha20-Poly1305 as WireGuard's only symmetric cipher for several reasons: it is constant-time by design (no timing side channels), performs well on all hardware including embedded devices, has a simple implementation that is easier to audit, and is one of Bernstein's own designs. WireGuard's single-cipher approach avoids cipher negotiation complexity entirely.

References

  1. [1]
  2. [2]
  3. [3]
  4. [4]
  5. [5]