@lib-q/hpke
v0.0.10
Published
Post-quantum HPKE (RFC 9180) for Node.js
Downloads
377
Maintainers
Readme
lib-q-hpke
RFC 9180–aligned Hybrid Public Key Encryption using post-quantum-only primitives (ML-KEM family for the HPKE KEM role in the default provider path; no classical KEM or classical signatures in that path).
Overview
lib-q-hpke implements HPKE for the lib-q stack: protocol logic and types in this crate, with KEM/AEAD/hash work delegated through lib-q-kem, lib-q-aead, and lib-q-hash. The high-level HpkeContext holds a lib_q_core::CryptoProvider for KemContext and an Arc<dyn HpkeCryptoProvider + Send + Sync> (default PostQuantumProvider) for encapsulation, KDF, AEAD, and exporter operations. Use HpkeContext::with_hpke_crypto to swap the HPKE backend; with_provider only replaces the inner KemContext crypto. Today the default provider wires ML-KEM only for HPKE KEM (other PQ KEMs may exist in the workspace but are not in the HpkeKem catalog yet).
Interoperability
Profiles, a mode×suite matrix, and fixture provenance are documented under the workspace interoperability.md and hpke-architecture.md. For code, see lib_q_hpke::interop. Run the integrator-oriented example (requires std):
cargo run -p lib-q-hpke --example hpke_interop_negotiation --features std
Supported Algorithms
Key Encapsulation Mechanisms (KEM)
- ML-KEM-512 (Level 1 security)
- ML-KEM-768 (Level 3 security)
- ML-KEM-1024 (Level 5 security)
Key Derivation Functions (KDF)
- HKDF-SHAKE128
- HKDF-SHAKE256
- HKDF-SHA3-256
- HKDF-SHA3-512
Authenticated Encryption (AEAD)
- Saturnin-256 (32-byte key, 16-byte nonce, 32-byte tag)
- SHAKE256-based AEAD (16-byte tag)
- Keccak duplex-sponge AEAD via
lib-q-aead— enable Cargo featureduplex-sponge-aeadon this crate (when using the umbrellalib-qcrate, enablehpke-duplex-aead) - Export-only mode (
HpkeAead::Export) for exporter-secret usage without message encryption
Quick Start
use lib_q_core::{Algorithm, KemContext, KemPublicKey, KemSecretKey};
use lib_q_hpke::HpkeContext;
use libq::LibQCryptoProvider;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create HPKE context (umbrella `lib-q` crate, Rust name `libq`)
let provider = Box::new(LibQCryptoProvider::new()?);
let mut hpke_ctx = HpkeContext::with_provider(provider);
// Generate key pair
let mut kem_ctx = KemContext::with_provider(Box::new(LibQCryptoProvider::new()?));
let keypair = kem_ctx.generate_keypair(Algorithm::MlKem512)?;
let recipient_pk = KemPublicKey::new(keypair.public_key().as_bytes().to_vec());
let recipient_sk = KemSecretKey::new(keypair.secret_key().as_bytes().to_vec());
// Encrypt message
let message = b"Hello, HPKE!";
let (encapsulated_key, ciphertext) = hpke_ctx.seal(
&recipient_pk,
b"application-info",
b"additional-data",
message,
)?;
// Decrypt message
let decrypted = hpke_ctx.open(
&encapsulated_key,
&recipient_sk,
b"application-info",
b"additional-data",
&ciphertext,
)?;
assert_eq!(decrypted, message);
Ok(())
}HPKE Modes
Base Mode
Standard HPKE without additional authentication.
let mut sender_ctx = hpke_ctx.setup_sender(&recipient_pk, b"session-info")?;
let ciphertext = sender_ctx.seal(b"aad", message)?;PSK Mode
Pre-shared key authentication.
let psk = b"shared-secret-key";
let psk_id = b"psk-identifier";
let mut sender_ctx = hpke_ctx.setup_sender_psk(
&recipient_pk,
b"session-info",
psk,
psk_id,
)?;Auth Mode — disabled (B14, interim)
Sender authentication using asymmetric keys.
This mode currently fails closed.
AuthEncap/AuthDecapdo not bind the sender's static secret key into the authentication tag or shared secret (RFC 9180 Section 5.1.3 gap), so anyone who can reach the recipient's public key could forge a tag under an arbitrary claimed sender identity. Rather than ship that silently, every call using this mode returns an explicit error. Use Base or PSK mode instead until a cryptographer-reviewed fix ships. SeeSECURITY.mdfor details.
// Returns an error today (B14) — shown for the intended future API shape.
let sender_ctx_result = hpke_ctx.setup_sender_auth(
&recipient_pk,
b"session-info",
&sender_sk,
&sender_pk,
);
assert!(sender_ctx_result.is_err());AuthPSK Mode — disabled (B14, interim)
Combined PSK and sender authentication. Routes through the same AuthEncap/AuthDecap gap as
Auth mode above and fails closed identically.
// Returns an error today (B14) — shown for the intended future API shape.
let sender_ctx_result = hpke_ctx.setup_sender_auth_psk(
&recipient_pk,
b"session-info",
psk,
psk_id,
&sender_sk,
&sender_pk,
);
assert!(sender_ctx_result.is_err());PSK / AuthPSK wire format
For PSK and AuthPSK modes, the default is HpkePskWireFormat::Rfc9180 (RFC 9180 on-the-wire layout). Both peers may set HpkePskWireFormat::LibQCommitmentSuffix with HpkeContext::set_psk_wire_format to reject wrong (psk, psk_id) or a mismatched primary KEM ciphertext before decapsulation; that suffix is not interoperable with strict third-party RFC 9180 implementations.
Cargo features (summary)
| Feature | Purpose |
|---------|---------|
| std | Standard library support |
| alloc | Required for normal operation (enforced by the crate) |
| ml-kem | ML-KEM through lib-q-kem (default) |
| hash | HKDF hash backends via lib-q-hash (default) |
| saturnin / shake256 | AEAD algorithms (defaults) |
| duplex-sponge-aead | Duplex-sponge AEAD in HpkeAead::DuplexSpongeAead |
| wasm | wasm32 bindings and helpers |
| secure-rng | OS-backed RNG where applicable (default) |
See Cargo.toml for the full feature matrix and optional dev dependencies.
Documentation
- Architecture overview — workspace-level HPKE design (PQ-only path, PSK wire format, interoperability)
- API Reference — public API
- Security Considerations — security analysis and best practices
- Architecture details — module layout and provider wiring in this crate
Testing
The implementation includes comprehensive test coverage:
# Run all tests
cargo test
# Run specific test suites
cargo test --test psk_mode_comprehensive_tests
cargo test --test authpsk_mode_comprehensive_tests
cargo test --test security_validation_comprehensive_testsWire sizes (KEM)
Public key and encapsulated ciphertext lengths match HpkeKem (public_key_len / enc_len):
| KEM | NIST category (approx.) | Public key | Encapsulated ciphertext | |-----|-------------------------|------------|-------------------------| | ML-KEM-512 | 1 | 800 B | 768 B | | ML-KEM-768 | 3 | 1184 B | 1088 B | | ML-KEM-1024 | 5 | 1568 B | 1568 B |
AEAD ciphertext expansion is plaintext length plus the AEAD tag (e.g. 32 bytes for Saturnin-256); see HpkeAead::tag_len in src/types.rs.
Dependencies
lib-q-core- Core cryptographic types and interfaceslib-q-kem- Key encapsulation mechanism implementationslib-q-hash- Hash function implementationslib-q-aead- Authenticated encryption implementationslib-q(Rust importlibq) —LibQCryptoProviderfor demos; production integrations may use narrower providers
License
This project is licensed under the same terms as the lib-q project.
Subresource integrity (SHA-384)
Paths in integrity-manifest.json are relative to the package root (including web/ and nodejs/ when both ship).
