Status
Current version: 0.8.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) | shipped |
| 0.9.0 | Fuzz testing | next |
| 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/.
What's in 0.8.0
Symmetric AEAD encryption — crypt_io::aead
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.
Hashing — crypt_io::hash
- 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.
Message Authentication — crypt_io::mac
- 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.
Key Derivation — crypt_io::kdf
- 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.
Stream / File Encryption — crypt_io::stream
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.
Examples — examples/
Five runnable end-to-end programs covering the major use cases:
Portfolio integration
mod-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.
Benchmarks — benches/
Five criterion suites, one per module: aead, hash, mac, kdf, stream. Run all:
Measured numbers committed in docs/PERFORMANCE.md on the reference machine; the section above is the TL;DR.
What's not in 0.8.0 yet
- Fuzz testing — Phase 0.9.0.
- Cross-platform bench numbers (x86 without AES-NI, ARMv8 with crypto extensions) — deferred to post-1.0 ops work.
- Zero-allocation encrypt path (
encrypt_into(&mut buf, ...)) — post-1.0; documented as the main wrapping-overhead gap vs upstream RustCrypto. - 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.
Installation
[]
= "0.8"
Or:
MSRV: Rust 1.85 (edition 2024). Older toolchains will not build.
Quick start
AEAD round-trip
use Crypt;
let key = ; // your 256-bit key
let crypt = new; // ChaCha20-Poly1305 by default
let ciphertext = crypt.encrypt?;
let recovered = crypt.decrypt?;
assert_eq!;
# Ok::
AES-256-GCM (when you want hardware acceleration)
use Crypt;
let key = ;
let crypt = aes_256_gcm; // requires `aead-aes-gcm` (default-on)
let ciphertext = crypt.encrypt?;
let recovered = crypt.decrypt?;
# Ok::
Hashing
use hash;
let digest = blake3; // [u8; 32]
let sha256 = sha256; // [u8; 32]
let sha512 = sha512; // [u8; 64]
let xof = blake3_long; // Vec<u8>, 128 bytes
MAC with constant-time verify
use mac;
let key = b"shared secret";
let data = b"message to authenticate";
let tag = hmac_sha256?;
assert!;
// Never `tag == expected_tag` against a secret — use the `*_verify` path.
# Ok::
BLAKE3 keyed mode — typed key, infallible:
use mac;
let key = ;
let tag = blake3_keyed;
assert!;
Streaming (large or chunked inputs)
use Blake3Hasher;
let mut h = new;
h.update;
h.update;
let digest = h.finalize;
Key derivation
Deriving a subkey from a master:
use kdf;
let master = ;
let session_key = hkdf_sha256?;
assert_eq!;
# Ok::
Hashing a password (Argon2id, OWASP-recommended defaults):
use kdf;
let phc = argon2_hash?;
assert!;
# Ok::
Encrypt a file
use Algorithm;
use stream;
let key = ;
encrypt_file?;
decrypt_file?;
# Ok::
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.
Design philosophy
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)
When to use crypt-io
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
Performance
Measured on a reference machine (AMD Ryzen 9 9950X3D, AES-NI + SHA-NI + AVX-512, WSL2 Ubuntu, Rust 1.85.0). Full methodology + per-suite tables in docs/PERFORMANCE.md.
| Operation | Target | Measured | Status |
|---|---|---|---|
| ChaCha20-Poly1305 encrypt, 1 KiB | < 2 µs | 1.72 µs | ✅ |
| AES-256-GCM encrypt, 1 KiB (HW accel) | < 1 µs | 944 ns | ✅ |
| BLAKE3 hash, 1 KiB | < 500 ns | 1.07 µs | ⚠️ revised |
| BLAKE3 hash, 64 KiB | — | 11.24 GiB/s | ✅ |
| SHA-256 hash, 1 KiB (SHA-NI) | < 2 µs | 426 ns | ✅ |
| HMAC-SHA256, 1 KiB | < 3 µs | 565 ns | ✅ |
| HKDF-SHA256, 32-byte output | < 5 µs | 304 ns | ✅ |
| Argon2id, default params | ~100 ms | ~9 ms (Zen 5 too fast — tune t_cost) |
⚠️ |
| Stream encrypt, 1 MiB plaintext | > 1 GiB/s | 932-999 MiB/s | ⚠️ marginal |
Reproduce: cargo bench --all-features (numbers vary by hardware — see PERFORMANCE.md for the portable analysis).
Documentation
docs/API.md— complete public-API reference for the current version.docs/PERFORMANCE.md— measured throughput, reference-machine specs, contract-check matrix, parameter-choice guidance.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.
Standards
- 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).
License
Dual-licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT License (LICENSE-MIT)
at your option.
Contribution
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.