Rust · From scratch · RFC 8439
Cipherlock is a single binary that implements a real modern AEAD, ChaCha20-Poly1305, from scratch. Encrypt and decrypt files with a passphrase, or read the source to see exactly how authenticated encryption works.
This is a second, independent implementation of the same ChaCha20-Poly1305 construction, written in plain JavaScript and running entirely on this page. Nothing you type here leaves your browser.
Type a message and a passphrase on the left and press Encrypt to get the salt, nonce, ciphertext, and authentication tag. Copy them into the right panel with the same passphrase and press Decrypt to recover the plaintext. Change one byte of the ciphertext or the passphrase and decryption fails with an authentication error, exactly as a real AEAD should.
encrypt then decrypt to recover the text · tamper flips one ciphertext bit
Two primitives, combined encrypt then authenticate, so a wrong passphrase or a tampered file fails loudly.
A stream cipher that expands a 256 bit key, a 96 bit nonce, and a counter into a keystream using 20 rounds of add rotate xor quarter rounds. XORed with the plaintext, it produces the ciphertext.
A one time message authenticator. A fresh key is derived from ChaCha20 for every message, and the authenticator produces a 128 bit tag over the ciphertext and any associated data.
The two are combined into AEAD_CHACHA20_POLY1305, encrypt then authenticate. Decryption verifies the tag before releasing any plaintext.
The same construction you just tried above, encrypting a real file on disk.
cargo build --release
cipherlock encrypt secret.txt secret.txt.lock --pass "correct horse battery staple"
cipherlock decrypt secret.txt.lock secret.txt --pass "correct horse battery staple"
Correctness is not a claim, it is a test. This is real output from cargo test: the four RFC 8439 known-answer vectors, the encrypt then decrypt roundtrip, and the tamper-detection test, all green.
cargo test, alongside an encrypt then decrypt roundtrip test and a tamper detection test that flips a ciphertext byte and confirms decryption fails. This is a correct, teaching grade tool. It is not a substitute for an audited cryptography library in production.
Cipherlock is built to be read as much as run. The design choices below are what set it apart from wrapping a crypto library.
ChaCha20, Poly1305, and the combined AEAD are implemented from scratch in src/chacha20.rs, src/poly1305.rs, and src/aead.rs. The binary that encrypts your files is also the clearest place to read how the AEAD works.
ChaCha20-Poly1305 is the same authenticated encryption used in TLS 1.3 and WireGuard, not a toy cipher. The file format is salt, nonce, ciphertext, then a 16 byte Poly1305 tag.
Decryption verifies the tag in constant time before releasing any plaintext. A wrong passphrase or a single flipped byte is a hard authentication error, and nothing is written on failure.
The passphrase KDF in src/kdf.rs is a fixed-round ChaCha20 stretch. It is deliberately simple and is weaker than Argon2 or scrypt, with no memory hardness. This is a teaching-grade tool, not a replacement for an audited library.
Two subcommands, encrypt and decrypt, each taking an input file, an output file, and a --pass passphrase. One binary, no runtime dependencies beyond the standard library and clap.
The primitives ship as a Rust library crate (cipherlock, src/lib.rs): the ChaCha20, Poly1305, AEAD, and KDF modules are callable directly if you want to read or reuse a single piece.
A self-describing layout: [16 byte salt][12 byte nonce][ciphertext][16 byte tag]. Everything needed to decrypt, except the passphrase, travels with the file.