Krypton is a Rust-based encryption toolkit for protecting files with a password. It provides a command-line interface, a reusable library core, a versioned binary envelope format, Docker support, tests, and performance benchmarks.
The project is designed as a security engineering implementation of modern password-based file encryption. It intentionally uses established cryptographic libraries instead of custom cryptography.
- What Krypton Does
- Why This Project Exists
- Cryptography Background
- Project Architecture
- File Structure
- Encryption Flow
- Decryption Flow
- Streaming Encryption
- Envelope Format
- Command-Line Usage
- Docker Usage
- Testing and Benchmarks
- Security Model
- Development Workflow
- Roadmap
Krypton turns a normal file into an encrypted file that can only be opened again with the correct password.
Example:
secrets.txt --> secrets.txt.kry
Later, the encrypted file can be decrypted back into the original file:
secrets.txt.kry --> secrets.txt
At a high level:
password
|
v
normal file --> [ Krypton ] --> encrypted .kry file
|
v
tamper detection metadata
Krypton is useful for:
- encrypting local files
- scripting file protection workflows
- demonstrating secure envelope design
- learning modern password-based encryption architecture
- running encryption inside containers or automation pipelines
File encryption sounds simple, but doing it safely requires more than running a cipher over bytes. A practical encryption tool needs to answer questions such as:
- How does a password become a strong encryption key?
- How is tampering detected?
- How can large files be encrypted without loading them fully into memory?
- How does the decryptor know which algorithm was used?
- How can the file format evolve without breaking older data?
Krypton answers these with:
| Requirement | Krypton's Approach |
|---|---|
| Password hardening | Argon2id key derivation |
| Encryption | XChaCha20-Poly1305 |
| Tamper detection | AEAD authentication tags |
| File format | Versioned binary envelope |
| Large files | Chunked streaming encryption |
| Automation | CLI flags and environment-variable passwords |
| Deployment | Multi-stage Docker image with non-root runtime user |
This section explains the core security concepts used by Krypton.
A password like correct horse battery staple is human-readable text. Encryption
algorithms need fixed-size binary keys.
Krypton uses a key derivation function:
password + random salt
|
v
Argon2id
|
v
32-byte encryption key
Argon2id is intentionally expensive. That cost is useful: it slows down attackers who try to guess passwords offline after stealing encrypted files.
A salt is random data generated during encryption. Krypton stores the salt inside the encrypted file because it is not secret.
The salt ensures that the same password does not always produce the same key:
password + salt A --> key A
password + salt B --> key B
A nonce is a number used once by the encryption algorithm. Krypton uses random 24-byte nonces with XChaCha20-Poly1305.
In streaming mode, every chunk gets its own nonce.
AEAD means "Authenticated Encryption with Associated Data."
It provides two things at the same time:
- confidentiality: attackers cannot read the plaintext
- integrity: attackers cannot modify the ciphertext undetected
Krypton uses XChaCha20-Poly1305, which produces:
plaintext + key + nonce + aad
|
v
XChaCha20-Poly1305
|
v
ciphertext + authentication tag
If a single byte of the ciphertext, tag, or authenticated metadata is changed, decryption fails.
Krypton is separated into small layers. Each layer has a specific job.
Command-line interface
|
v
Streaming file layer
|
v
Encryption engine
|
v
Crypto primitives
|
v
Envelope format
| Layer | Files | Responsibility |
|---|---|---|
| CLI | src/main.rs |
Parse commands, resolve passwords, open files |
| Stream | src/stream/ |
Encrypt and decrypt large files in chunks |
| Engine | src/engine.rs |
Orchestrate in-memory encryption/decryption |
| Crypto | src/crypto/ |
Key derivation and AEAD operations |
| Envelope | src/envelope/ |
Serialize and parse encrypted file metadata |
| Errors | src/error.rs |
Shared error surface |
The command-line interface does not implement cryptography directly. It delegates to the streaming layer, which delegates to the cryptographic core.
krypton/
|-- Cargo.toml # Rust package manifest
|-- Cargo.lock # Locked dependency versions
|-- Dockerfile # Multi-stage container build
|-- README.md # Project guide
|-- BENCHMARKS.md # Benchmark summary
|
|-- Docs/
| |-- architecture.md # Architecture notes
| |-- benchmarks.md # Benchmark interpretation
| |-- format.md # Binary format specification
| `-- security.md # Threat model and security notes
|
|-- src/
| |-- main.rs # CLI entry point
| |-- lib.rs # Library module exports
| |-- engine.rs # In-memory encryption engine
| |-- error.rs # Error types
| |
| |-- crypto/
| | |-- aead.rs # XChaCha20-Poly1305 wrapper
| | |-- kdf.rs # Argon2id key derivation
| | `-- mod.rs
| |
| |-- envelope/
| | |-- format.rs # Magic bytes, version, IDs, structs
| | |-- parse.rs # Envelope parser
| | `-- mod.rs # Envelope serializer exports
| |
| `-- stream/
| |-- encrypt.rs # Chunked streaming encryption
| |-- decrypt.rs # Chunked streaming decryption
| `-- mod.rs
|
|-- tests/
| |-- crypto.rs # Crypto primitive tests
| |-- engine.rs # Engine roundtrip tests
| |-- envelope.rs # Format serialization tests
| |-- error_surface.rs # Error behavior tests
| `-- stream.rs # Streaming tests
|
`-- benches/
|-- engine.rs # Engine benchmarks
|-- kdf.rs # Argon2id benchmark
|-- stream.rs # Streaming benchmark
`-- memory_profile.rs # Whole-file vs streaming comparison
When you run:
krypton encrypt secrets.txt --password hunter2Krypton follows this path:
1. CLI parses command
2. CLI resolves password
3. Input file is opened with a buffered reader
4. Output file is created
5. Streaming encryption starts
6. Salt is generated
7. Password is transformed into a 32-byte key using Argon2id
8. Stream header is written
9. File is read in 64 KB chunks
10. Each chunk is encrypted and authenticated
11. Each encrypted chunk record is written
12. Sensitive buffers are zeroized where applicable
src/main.rs
run_encrypt()
|
v
src/stream/encrypt.rs
encrypt_stream()
|
+--> crypto/kdf.rs
| derive_key()
|
+--> crypto/aead.rs
encrypt()
Streaming encryption writes a stream header first:
KRYSTRM
version
cipher id
kdf id
salt
Then it writes one record per chunk:
nonce
ciphertext length
ciphertext
authentication tag
When you run:
krypton decrypt secrets.txt.kry --password hunter2Krypton performs the reverse operation:
1. CLI parses command
2. CLI resolves password
3. Encrypted file is opened
4. Output file is created
5. Stream header is read
6. Magic bytes and version are validated
7. Cipher and KDF identifiers are validated
8. Salt is read from the encrypted file
9. Password is transformed into the same 32-byte key
10. Each encrypted chunk record is read
11. Each chunk is authenticated and decrypted
12. Plaintext chunks are written to the output file
If the password is wrong, the file is corrupted, or the metadata is invalid, decryption fails.
src/main.rs
run_decrypt()
|
v
src/stream/decrypt.rs
decrypt_stream()
|
+--> crypto/kdf.rs
| derive_key()
|
+--> crypto/aead.rs
decrypt()
Krypton's CLI uses streaming encryption for file operations.
The chunk size is defined in src/stream/encrypt.rs:
const CHUNK_SIZE: usize = 64 * 1024;That means the file is read in 64 KB pieces:
large file
|
+--> chunk 1: 64 KB --> encrypt --> write record
+--> chunk 2: 64 KB --> encrypt --> write record
+--> chunk 3: 64 KB --> encrypt --> write record
+--> final chunk --> encrypt --> write record
Without streaming, encrypting a 4 GB file would require loading that file into memory before encryption.
With streaming, Krypton only needs a bounded file buffer:
whole-file approach:
memory grows with input size
streaming approach:
file buffer stays around the chunk size
Krypton includes a benchmark that compares both approaches:
cargo bench --bench memory_profileSample result for a 32 MiB input:
Whole-file load then encrypt: 122.25 ms to 129.07 ms, approx 128.00 MiB peak heap growth
Chunked stream encrypt: 67.755 ms to 70.633 ms, approx 19.00 MiB peak heap growth
The exact numbers depend on the machine, but the important lesson is stable: whole-file encryption grows with file size; streaming keeps file-buffer memory bounded.
Krypton uses a binary envelope so encrypted data carries the metadata needed for safe decryption.
There are two related formats:
| Format | Used By | Magic Bytes |
|---|---|---|
| Standard envelope | In-memory engine API | KRY1 |
| Streaming envelope | CLI file encryption | KRYSTRM |
The standard in-memory envelope contains:
MAGIC
VERSION
CIPHER_ID
KDF_ID
SALT
NONCE
AAD_LENGTH
AAD
CIPHERTEXT
TAG
The streaming file format contains:
STREAM_MAGIC
VERSION
CIPHER_ID
KDF_ID
SALT
CHUNK_RECORD_1
CHUNK_RECORD_2
CHUNK_RECORD_3
...
Each chunk record contains:
NONCE
CIPHERTEXT_LENGTH
CIPHERTEXT
TAG
The envelope lets Krypton verify:
- this is a Krypton file
- the version is supported
- the cipher suite is supported
- the key derivation function is supported
- the encrypted payload has not been silently modified
For the full specification, see Docs/format.md.
cargo build --releaseThe compiled binary will be available at:
target/release/krypton
cargo run -- encrypt secrets.txt --password hunter2Or, after building:
krypton encrypt secrets.txt --password hunter2Default output:
secrets.txt.kry
cargo run -- decrypt secrets.txt.kry --password hunter2Or:
krypton decrypt secrets.txt.kry --password hunter2Default output:
secrets.txt
krypton encrypt secrets.txt --output backup.kry --password hunter2krypton decrypt backup.kry --output recovered.txt --password hunter2This is useful for scripts and CI jobs:
KRYP_PASS=hunter2 krypton encrypt secrets.txt --password-env KRYP_PASSPowerShell example:
$env:KRYP_PASS = "hunter2"
krypton encrypt secrets.txt --password-env KRYP_PASSIf no password flag is provided, Krypton prompts for one:
krypton encrypt secrets.txtNote: the current prompt reads from standard input. It is functional, but it is not yet a hidden terminal password prompt.
Build the image:
docker build -t krypton .Encrypt a file from the current directory:
docker run --rm -v ${PWD}:/data krypton encrypt /data/secrets.txt --password hunter2Decrypt:
docker run --rm -v ${PWD}:/data krypton decrypt /data/secrets.txt.kry --password hunter2Use an environment variable:
docker run --rm \
-e KRYP_PASS=hunter2 \
-v ${PWD}:/data \
krypton encrypt /data/secrets.txt --password-env KRYP_PASSThe runtime image uses a non-root krypton user.
cargo testThe test suite covers:
- AEAD encryption/decryption behavior
- key derivation behavior
- envelope serialization and parsing
- engine roundtrips
- streaming roundtrips
- wrong-password failures
- tampering failures
- large-file streaming behavior
cargo benchcargo bench --bench kdf
cargo bench --bench engine
cargo bench --bench stream
cargo bench --bench memory_profileCriterion reports are generated under:
target/criterion/report/index.html
| Benchmark | Purpose |
|---|---|
kdf |
Measures Argon2id key derivation cost |
engine |
Measures in-memory encrypt/decrypt operations |
stream |
Measures streaming encryption throughput |
memory_profile |
Compares whole-file loading vs chunked streaming |
Krypton is designed to protect against:
- reading encrypted files without the password
- offline password guessing at high speed
- ciphertext tampering
- corrupted encrypted chunks
- unsupported or malformed envelope metadata
- accidental parsing of unrelated files
Krypton does not protect against:
- weak passwords
- malware on the machine running Krypton
- attackers who can read the password as it is typed
- compromised operating systems
- full memory disclosure attacks
- side-channel attacks
| Area | Choice |
|---|---|
| KDF | Argon2id |
| Cipher | XChaCha20 |
| Authentication | Poly1305 |
| Mode | AEAD |
| Salt | Random 16 bytes |
| Nonce | Random 24 bytes |
| Streaming | Independent authenticated chunk records |
| Secret cleanup | zeroize for selected sensitive buffers |
Krypton is a security-focused engineering project, but it has not undergone a formal third-party cryptographic audit. Do not treat it as audited production cryptography without review.
For more details, see Docs/security.md.
cargo fmtcargo clippy --all-targets --all-features -- -D warningscargo testcargo build --releasecargo fmt
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo bench --bench memory_profile --no-runPlanned or natural next improvements:
- hidden password prompt support
- safer output-file handling to avoid accidental overwrite
- stronger memory zeroization coverage
- fuzz testing for envelope parsers
- key-file or raw-key encryption mode
- structured CLI errors instead of panics
- optional compression before encryption
- hardware-backed key integration
- WASM-compatible encryption API
- formal cryptographic review preparation
Krypton demonstrates how to build a modern password-based encryption tool with:
- password hardening through Argon2id
- authenticated encryption through XChaCha20-Poly1305
- tamper detection through authentication tags
- versioned metadata through a binary envelope format
- large-file support through chunked streaming
- reproducible usage through CLI, Docker, tests, and benchmarks
The simplest mental model is:
password + file
|
v
Krypton derives a key, encrypts authenticated chunks, and writes a .kry file
|
v
only the correct password can authenticate and recover the original bytes