pq-key-encoder
Zero-dependency* post-quantum key encoding library for Rust. Encodes and decodes ML-KEM, ML-DSA, and SLH-DSA keys across DER (SPKI/PKCS#8), PEM, and JWK formats.
*Only depends on pq-oid and zeroize.
Supported Algorithms
| Family | Variants |
|---|---|
| ML-KEM | ML-KEM-512, ML-KEM-768, ML-KEM-1024 |
| ML-DSA | ML-DSA-44, ML-DSA-65, ML-DSA-87 |
| SLH-DSA | SHA2/SHAKE variants: 128s, 128f, 192s, 192f, 256s, 256f |
All 18 NIST post-quantum algorithm parameter sets are supported.
Installation
[]
= "0.0.1"
Feature Flags
| Feature | Default | Description |
|---|---|---|
std |
Yes | Enables std::error::Error impl |
pem |
Yes | PEM encoding/decoding |
jwk |
No | JWK encoding/decoding |
For no_std environments:
[]
= { = "0.0.1", = false }
Usage
Public Keys
use ;
use MlKem;
let alg = MlKem;
// From raw bytes
let key = from_bytes?;
// DER (SPKI) round-trip
let der = key.to_der;
let decoded = from_spki?;
// Zero-copy decode (borrows from input)
let key_ref = from_spki?;
assert_eq!;
// Encode into an existing buffer (zero intermediate allocations)
let mut buf = Vecnew;
key.encode_der_to;
Private Keys
use ;
use MlDsa;
let alg = MlDsa;
// From raw bytes
let key = from_bytes?;
// DER (PKCS#8) round-trip
let der = key.to_der;
let decoded = from_pkcs8?;
// Private keys are zeroized on drop
// into_bytes() returns Zeroizing<Vec<u8>> for safe ownership transfer
let bytes = key.into_bytes; // automatically zeroized when dropped
PEM
Requires the pem feature (enabled by default).
use ;
// Encode
let pem = public_key.to_pem;
// -----BEGIN PUBLIC KEY-----
// MIIEMjALBgkr...
// -----END PUBLIC KEY-----
// Decode (type-specific)
let key = from_pem?;
// Auto-detect public or private
let key = from_pem?;
JWK
Requires the jwk feature.
= { = "0.0.1", = ["jwk"] }
use ;
// Public JWK
let jwk = public_key.to_jwk;
let json = jwk.to_json;
// {"kty":"PQC","alg":"ML-KEM-768","x":"<base64url>"}
let decoded = from_jwk?;
// Private JWK (requires corresponding public key)
let priv_jwk = private_key.to_jwk?;
// Parse from JSON string (auto-detects public/private)
let key = from_jwk_str?;
Auto-Detection
The Key enum auto-detects key type from encoded data:
use Key;
// Auto-detect SPKI (public) or PKCS#8 (private) from DER
let key = from_der?;
// Also works with PEM labels and JWK fields
let key = from_pem?;
Security
- Private key bytes are zeroized on drop via the
zeroizecrate Debugoutput redacts private key material ([REDACTED])PrivateKey::into_bytes()returnsZeroizing<Vec<u8>>for safe ownership transferPrivateJwkfields are zeroized on drop- Base64 decoding enforces strict padding validation
- JWK parser rejects duplicate fields to prevent key confusion attacks
- JWK parsing enforces resource limits to prevent denial-of-service:
- Input size capped at 64 KiB
- Field count capped at 32
- Nesting depth capped at 8
Design
no_stdcompatible withalloc— suitable for embedded and blockchain runtimes- Zero-copy decoding —
PublicKeyRef/PrivateKeyRefborrow directly from input buffers - Zero intermediate allocations on encode paths — two-pass pattern computes size first, then writes directly to the output buffer
- No format-specific dependencies — DER, PEM, base64, and JSON are all hand-rolled with minimal code
- OID matching uses raw DER byte comparison against compile-time constants (no string allocation)
Minimum Supported Rust Version
This crate requires Rust 1.78 or later. The MSRV is tested in CI and will only be bumped in minor or major version releases.
Contributing
Contributions are welcome! Please open an issue or submit a pull request on GitHub.
License
MIT