CryptGuard v2.0.5
crypt_guard is a post-quantum sealing library centered on one safe default flow:
ML-KEM (FIPS 203) -> HKDF -> CGv2 authenticated envelope
The primary API produces one self-contained CGv2 envelope. Nonce handling,
key derivation, and KEM encapsulation are all internal — the caller provides
a public key and plaintext and receives a sealed Envelope back.
Current release version: 2.0.5. The Phase 4 safe-default upgrade remains the
primary supported path. The additive crypt_guard::hpke::rfc9180 API provides
vector-verified RFC 9180 setup for all five DHKEMs, all registered encryption
AEADs, and Base/PSK/Auth/AuthPSK; it is intentionally distinct from CGv2.
Separately, the default build exposes revision-pinned, experimental PQ HPKE APIs.
The compatibility API retains two pinned draft-ietf-hpke-pq-05 ML-KEM
profiles, while the full namespace also exposes vector-verified concrete
hybrids. That active Internet-Draft is not an
RFC or a finalized IANA profile; its literal revision is part of the protocol
identity.
Safe Default: Encryptor / Decryptor
use ;
use XChaCha20Poly1305;
#
use ;
use OsRng;
The Envelope type is a self-describing serialisable blob:
{ header, kem_ciphertext, nonce, ciphertext }. The nonce is
generated internally and bound into the AEAD AAD; callers never
juggle it separately.
What is New in v2
| Area | v1.x | v2 |
|---|---|---|
| KEM | pqcrypto Kyber (NIST Round 3) | ML-KEM (FIPS 203 final) |
| Signing | Falcon / Dilithium | ML-DSA (FIPS 204) + SLH-DSA (FIPS 205) |
| Key schedule | ad-hoc passphrase KDF | HKDF-SHA256/512 with domain-separated labels |
| Envelope | (ciphertext, kyber_secret) tuple |
CGv2 Envelope — one self-describing blob |
| Nonce | caller-managed, separate artifact | nonce embedded and bound inside envelope |
| Type enforcement | decorative content axis | compile-enforced content-axis typestate |
| API shape | macro-first | Encryptor/Decryptor staged builders (macros still work) |
Protocol: CGv2 authenticated envelope (not RFC 9180 HPKE)
crypt_guard v2 currently implements its own CGv2 envelope format. It uses a KEM, HKDF, and AEAD, but it is not an implementation of RFC 9180 HPKE: it does not use the RFC 9180 KEM interface, labeled key schedule, AEAD nonce sequencing, or wire format, and is not interoperable with RFC 9180 implementations.
The current CGv2 construction is:
Sender:
(kem_ct, shared_secret) = ML-KEM.Encapsulate(recipient_pk)
session_key, base_nonce = HKDF(ikm=shared_secret, salt=kem_ct,
info="crypt_guard:v2:aead:<alg>")
ciphertext = AEAD.Seal(session_key, nonce, aad, plaintext)
envelope = { header, kem_ct, nonce, ciphertext }
Receiver:
shared_secret = ML-KEM.Decapsulate(kem_ct, recipient_sk)
session_key = HKDF(same params)
plaintext = AEAD.Open(session_key, nonce, aad, ciphertext)
The shared secret is zeroized immediately after key derivation, following the
NIST SP 800-227 direction for KEM-based protocols. The header field carries the
algorithm identifiers (kem_id, kdf_id, aead_id) so the
envelope is self-describing and forwards-compatible.
The crypt_guard::api::hpke module is retained as a source-compatible legacy
CGv2 framing API. Its info and aad arguments are encoded inside an encrypted
HFv1 payload framing; they do not provide RFC 9180 HPKE setup or AEAD-AAD
semantics. The separate crypt_guard::hpke::rfc9180 module is the versioned
RFC 9180 setup and interoperability API; it is independent from this legacy
framing.
Experimental draft-ietf-hpke-pq-05 Base mode
The additive default
crypt_guard::hpke_pq::draft_ietf_hpke_pq_05_full namespace exposes the
draft-05 registry (ML-KEM, hybrid KEM identifiers, HKDF/SHAKE/TurboSHAKE KDFs,
and the registered AEADs). MLKEM768-P256, MLKEM1024-P384, and
MLKEM768-X25519 are vector-verified; no ML-KEM-only substitution is performed.
The
compatibility crypt_guard::hpke_pq::draft_ietf_hpke_pq_05 module remains
limited to these exact pinned Base-mode profiles:
- ML-KEM-768 / HKDF-SHA256 / AES-128-GCM
- ML-KEM-1024 / HKDF-SHA384 / AES-256-GCM
It is a vector-gated experimental implementation of an active Internet
Draft, not an RFC-standardized post-quantum HPKE profile. There is no
algorithm negotiation or fallback. Applications must store the protocol family,
the literal draft-ietf-hpke-pq-05 revision, and the exact profile alongside
the separately transported enc and ciphertext; they must select this reader
directly rather than trial-decrypting CGv2/HFv1 data.
No feature flag is required; the revision-pinned API is part of the default build.
use ;
let profile = MlKem768HkdfSha256Aes128Gcm;
let recipient_keys = generate_recipient_key_pair;
// `enc` is a separate transport value. Contexts own nonce sequencing; callers
// supply only setup info and AEAD AAD, never a nonce or a raw shared secret.
let = setup_base_sender?;
let ciphertext = sender.seal?;
let mut recipient = setup_base_receiver?;
assert_eq!;
# Ok::
The sender and recipient contexts are intentionally non-Clone, advance their
sequence only after a successful AEAD operation, and expose no manual-nonce
API. Wrong AAD, ciphertext, or a same-size modified enc all produce the same
opaque authentication failure when opening.
Supported Algorithms
Key Encapsulation (KEM)
| Marker | Algorithm | Security | Standard |
|---|---|---|---|
MlKem512 |
ML-KEM-512 | Category 1 | FIPS 203 |
MlKem768 |
ML-KEM-768 | Category 3 (recommended) | FIPS 203 |
MlKem1024 |
ML-KEM-1024 | Category 5 | FIPS 203 |
Authenticated Encryption (AEAD)
| Marker | Algorithm | Notes |
|---|---|---|
XChaCha20Poly1305 |
XChaCha20-Poly1305 | Default; 24-byte nonce |
AesGcmSiv |
AES-256-GCM-SIV | Nonce-misuse resistant |
Digital Signatures
| Algorithm | Feature | Standard |
|---|---|---|
| ML-DSA-44 / 65 / 87 | ml-dsa-backend (default) |
FIPS 204 |
| SLH-DSA | sign-slhdsa |
FIPS 205 |
Feature Flags
| Flag | Default | What it adds |
|---|---|---|
ml-kem-backend |
yes | ML-KEM-512/768/1024 (FIPS 203) |
ml-dsa-backend |
yes | ML-DSA-44/65/87 (FIPS 204) |
sign-slhdsa |
no | SLH-DSA (FIPS 205) |
aes-ctr |
no | AES-CTR stream cipher |
aes-xts |
no | AES-XTS disk encryption |
archive |
no | tar/xz/gz archive helpers |
legacy-pqclean |
no | Legacy Kyber/Falcon/Dilithium + old tuple API |
To use only the new FIPS path without legacy code:
[]
= { = "2.0.5", = true }
To include the legacy path for reading data encrypted with v1.x:
[]
= { = "2.0.5", = ["legacy-pqclean"] }
Typestate Design: Kyber<Process, Size, Content, Algorithm>
The underlying Kyber<P, S, C, A> type encodes four axes in the type:
| Axis | Variants |
|---|---|
| Process | Encryption, Decryption |
| Size | MlKem512, MlKem768, MlKem1024 |
| Content | Data, Message, Files |
| Algorithm | XChaCha20Poly1305, AesGcmSiv, … |
Calling encrypt_file on a Message instance is a compile error (E0599).
The Encryptor/Decryptor builders use the same underlying type but
expose only the safe Data path by default.
Signing
use OsRng;
use ;
let mut rng = OsRng;
let = keypair?;
let sig = sign?;
verify?;
# Ok::
Architecture
crypt_guard::api (Encryptor / Decryptor — safe entry points)
-> crypt_guard::protocol (CGv2 Envelope + Header + AAD construction)
-> crypt_guard::kem (ML-KEM backend trait + ML-KEM-512/768/1024)
-> crypt_guard::kdf (HKDF-SHA256/512 with domain-separated labels)
-> crypt_guard::core (AEAD wiring; typestate Kyber<P,S,C,A>)
-> crypt_guard::sign (ML-DSA, SLH-DSA)
-> crypt_guard::legacy (pqcrypto Kyber/Falcon/Dilithium — feature-gated)
Legacy Compatibility
If you have data encrypted with crypt_guard v1.x (pqcrypto Kyber, tuple-return
API, manual nonce), enable the legacy-pqclean feature:
[]
= { = "2.0.5", = ["legacy-pqclean"] }
The old Kyber<Encryption, Kyber1024, Message, AES> types, the
encryption! / decryption! macros, the kyber_keypair! macro, and
the tuple-returning encrypt_msg / decrypt_msg functions remain available
under the legacy module and through the crate re-exports.
// Legacy usage (requires --features legacy-pqclean)
use ;
let = kyber_keypair!;
let = encryption!?;
let plaintext = decryption!?;