crypt-io 0.6.0

AEAD encryption (ChaCha20-Poly1305, AES-256-GCM), hashing (BLAKE3, SHA-2), MAC (HMAC, BLAKE3 keyed), and KDF (HKDF, Argon2id) for Rust. Algorithm-agile. RustCrypto-backed primitives with REPS discipline. Simple API. Sub-microsecond throughput.
Documentation

Status

Current version: 0.6.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 next
0.8.0 Performance verification (criterion benches) planned
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/.

What's in 0.6.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 / decrypt surface, same wire format (nonce || ciphertext || tag), same 32-byte key. Switch by picking the constructor.
  • Fresh nonces per call via mod-rand Tier 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

  • BLAKE3hash::blake3 (32-byte default) + hash::blake3_long (XOF, any length) + streaming Blake3Hasher.
  • SHA-256 / SHA-512hash::sha256 / hash::sha512 + streaming Sha256Hasher / 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 in mac.

Message Authentication — crypt_io::mac

  • HMAC-SHA256 / HMAC-SHA512mac::hmac_sha256 / mac::hmac_sha512 + streaming HmacSha256 / HmacSha512.
  • BLAKE3 keyed modemac::blake3_keyed (typed 32-byte key, infallible) + streaming Blake3Mac.
  • Constant-time verification by default. Every algorithm exposes a *_verify path that compares against an expected tag via upstream constant-time comparators. Never tag == expected against 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-SHA512kdf::hkdf_sha256 / kdf::hkdf_sha512 for 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 and info domain-separator.
  • Argon2idkdf::argon2_hash for hashing passwords with the OWASP-recommended parameter set (~100 ms per hash). Salt is generated fresh per-call via mod-rand Tier 3 and embedded in the returned PHC string — callers don't manage salt storage. kdf::argon2_verify for constant-time verification against a stored PHC string. kdf::argon2_hash_with_params + Argon2Params for 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 hkdf crate.

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.

What's not in 0.6.0 yet

  • Stream / file encryption — Phase 0.7.0.
  • 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.
  • 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

[dependencies]
crypt-io = "0.6"

Or:

cargo add crypt-io

MSRV: Rust 1.85 (edition 2024). Older toolchains will not build.

Quick start

AEAD round-trip

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>(())

AES-256-GCM (when you want hardware acceleration)

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>(())

Hashing

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 bytes

MAC with constant-time verify

use 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));

Streaming (large or chunked inputs)

use crypt_io::hash::Blake3Hasher;

let mut h = Blake3Hasher::new();
h.update(b"first chunk ");
h.update(b"second chunk");
let digest = h.finalize();

Key derivation

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>(())

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 Crypt API either way.
  • Constant-time discipline. MAC verification uses upstream constant-time comparators, never ==. Documented in module overviews.
  • Hash ≠ MAC. Blake3Hasher has no with_key. The only way to produce a keyed tag is through the mac module. This separation is deliberate.
  • Redaction-clean errors. No variant of Error ever 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, and cargo doc with -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-vault for in-memory key handling

Wrong fit:

Performance targets

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 (0.7.0) > 1 GiB/s

Documentation

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:

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.