crypto-pq
Secure, optimized post-quantum cryptography in pure Rust with no dependencies: ML-KEM, ML-DSA, SLH-DSA, FrodoKEM, HSS/LMS and XMSS, plus SHA-2, SHA-3, BLAKE2, BLAKE3, Ascon, HMAC and HKDF.
- Version
- 1.0.0
- Rust
- 1.88 or later
- License
- Apache-2.0 OR MIT
Why it exists
Post-quantum cryptography needs implementations that can be checked rather than taken on trust. crypto-pq is one.
- Post-quantum only. Every public-key algorithm is post-quantum. There are no classical or hybrid public-key schemes.
- Published standards. Each algorithm follows a NIST, ISO/IEC or RFC publication or, for BLAKE3, its specification. No drafts.
- No third-party code. No dependencies and no build script: the crate uses only Rust’s
coreandalloclibraries and the operating system. - Specified. A public specification fixes every operation, size, byte format and error. The crate is tested against it and against the official test vectors.
Algorithms
Thirteen algorithm families, each a Cargo feature. The default features enable all of them.
| Family | Standard | Parameter sets | Feature |
|---|---|---|---|
| Key encapsulation | |||
| ML-KEM | FIPS 203 | ML-KEM-512, ML-KEM-768, ML-KEM-1024 | ml-kem |
| FrodoKEM, eFrodoKEM | ISO/IEC 18033-2 Amd 2 | FrodoKEM-976 and -1344 and their ephemeral eFrodoKEM forms, each with AES or SHAKE | frodokem |
| Signatures | |||
| ML-DSA | FIPS 204 | ML-DSA-44, ML-DSA-65, ML-DSA-87; pure and pre-hash (HashML-DSA) | ml-dsa |
| SLH-DSA, HashSLH-DSA | FIPS 205, RFC 9909 | 12 sets (SHA2 or SHAKE; 128, 192 or 256; s or f), pure and pre-hash, and the 12 HashSLH-DSA values of RFC 9909 | slh-dsa |
| Stateful hash-based signatures | |||
| HSS/LMS | RFC 8554, RFC 9858, NIST SP 800-208 | LMS with SHA-256 or SHAKE256, m = 24 or 32, heights 5 to 25; LM-OTS with w = 1, 2, 4 or 8 | lms |
| XMSS, XMSSMT | RFC 8391, NIST SP 800-208 | The SP 800-208 sets: SHA-2 or SHAKE256, n = 24 or 32; XMSS heights 10, 16 and 20; eight XMSSMT height and layer pairs | xmss |
| Hashes, XOFs, MACs and KDFs | |||
| SHA-2 | FIPS 180-4 | SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256 | sha2 |
| SHA-3 | FIPS 202, NIST SP 800-185 | SHA3-224, -256, -384, -512; SHAKE128, SHAKE256; cSHAKE128, cSHAKE256; KMAC128, KMAC256 | sha3 |
| BLAKE2 | RFC 7693 | BLAKE2b-160, -256, -384, -512; BLAKE2s-128, -160, -224, -256; BLAKE2b-MAC, BLAKE2s-MAC | blake2 |
| BLAKE3 | BLAKE3 specification | BLAKE3 (XOF), BLAKE3-256, BLAKE3-MAC, BLAKE3-KDF | blake3 |
| Ascon | NIST SP 800-232 | Ascon-Hash256, Ascon-XOF128, Ascon-CXOF128 | ascon |
| HMAC | FIPS 198-1 | HMAC-SHA-224, -256, -384, -512 | hmac |
| HKDF | RFC 5869 | HKDF-SHA-256, -384, -512 | hkdf |
ML-KEM, ML-DSA and SLH-DSA keys encode as raw bytes, DER and PEM; FrodoKEM keys as raw bytes; HSS/LMS and XMSS public keys as raw bytes, DER and PEM. Every parameter set and its sizes are in Section 5 of the specification.
Getting started
Add the crate to Cargo.toml.
The API documentation is on docs.rs.
[dependencies]
crypto-pq = "1.0.0"To build only some algorithm families, turn off the default features and name the ones you need:
[dependencies.crypto-pq]
version = "1.0.0"
default-features = false
features = ["ml-kem", "hmac"]Key encapsulation with ML-KEM-768, followed by key confirmation: the two sides hold the same secret when a MAC tag made with one copy verifies with the other. MAC verification runs in constant time, so the secrets themselves are never compared.
use crypto_pq::{Error, HMAC_SHA_256, KeyGenOptions, ML_KEM_768};
fn main() -> Result<(), Error> {
let pair = ML_KEM_768.generate_key_pair(&KeyGenOptions::default())?;
let encapsulation = pair.public_key.encapsulate()?;
let shared_secret =
pair.private_key.decapsulate(&encapsulation.ciphertext)?;
let tag = HMAC_SHA_256
.digest(&encapsulation.shared_secret, b"key confirmation")?;
assert!(HMAC_SHA_256.verify(&shared_secret, b"key confirmation", &tag));
Ok(())
}
The crate is no_std and needs alloc. CPU-specific code for
x86-64 and ARM64 is selected at run time without any target
CPU flag, with portable code as the fallback on every target. Randomness comes from the
operating system; on a target without a source, such as bare metal, operations that draw
randomness return Error::RngFailure.
Usage notes
- Stateful keys
- An HSS/LMS, XMSS or XMSSMT private key keeps its state in a
StateStorethat the application implements. Itsupdateis an atomic, durable compare-and-swap, and a stored record is never restored from a copy or a backup. - hazmat
- The opt-in
hazmatfeature provides deterministic operations that take seeds and randomness from the caller, for known-answer and interoperability tests.
Security
Secrets are handled in constant time and wiped when they are no longer needed, and randomness comes from the operating system.
- Constant time. Code that handles secret data has no branch or memory access that depends on a secret. CI checks this under valgrind on x86-64 and ARM64, in the default and the portable builds.
- Data-independent timing. On ARM64 CPUs with FEAT_DIT, asymmetric secret operations run with DIT set;
enable_data_independent_timing()extends it to MACs and KDFs. - Wiping. Secret outputs are
SecretBytes, which wipe themselves when dropped, and private keys are wiped on drop. Seeds, expanded keys and intermediate values are wiped on success and on every error path. - Randomness. Each random value is requested from the operating system for the operation that uses it, with no user-space generator and no fallback.
- Self-tests. A pairwise consistency test runs at key generation by default and at every KEM private-key import, and every stateful signature is verified before it is released.
Section 14 of the specification states the security requirements. Report vulnerabilities privately through GitHub Security Advisories; the security policy describes the process.
Specification
A public specification, written in RFC style, is the contract that the crate is built and tested against.
It covers the API, the algorithms, the byte formats, the errors, the security requirements and the conformance rules. Its Rust profile states how this crate expresses it in Rust, and a decision log records the reasons behind its choices. Read it on GitHub.
- Vectors
- NIST ACVP and CAVP vectors, C2SP Wycheproof vectors, RFC test vectors, known-answer tests for Ascon, BLAKE2, BLAKE3 and FrodoKEM, XMSS vectors from the RFC 8391 reference implementation, and cross-check files for every error case, option and format.
- Every push
- The vectors in the repository, with every file checked against its SHA-256; the constant-time check under valgrind; Miri and AddressSanitizer.
- Weekly
- The full ACVP sets, the large-data tests, and long robustness runs that feed random and mutated key encodings, signatures, ciphertexts, state records and tree caches to every parser.
- Platforms
- Linux on x86-64 and ARM64, macOS on ARM64, Windows on x86-64 and ARM64, Linux musl, 32-bit x86, and RISC-V 64 and big-endian s390x under emulation; Rust 1.88 and stable.
Versions and releases
Releases follow Semantic Versioning, and the MAJOR.MINOR of each release is the version of the specification that it implements.
- Compatibility
- Within 1.x, no public item is removed or renamed and no specified behaviour changes. A minor release can add algorithms, options and error codes, and every 1.x release reads every byte format that an earlier 1.x release wrote.
- Support
- Only the latest release is supported, and security fixes ship as a new patch release.
- Releases
- Each starts from a signed tag on main. The crate is built on x86-64 and ARM64 and the two builds must match byte for byte; it gets a build provenance attestation, is published through crates.io trusted publishing after a maintainer’s approval, and is downloaded again and compared with the attested hash.
License and contact
Open source under Apache-2.0 OR MIT, at your option.
- Security
- GitHub Security Advisories, privately
- Contact
- hello@crypterial.com