# v0.3.0 — AES-256-GCM Joins the Default Stack
**Release date:** 2026-05-21
**Status:** pre-1.0 milestone. The public API is allowed to evolve in
breaking ways through the `0.x` series; 1.0 freezes it.
---
## What this release is
AES-256-GCM joins ChaCha20-Poly1305 as a first-class algorithm in
`Crypt`. Same `encrypt` / `decrypt` surface, same key length, same
nonce length, same wire layout — pick the algorithm at handle
construction and the rest of your code doesn't change.
If you're running on AES-NI hardware (every Intel / AMD CPU since
~2010, modern Apple Silicon, AWS Graviton), AES-256-GCM gives you a
2–5× throughput win over the software path. If you're not — or you
can't guarantee the deployment target — stay on ChaCha20-Poly1305.
Both are safe defaults; the choice is about hardware utilisation and
interop, not security.
---
## Highlights
- **`Algorithm::Aes256Gcm`** — new variant in the `#[non_exhaustive]`
enum. Same 32-byte key, 12-byte nonce, 16-byte tag, same wire
format. Pick it via `Crypt::with_algorithm(Algorithm::Aes256Gcm)`
or the shorter `Crypt::aes_256_gcm()`.
- **NIST GCM Test Cases 14 + 15** as known-answer tests, verifying
the upstream `aes-gcm` primitive matches the spec byte-for-byte.
- **Cross-algorithm guard tests** — a ChaCha20 ciphertext cannot be
decrypted as AES-GCM (and vice versa) — it surfaces as
`Error::AuthenticationFailed`. Routing the wrong wire to the
wrong algorithm is a detectable failure, not a silent corruption.
- **Hardware acceleration is automatic.** The upstream `aes-gcm`
crate detects AES-NI (x86) or ARMv8 crypto extensions (AArch64)
at runtime and dispatches to the hardware path. No `cfg` gates,
no manual feature work on the consumer side.
- **Defaults extended.** `cargo add crypt-io` now gives you both
ChaCha20-Poly1305 and AES-256-GCM. The 0.2.0 set is still
reachable by opting out of `aead-aes-gcm`.
---
## API at a glance
```rust
use crypt_io::{Algorithm, Crypt};
// Default — ChaCha20-Poly1305.
let chacha = Crypt::new();
// AES-256-GCM via the convenience constructor.
let aes = Crypt::aes_256_gcm();
// AES-256-GCM via the algorithm-agile surface.
let aes_alt = Crypt::with_algorithm(Algorithm::Aes256Gcm);
assert_eq!(aes, aes_alt);
let key = [0u8; 32];
let ct = aes.encrypt(&key, b"hello AES")?;
let pt = aes.decrypt(&key, &ct)?;
assert_eq!(&*pt, b"hello AES");
# Ok::<(), crypt_io::Error>(())
```
The wire layout (`nonce || ciphertext || tag`, 28 bytes of
overhead) is the same for both algorithms. The choice of algorithm
is *not* stored in the buffer — callers that need to route stored
ciphertexts back to the right algorithm should keep that
association externally (e.g. via a key-id in their metadata).
---
## When to pick which
| The safe default with no thinking required | ChaCha20-Poly1305 |
| Maximum throughput on AES-NI / ARMv8 hardware | AES-256-GCM |
| Interop with TLS, JWE A256GCM, FIPS-spec'd protocols | AES-256-GCM |
| A target without hardware AES (embedded, older ARM, RISC-V) | ChaCha20-Poly1305 |
| Post-quantum-safe at 256-bit symmetric strength | Either (both are 256-bit) |
---
## What's NOT in 0.3.0
- **Measured HW-acceleration speedup numbers** — the `aes-gcm`
crate's runtime dispatch is the source of the win, and a
rigorous side-by-side measurement belongs in Phase 0.8.0 where
the full criterion harness lands. The functional correctness is
in by 0.3.0; the verified-throughput-claim is a 0.8.0 deliverable.
- **Cross-algorithm benchmark** — same reason. The 0.8.0 perf
document will publish ChaCha20-vs-AES-GCM tables at the same
input sizes.
- **Hashing / MAC / KDF / Streaming** — phases 0.4, 0.5, 0.6, 0.7
on the roadmap.
---
## Security notes
- **`AuthenticationFailed` is opaque across both algorithms.** Wrong
key, tampered ciphertext, tampered tag, AAD mismatch, *and*
wrong-algorithm routing all surface as the single
`Error::AuthenticationFailed`. An attacker observing failures
cannot tell which mode triggered them.
- **Constant-time tag verification** preserved by deferring to the
upstream `aes-gcm` and `chacha20poly1305` crates. No equality
comparisons on tag bytes in this wrapper.
- **AES-GCM nonce safety.** GCM is *especially* sensitive to nonce
reuse: repeating a `(key, nonce)` pair leaks the XOR of the two
plaintexts and the GHASH authentication key. The public API draws
a fresh nonce from the OS CSPRNG for every encrypt call — this
failure mode cannot be triggered through `Crypt::encrypt`.
- **No bytes in errors.** `Error` variants carry lengths, names,
and `&'static str` reasons — never key material, plaintext,
ciphertext, or nonces.
---
## Compatibility & build
- **Default features extended.** `default = ["std", "zeroize",
"aead-chacha20", "aead-aes-gcm", "hash-blake3", "mac-hmac",
"kdf-hkdf"]`. To stay on the 0.2.0 default set (no `aes-gcm`
dep), use `default-features = false` and pick the features you
want explicitly.
- **MSRV** unchanged: Rust 1.85 (edition 2024).
- **No breaking changes to `Crypt` / `Algorithm` / `Error` /
`Result`.** Match sites that wildcard `Algorithm` and `Error`
(both `#[non_exhaustive]`) compile without modification.
---
## Installation
```toml
[dependencies]
crypt-io = "0.3"
```
Or:
```bash
cargo add crypt-io
```
---
## Verification
| `cargo fmt --all -- --check` | clean |
| `cargo clippy --all-targets --all-features -- -D warnings` | clean |
| `cargo test --all-features` | 43 unit + 1 smoke + 3 doctest — all passing |
| NIST GCM Test Case 14 (256-bit key, 128-bit plaintext) | byte-exact match |
| NIST GCM Test Case 15 (256-bit key, full data path) | byte-exact match |
| Cross-algorithm ciphertext routing | both directions surface `AuthenticationFailed` |
| MSRV (1.85) build | clean |
---
## Acknowledgements
`crypt-io` does not implement cryptographic primitives. The math
new in 0.3.0 comes from:
- [`aes-gcm`](https://crates.io/crates/aes-gcm) — RustCrypto's
AES-GCM implementation, including the AES-NI / ARMv8 dispatch.
- [`aes`](https://crates.io/crates/aes) — the underlying AES block
cipher with hardware-acceleration backends.
Plus, carried forward from 0.2.0:
- [`chacha20poly1305`](https://crates.io/crates/chacha20poly1305),
[`chacha20`](https://crates.io/crates/chacha20),
[`poly1305`](https://crates.io/crates/poly1305) — the ChaCha20
primitive stack.
- [`mod-rand`](https://crates.io/crates/mod-rand) — Tier 3 OS-backed
CSPRNG for nonce generation.
---
## What's next
Phase 0.4.0: Hashing. `BLAKE3`, `SHA-256`, `SHA-512` with a
consistent free-function API in `crypt_io::hash`, plus streaming
hashers for large inputs and known-answer vectors per algorithm.