Cipherlock logo

Rust · From scratch · RFC 8439

Authenticated encryption
you can actually read.

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.

View on GitHub How it works

Try it in your browser

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.

How to use this playground

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

 
 
 
 

DECRYPT

 

encrypt then decrypt to recover the text · tamper flips one ciphertext bit

How it works

Two primitives, combined encrypt then authenticate, so a wrong passphrase or a tampered file fails loudly.

1

ChaCha20

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.

2

Poly1305

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.

3

AEAD

The two are combined into AEAD_CHACHA20_POLY1305, encrypt then authenticate. Decryption verifies the tag before releasing any plaintext.

Run it from the command line

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"

The RFC vectors, actually run

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 Running unittests src/lib.rs test chacha20::tests::rfc8439_block_function_vector ... ok test chacha20::tests::rfc8439_encryption_vector ... ok test poly1305::tests::rfc8439_poly1305_vector ... ok test aead::tests::rfc8439_aead_vector ... ok test aead::tests::roundtrip ... ok test aead::tests::tamper_detection ... ok test kdf::tests::deterministic_for_same_inputs ... ok test kdf::tests::different_salt_different_key ... ok test kdf::tests::different_passphrase_different_key ... ok test format::tests::roundtrip ... ok test format::tests::wrong_passphrase_fails ... ok test format::tests::tampered_file_fails ... ok test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Proof of correctness

Every primitive is checked against the official RFC 8439 known answer test vectors: the ChaCha20 block function vector, the ChaCha20 encryption vector, the Poly1305 vector, and the full AEAD vector. All four pass under 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.

What makes it different

Cipherlock is built to be read as much as run. The design choices below are what set it apart from wrapping a crypto library.

No crypto crates

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.

A real modern construction

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.

Fails loudly, never silently wrong

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.

Honest about its limits

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.

Three ways in

CLI

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.

Library

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.

File format

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.