# 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

| You want… | Pick |
|---|---|
| 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

| Check | Result |
|---|---|
| `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.
