XChaCha20-Poly1305 and Argon2id, in plain language
Two names that appear in every serious encryption product, explained without the mathematics — and why the boring choice is the right one.
Every encrypted vault has to solve two different problems, and they are usually confused with each other. The first is how to scramble data so that only someone holding the key can read it, and so that nobody can quietly alter it. The second is how to turn a password — a short, memorable, low-entropy thing typed by a human — into a key worth having. Different problems, different tools. Subfile uses XChaCha20-Poly1305 for the first and Argon2id for the second.
XChaCha20-Poly1305: the encryption half
Read it as three parts. ChaCha20 is the cipher. Poly1305 is the authenticator. The X on the front is a change to how the cipher is initialised.
ChaCha20 is a stream cipher. Given a key and a nonce — a number used once — it produces a long pseudorandom stream of bytes, which is combined with your data to encrypt it, and combined with the ciphertext again to decrypt it. It was designed by Daniel J. Bernstein as a refinement of Salsa20, it has been studied publicly for years, and it is fast on ordinary CPUs without needing special hardware instructions to stay fast. That last property matters more than it sounds: an algorithm whose security depends on being implemented carefully, and whose careful implementation is slow, tempts people into shortcuts.
Poly1305 is a message authentication code. It produces a short tag over the ciphertext, and decryption checks that tag before returning a single byte of plaintext. If anything has changed — a corrupted block, a deliberately flipped bit, a chunk spliced in from a different vault — the check fails and you get an error instead of subtly wrong data. Encryption without authentication is a well-known way to build something that looks secure and is not, because an attacker who cannot read your data can often still modify it in useful ways.
Together they form an AEAD: authenticated encryption with associated data. Confidentiality and integrity in one construction, checked as one operation, with no way to accidentally use only half of it.
What the X buys
The nonce is the part that quietly ruins stream ciphers. Reusing a nonce with the same key is not a small mistake; it can expose relationships between the two messages encrypted under it. The IETF version of ChaCha20-Poly1305 uses a 96-bit nonce, which is short enough that you cannot safely pick them at random forever — you have to track a counter and make sure it never repeats, across every session, every crash, every restore from backup.
XChaCha20 extends the nonce to 192 bits by deriving a per-message subkey first. At that size, generating nonces randomly is safe for any realistic number of messages, and you no longer need to maintain state that must never be reused. For a vault that is written to over years, copied between drives, and restored from backups, removing an entire class of state-tracking bug is worth more than a small performance difference. The construction is specified in a CFRG internet-draft rather than a finished RFC, and it is implemented in widely used libraries such as libsodium — it is conservative and well-travelled, not exotic.
Inside a vault, data is encrypted in blocks, each with its own authentication tag. That is what makes a mounted volume behave like a disk: an application can read a range in the middle of a large file without the whole file being decrypted first, and every block that is returned has been verified on the way out.
Argon2id: the password half
A cipher key is 256 bits of uniformly random data. A password is not. If the password were used directly as a key, an attacker with a copy of the vault could simply guess passwords, and modern guessing is industrial: dedicated hardware trying enormous numbers of candidates in parallel. The job of a key derivation function is to make each individual guess expensive enough that the industrial approach stops paying.
Argon2 won the Password Hashing Competition in 2015 and is specified in RFC 9106. It comes in three variants; Argon2id is the hybrid, and it is the one the RFC recommends by default. Its central idea is memory hardness. An older function like PBKDF2 costs an attacker time but almost no memory, which suits a GPU perfectly — thousands of small cores, each doing a cheap repetitive task. Argon2id deliberately requires a large working memory region for every single guess, so an attacker needs not just processing units but gigabytes of fast RAM per unit. That collapses the parallelism advantage that makes mass guessing viable.
- m = 1 GiB
- memory each guess must allocate
- t = 4
- passes over that memory
- p = 4
- lanes computed in parallel
- salt
- unique per vault, stored in the header, not secret
The salt is what stops precomputation. Because every vault has a different one, an attacker cannot build a table of derived keys once and reuse it against everybody; work spent on your vault is spent only on your vault. The parameters are deliberately heavy, which is why unlocking takes a noticeable moment. That pause is the feature. It is the same pause an attacker pays, multiplied by every password they try.
Why boring is the point
Neither of these is novel, and that is the argument for them. Both have been public for years, implemented many times over, and attacked by people whose careers depend on breaking things. Cryptography that nobody has tried to break is not strong, it is untested. The interesting parts of a product like this should be the format, the interface and the honesty of the claims — not the cipher.
xchacha20-poly1305 · argon2id m=1GiB t=4 p=4 · blake3 header