Post-quantum hybrid cryptography and at-rest key management as a standalone Rust library.
Security status: pre-audit. This crate has not gone through an independent cryptographic audit and should not be used to protect sensitive production data yet.
Every construction combines a classical and a post-quantum primitive, so it stays secure as long as either one holds:
- Encryption - X25519 + ML-KEM-1024, sealed with AES-256-GCM-SIV (
crypto::kyberbox) - Signatures - Ed25519 + ML-DSA-87, both provided by
crypto::sign - KDF / passwords - HKDF-SHA256, Argon2
The crate is #![deny(unsafe_code)] (the only unsafe is confined to secrets::arena, which
wraps OS-locked memory behind a safe API - mmap/mlock on unix, VirtualAlloc/VirtualLock
on Windows), secret types zeroize on drop, and all domain-separation labels are supplied by the
caller as a validated crypto::Context - the crypto itself is application-agnostic.
KeyManager owns an on-disk key store: it generates the hybrid identity, seals private keys under
a master key from a pluggable MkProvider, and performs crash-safe MK rotation and rewrap
automatically on a background thread. The only built-in provider stores the MK in cleartext and is gated
behind the non-default insecure-plaintext-mk feature (dev/tests only); production callers supply
password-backed, hardware-backed, TPM-backed, or application-specific sealing (see
examples/password_mkprovider.rs and docs/mkprovider-examples.md).
The MK is loaded from the provider once per operation by default; the non-default cached-mk feature
unseals it once at startup and after each rotation and keeps it in the locked arena instead.
Secret types (SecByte32, SecretBytes, MasterKey32, ...) zeroize on drop.
By default KeyManager is fail-closed: it takes an exclusive lock on the store directory and locks
its key material into RAM, refusing to start if either cannot be done. The non-default best-effort
feature relaxes each for platforms that cannot honor them (e.g. Android, where flock is
unsupported), proceeding on swappable memory or without the store lock and reporting the degraded
state through a callback. A live second writer on the same store is still rejected.
crypto::kyberbox is the X25519 + ML-KEM-1024 AEAD construction: the KEM produces a fresh
shared secret per message that is combined with the X25519 output through HKDF, so recovering the
message key requires breaking both branches. See docs/combiner.md for the exact
construction and its rationale, and docs/kyberbox.md for the wire format.
Secondary, deployment-agnostic building blocks layered on the pillars: opaque (OPAQUE PAKE +
export-key DEK wrapping), passwords (DEK generation), utils::store (TTL secret store).
cargo run --example kyberbox # hybrid encrypt/decrypt round-trip
cargo run --example password_mkprovider # KeyManager identity, master key sealed under a passphrase
# dev-only: same lifecycle on the built-in cleartext master-key provider
cargo run --features insecure-plaintext-mk --example keyfileuse lithium_core::crypto::Context;
use lithium_core::crypto::kyberbox::DualEncryptionPrivateKey;
use lithium_core::secrets::SecretBytes;
// Domain separation, built from validated segments; the library adds the /v1.
let ctx = Context::base("myapp")?.add("message")?;
// The recipient's hybrid (X25519 + ML-KEM-1024) encryption keypair.
let (recipient_priv, recipient_pub) = DualEncryptionPrivateKey::ephemeral()?;
let body = SecretBytes::from_slice(b"Hello world!");
// `seal` draws a fresh reply keypair per call and bundles its public half
// into `sealed`, bound into the ciphertext so a MITM cannot swap it. It
// returns the matching reply secret: drop it for one-shot sealing, or keep
// it to open the recipient's reply. `aad` is an optional caller header
// bound into the ciphertext (b"" = none).
let (sealed, _reply_priv) = recipient_pub.seal(&ctx, b"", &body)?;
// `open` returns the plaintext and the reply public key the sender bundled in.
let (plain, _reply_pub) = recipient_priv.open(&ctx, b"", &sealed)?;
assert_eq!(plain.expose_as_slice(), body.expose_as_slice());Not yet independently audited. The constructions, the hybrid-combiner rationale and the open
questions for an auditor are documented under docs/: combiner.md, kyberbox.md,
threat-model.md. The public API is intended to be frozen at 0.1 through the audit.
The library deliberately does not provide a whole application security model. In particular, the caller is responsible for authentic recipient public keys, unique domain-separation labels, replay protection, transport security, and deciding when to call key rotation.
To report a vulnerability, see SECURITY.md.
Contributions are accepted under AGPL-3.0-only with a grant for commercial relicensing, and
require a DCO sign-off - see CONTRIBUTING.md.
GNU AGPL-3.0-only, with a commercial license available (dual licensing) - see LICENSE.