Key takeaways
- XChaCha20-Poly1305 uses a 192-bit nonce, making collisions practically impossible without coordination
- ChaCha20 outperforms hardware-AES on mobile SoCs that lack dedicated AES acceleration
- Poly1305 authentication runs in constant time, closing a class of timing side-channels
- Nonce-misuse resistance means a repeated nonce leaks nothing beyond the repeated message itself
When we picked a cipher for Inktally's document encryption, the obvious choice was AES-GCM — hardware-accelerated, widely audited, the default for everyone from TLS to Signal. We chose XChaCha20-Poly1305 instead. This is the engineering reasoning.
The nonce problem with AES-GCM
AES-GCM requires a unique nonce for every encryption operation. The nonce can be as short as 96 bits (12 bytes). If you encrypt two different plaintexts with the same key and the same nonce, the encryption is catastrophically broken — an attacker can XOR the two ciphertexts and immediately get the XOR of the two plaintexts, plus forge authentication tags.
With a 96-bit nonce, if you generate nonces randomly, birthday-bound probability puts your first collision at around 2^48 operations — roughly 281 trillion encryptions. That sounds comfortable until you consider that mobile apps generate a fresh nonce per message, per document version, per key wrapping operation, across millions of users. The math gets uncomfortable faster than you'd expect.
What XChaCha20-Poly1305 fixes
XChaCha20-Poly1305 uses a 192-bit nonce. At that size, birthday-bound collision probability is negligible even at internet scale. We can safely generate nonces randomly without a collision-resistant counter or a global sequence number — both of which require coordination that is hard to implement correctly in a distributed system.
The “X” prefix indicates the extended-nonce variant of ChaCha20, specifically designed to make random nonce generation safe. The underlying stream cipher (ChaCha20) plus the Poly1305 MAC gives us authenticated encryption with associated data (AEAD) — same category as AES-GCM, same security model, larger nonce.
Implementation detail
We use libsodium's crypto_secretstream_xchacha20poly1305 for streaming large documents and crypto_aead_xchacha20poly1305_ietf for short sealed boxes. Both are audited, formally verified implementations — we do not implement the cipher ourselves.
Performance on constrained devices
AES-GCM has hardware acceleration (AES-NI) on most modern CPUs, but not on all mobile processors, and not in older WebAssembly environments. ChaCha20 is software-fast — it was designed to be competitive on processors without AES acceleration, achieving performance comparable to AES-NI on systems that lack it, and slightly slower on systems that have it.
For Inktally's use case, the bottleneck is almost always network and storage I/O, not cipher throughput. The small CPU difference is irrelevant in practice.
See this in practice.
Your vault is encrypted before it leaves your device. Inktally never sees your keys.
Try Inktally freeThe decision
XChaCha20-Poly1305 gives us nonce-misuse resistance, random nonce safety, and cross-platform software performance. We give up a small amount of CPU performance on AES-NI-equipped desktops, and we accept a cipher that is slightly less familiar to auditors than AES-GCM (though ChaCha20 is extensively studied).
For a client-side encrypted application where mistakes in nonce management would be catastrophic and undetectable, that trade is an easy one to make.

