AEAD encryption, hashing, and message authentication for Rust
Algorithm-agile. RustCrypto-backed primitives. Simple API. REPS-disciplined.
crypt-io is a focused encryption library that wraps battle-tested cryptographic primitives (from RustCrypto and the BLAKE3 team) behind a clean, hard-to-misuse API. Built from the ground up with REPS discipline, algorithm agility, and tight portfolio integration (mod-rand for CSPRNG nonces, error-forge for error metadata), it targets the symmetric-crypto needs that most applications actually have: encrypt some data, hash some data, authenticate a tag, derive a key.
Unlike monolithic crypto crates that try to be everything, crypt-io stays focused. No asymmetric crypto, no PGP, no TLS — those are different problems best solved by purpose-built crates. crypt-io is the foundation primitive that handles the 95% case with a clean API where the easy path is also the secure path: constant-time verification for MACs, fresh nonces per call for AEAD, redaction-clean errors, and a hash module that deliberately won't let you accidentally use a raw hash as a MAC.
Current version: 0.7.0 (2026-05-22). Pre-1.0 — the public API is allowed to evolve in breaking ways through the 0.x series; 1.0.0 freezes it.
| Phase | Surface | Status |
|---|---|---|
| 0.1.0 | Scaffold, REPS baseline, CI | shipped |
| 0.2.0 | AEAD foundation — ChaCha20-Poly1305 | shipped |
| 0.3.0 | AES-256-GCM + algorithm selection | shipped |
| 0.4.0 | Hashing — BLAKE3 (+ XOF), SHA-256, SHA-512 | shipped |
| 0.5.0 | MAC — HMAC-SHA256/512, BLAKE3 keyed | shipped |
| 0.6.0 | KDF — HKDF-SHA256/512, Argon2id | shipped |
| 0.7.0 | Stream / file encryption | shipped |
| 0.8.0 | Performance verification (criterion benches) | next |
| 0.9.0 | Fuzz testing | planned |
| 0.10.0 | Docs + Release Candidate | planned |
| 1.0.0 | Stable Release | planned |
See .dev/ROADMAP.md for the full milestone plan and CHANGELOG.md for per-version detail. Per-release notes live under docs/release/.
Crypt::new()— ChaCha20-Poly1305 (default, post-quantum-safe at 256 bits).Crypt::aes_256_gcm()— AES-256-GCM (hardware-accelerated on AES-NI / ARMv8 crypto extensions, runtime-dispatched by upstream).- Algorithm-agile API. Same
encrypt/decryptsurface, same wire format (nonce || ciphertext || tag), same 32-byte key. Switch by picking the constructor. - Fresh nonces per call via
mod-randTier 3 (OS CSPRNG). Nonce reuse cannot happen through the public API. - AAD support via
encrypt_with_aad/decrypt_with_aad. - RFC 8439 + NIST SP 800-38D known-answer tests verifying byte-exact output against the specs.
- BLAKE3 —
hash::blake3(32-byte default) +hash::blake3_long(XOF, any length) + streamingBlake3Hasher. - SHA-256 / SHA-512 —
hash::sha256/hash::sha512+ streamingSha256Hasher/Sha512Hasher. - NIST FIPS 180-4 known-answer tests for SHA-2; byte-pinned KATs for BLAKE3.
- Streaming-equals-one-shot verified at multiple chunk boundaries.
- Hash-only by design. No
with_key. Keyed hashing lives inmac.
- HMAC-SHA256 / HMAC-SHA512 —
mac::hmac_sha256/mac::hmac_sha512+ streamingHmacSha256/HmacSha512. - BLAKE3 keyed mode —
mac::blake3_keyed(typed 32-byte key, infallible) + streamingBlake3Mac. - Constant-time verification by default. Every algorithm exposes a
*_verifypath that compares against an expected tag via upstream constant-time comparators. Nevertag == expectedagainst a secret. - RFC 4231 known-answer tests for HMAC; byte-pinned KAT for BLAKE3 keyed.
- Wrong-length tags are rejections, not panics.
- HKDF-SHA256 / HKDF-SHA512 —
kdf::hkdf_sha256/kdf::hkdf_sha512for deriving subkeys from a master key, a Diffie-Hellman shared secret, or any other high-entropy input. Single-call extract-then-expand with optional salt andinfodomain-separator. - Argon2id —
kdf::argon2_hashfor hashing passwords with the OWASP-recommended parameter set (~100 ms per hash). Salt is generated fresh per-call viamod-randTier 3 and embedded in the returned PHC string — callers don't manage salt storage.kdf::argon2_verifyfor constant-time verification against a stored PHC string.kdf::argon2_hash_with_params+Argon2Paramsfor callers with different cost tolerances. - HKDF is not for passwords. Module documentation explicitly distinguishes the two — HKDF assumes high-entropy input, Argon2id assumes low-entropy input that needs brute-force resistance.
- RFC 5869 known-answer tests for HKDF-SHA256 (Test Cases 1 + 3); SHA-512 cross-checked against the upstream
hkdfcrate.
StreamEncryptor/StreamDecryptor— chunked AEAD for data that doesn't fit in memory. Symmetricupdate()/finalize()triad on both sides; callers don't have to track chunk boundaries. Default 64 KiB chunks, tunable vianew_with_chunk_size(1 KiB..16 MiB).stream::encrypt_file/stream::decrypt_file— file-to-file helpers for the common workflow. Decryption reads the algorithm from the stream header — no need to track which one was used.- STREAM-construction nonces (the same shape AGE uses) defeat truncation, chunk reordering, and chunk duplication. Header bytes are AAD on every chunk, so header tampering (algorithm byte, nonce prefix) fails verification on the first chunk. Wire format documented in
docs/API.md. - Final-chunk-always invariant. The encoder always emits a final chunk (even if it carries zero plaintext) so EOF detection is unambiguous — a stream that ends mid-chunk or after a non-final chunk fails to verify.
- 25 attack-surface integration tests in
tests/stream.rs— wrong key, body/tag tamper, three flavours of truncation, swapped chunks, duplicated chunks, header tamper, byte-by-byte feeding, file round-trip.
Five runnable end-to-end programs covering the major use cases:
cargo run --example aead_round_trip
cargo run --example password_hash --release # --release: Argon2id is intentionally slow
cargo run --example derive_subkeys
cargo run --example mac_authenticate
cargo run --example encrypt_filemod-rand— Tier 3 OS-backed CSPRNG for nonces.error-forge— declared dependency (deeper integration in a later phase).log-io(optional) — operation logging.metrics-lib(optional) — performance instrumentation.key-vault— peer crate; the consumer wires them together. No direct dependency.
- Benchmark suite — Phase 0.8.0. Performance targets are in the contract (see
.dev/ROADMAP.md); committed criterion-backed measurements land in 0.8. - Fuzz testing — Phase 0.9.0.
- Resumable streaming (checkpoint encryptor state, resume after a process restart) — post-1.0.
- Async file helpers — Phase 1.x.
- Asymmetric crypto, PGP, TLS, RNG, UUIDs, key storage — out of scope for the lifetime of this crate. Use
mod-rand,key-vault,rustls,sequoia-openpgp, etc.
[dependencies]
crypt-io = "0.7"Or:
cargo add crypt-ioMSRV: Rust 1.85 (edition 2024). Older toolchains will not build.
use crypt_io::Crypt;
let key = [0u8; 32]; // your 256-bit key
let crypt = Crypt::new(); // ChaCha20-Poly1305 by default
let ciphertext = crypt.encrypt(&key, b"plaintext data")?;
let recovered = crypt.decrypt(&key, &ciphertext)?;
assert_eq!(&*recovered, b"plaintext data");
# Ok::<(), crypt_io::Error>(())use crypt_io::Crypt;
let key = [0u8; 32];
let crypt = Crypt::aes_256_gcm(); // requires `aead-aes-gcm` (default-on)
let ciphertext = crypt.encrypt(&key, b"hello AES")?;
let recovered = crypt.decrypt(&key, &ciphertext)?;
# Ok::<(), crypt_io::Error>(())use crypt_io::hash;
let digest = hash::blake3(b"the quick brown fox"); // [u8; 32]
let sha256 = hash::sha256(b"the quick brown fox"); // [u8; 32]
let sha512 = hash::sha512(b"the quick brown fox"); // [u8; 64]
let xof = hash::blake3_long(b"input", 128); // Vec<u8>, 128 bytesuse crypt_io::mac;
let key = b"shared secret";
let data = b"message to authenticate";
let tag = mac::hmac_sha256(key, data)?;
assert!(mac::hmac_sha256_verify(key, data, &tag)?);
// Never `tag == expected_tag` against a secret — use the `*_verify` path.
# Ok::<(), crypt_io::Error>(())BLAKE3 keyed mode — typed key, infallible:
use crypt_io::mac;
let key = [0x42u8; 32];
let tag = mac::blake3_keyed(&key, b"message");
assert!(mac::blake3_keyed_verify(&key, b"message", &tag));use crypt_io::hash::Blake3Hasher;
let mut h = Blake3Hasher::new();
h.update(b"first chunk ");
h.update(b"second chunk");
let digest = h.finalize();Deriving a subkey from a master:
use crypt_io::kdf;
let master = [0x42u8; 32];
let session_key = kdf::hkdf_sha256(&master, Some(b"salt"), b"app:session:v1", 32)?;
assert_eq!(session_key.len(), 32);
# Ok::<(), crypt_io::Error>(())Hashing a password (Argon2id, OWASP-recommended defaults):
use crypt_io::kdf;
let phc = kdf::argon2_hash(b"correct horse battery staple")?;
assert!(kdf::argon2_verify(&phc, b"correct horse battery staple")?);
# Ok::<(), crypt_io::Error>(())use crypt_io::Algorithm;
use crypt_io::stream;
let key = [0u8; 32];
stream::encrypt_file("input.bin", "output.enc", &key, Algorithm::ChaCha20Poly1305)?;
stream::decrypt_file("output.enc", "decrypted.bin", &key)?;
# Ok::<(), crypt_io::Error>(())Chunked AEAD with the STREAM construction — works for files of any size, detects tampering / truncation / reordering. For in-memory streaming (network sockets, buffered I/O), use StreamEncryptor / StreamDecryptor directly.
See docs/API.md for the full reference.
crypt-io is intentionally focused:
- One job: symmetric crypto. Done well.
- No reinvention. Primitives come from RustCrypto and BLAKE3 (battle-tested, widely audited).
- Simple API. Encrypt in two lines. Hash in one. The easy path is the secure path.
- Algorithm agility. ChaCha20-Poly1305 by default, AES-256-GCM when you want hardware acceleration. Same
CryptAPI either way. - Constant-time discipline. MAC verification uses upstream constant-time comparators, never
==. Documented in module overviews. - Hash ≠ MAC.
Blake3Hasherhas nowith_key. The only way to produce a keyed tag is through themacmodule. This separation is deliberate. - Redaction-clean errors. No variant of
Errorever contains key material, plaintext, ciphertext, nonces, or tag bytes. - REPS-disciplined. Every commit passes
cargo fmt --check,cargo clippy --all-targets --all-features -- -D warnings,cargo test --all-features, andcargo docwith-D warnings.
What we explicitly do NOT do:
- Implement crypto primitives from scratch (use battle-tested upstreams)
- Asymmetric crypto (RSA, ECDSA, Ed25519) — different problem, separate crate
- PGP/GPG (use
sequoia-openpgp) - TLS (use
rustls) - Random number generation (use
mod-rand) - UUID generation (use
id-forge) - Key storage (use
key-vault)
Good fit:
- Encrypting data for storage (databases, file systems, caches)
- Encrypting API tokens or session data
- Authenticating messages, audit logs, signed records
- Hashing for integrity checks, fingerprinting, content-addressed storage
- HMAC signatures for outgoing requests (AWS SigV4, JWT HS256/HS512, webhooks)
- Composing with
key-vaultfor in-memory key handling
Wrong fit:
- TLS connections — use
rustls - OpenPGP interop — use
sequoia-openpgp - Digital signatures — use
ed25519-dalek - Key exchange — use
x25519-dalek - Random number generation — use
mod-rand
Verified by benchmarks in Phase 0.8.0 (criterion-backed, committed baselines). Until then these are documented targets, not measured numbers:
| Operation | Target |
|---|---|
| ChaCha20-Poly1305 encrypt, 1 KiB | < 2 µs |
| AES-256-GCM encrypt, 1 KiB (HW accel) | < 1 µs |
| BLAKE3 hash, 1 KiB | < 500 ns |
| SHA-256 hash, 1 KiB | < 2 µs |
| HMAC-SHA256, 1 KiB | < 3 µs |
| HKDF-SHA256, 32-byte output | < 5 µs |
| Argon2id, default params | ~100 ms (intentionally slow) |
| Stream encrypt throughput | > 1 GiB/s |
docs/API.md— complete public-API reference for the current version.CHANGELOG.md— per-version Added / Changed / Security entries.docs/release/— per-release notes (v0.2.0.md,v0.3.0.md, …)..dev/ROADMAP.md— milestone plan through 1.0.
- REPS (Rust Efficiency & Performance Standards) governs every decision. See
REPS.md. - MSRV: Rust 1.85.
- Edition: 2024.
- Cross-platform: Linux, macOS, Windows (CI matrix on stable + MSRV).
Dual-licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT License (LICENSE-MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.