XChaCha20
XChaCha20
ChaCha20 with a 24-byte nonce, so nobody has to count.
- Keyspace
- 2^256
- Decode
- same options back
- Works on
- UTF-8 or hex, hex out
- Family
- 6 in arx
Options
Access
- Create
create("xchacha20") - CLI
ciphers encode xchacha20 'ATTACK AT DAWN' --key 808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f --nonce 404142434445464748494a4b4c4d4e4f5051525354555658 - Tryplayground with the sample above
- Kinrabbit, salsa20, xsalsa20, chacha20 +1
Remember how XSalsa20 stretched the Salsa20 nonce? XChaCha20 does exactly that to ChaCha20. libsodium has it, and draft-irtf-cfrg-xchacha by Scott Arciszewski writes it down.
Why bother? Twelve random bytes of nonce collide after about 2^48 messages. Sounds like a lot until you run a busy server for a few years. Twenty-four bytes, and you can stop counting.
How it runs
Two steps, both cheap. HChaCha20 runs the ChaCha rounds over the key and the first 16 nonce bytes, where counter and nonce would sit. Nothing gets added back. The first and last rows of the result are a fresh 256-bit subkey.
Then ChaCha20 runs under that subkey. Its 12-byte nonce is four zero bytes and the last 8 bytes of yours.
Keys, nonce and counter
The key is 64 hex digits. nonce is 48 hex digits and required. counter is the ChaCha20 block counter, 0 to 4294967295, default 0.
const xchacha = create("xchacha20");
const key = "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f";
const nonce = "404142434445464748494a4b4c4d4e4f5051525354555658";
xchacha.encode("ATTACK AT DAWN", { key, nonce }).text; // "50659adb696b8e4c33e8d71d20c7"
xchacha.decode("50659adb696b8e4c33e8d71d20c7", { key, nonce }).text; // "ATTACK AT DAWN"
That's the key and nonce from the draft's examples. Spot the 58 at the end? It's not a typo here. The draft really skips 57.
The draft has two examples, one from counter 0 and one from counter 1, and both decrypt the same text. libsodium's crypto_stream_xchacha20 starts at 0. Its AEAD starts at 1, same as ChaCha20-Poly1305.
xchacha20(data, key, nonce, counter) from @agntn/ciphers/chacha does the same on Uint8Array.
Checked against
The HChaCha20 vector from section 2.2.1 of draft-irtf-cfrg-xchacha-03, and the dhole text from appendix A.2 at both counters. Every byte of both ciphertexts, by digest.
A key that isn't 64 hex digits is an InvalidOptionError, and so is a nonce that isn't 48 or a counter off the range. A missing key or nonce is a MissingOptionError.
Random nonces are fine now. Reused ones still aren't, and there's still no tag. Plain TypeScript, puzzles only.