lib-q-hpke 0.0.10

HPKE implementation for lib-q
Documentation

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 feature duplex-sponge-aead on this crate (when using the umbrella lib-q crate, enable hpke-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/AuthDecap do 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. See SECURITY.md for 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

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_tests

Wire 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 interfaces
  • lib-q-kem - Key encapsulation mechanism implementations
  • lib-q-hash - Hash function implementations
  • lib-q-aead - Authenticated encryption implementations
  • lib-q (Rust import libq) — LibQCryptoProvider for demos; production integrations may use narrower providers

License

This project is licensed under the same terms as the lib-q project.