-
Notifications
You must be signed in to change notification settings - Fork 3
ML KEM
Module-Lattice-Based Key-Encapsulation Mechanism — formerly CRYSTALS-Kyber,
standardized by NIST as FIPS 203.
ML-KEM lets two parties establish a shared 32-byte secret over an insecure
channel. pqcrypto supports all three parameter sets and is byte-exact against
the NIST KAT corpus, with verified OpenSSL interoperability.
import 'package:pqcrypto/pqcrypto.dart';
final kem = PqcKem.kyber768; // or .kyber512 / .kyber1024
// Receiver: generate and publish the public key.
final (publicKey, secretKey) = kem.generateKeyPair();
// Sender: encapsulate to the public key.
final (ciphertext, sharedSecret) = kem.encapsulate(publicKey);
// Receiver: decapsulate to recover the same secret.
final recovered = kem.decapsulate(secretKey, ciphertext);sequenceDiagram
participant R as Receiver
participant S as Sender
R->>R: (pk, sk) = generateKeyPair()
R->>S: publish pk
S->>S: (ct, ss) = encapsulate(pk)
S->>R: send ct
R->>R: ss' = decapsulate(sk, ct)
Note over R,S: ss == ss' (32 bytes)
ML-KEM operates over the polynomial ring Z_q[X]/(X^256 + 1) with q = 3329,
using the Number Theoretic Transform (NTT) for fast multiplication. Security
against chosen-ciphertext attacks (IND-CCA2) comes from the Fujisaki–Okamoto
transform: on decapsulation the implementation always computes both the
re-encryption secret and the implicit-rejection secret J(z‖c), then selects
between them with a branchless constant-time mask, so a tampered ciphertext does
not leak through control flow. Sensitive intermediates are zeroized best-effort.
| Parameter set | NIST category | Public key | Secret key | Ciphertext | Shared secret |
|---|---|---|---|---|---|
| ML-KEM-512 | 1 (~AES-128) | 800 | 1632 | 768 | 32 |
| ML-KEM-768 | 3 (~AES-192) | 1184 | 2400 | 1088 | 32 |
| ML-KEM-1024 | 5 (~AES-256) | 1568 | 3168 | 1568 | 32 |
Sizes are in bytes. The secret key is the expanded FIPS 203 form
ByteEncode12(s) ‖ ek ‖ H(ek) ‖ z. Default to ML-KEM-768; use 1024 for
long-lived confidentiality. Validate exact lengths before any crypto.
generateKeyPair accepts an optional seed: 32 bytes (d) or 64 bytes
(d‖z). With the same 64-byte seed, OpenSSL derives the byte-identical public
key — the basis of the interop suite.
final seed64 = mySecureSeed(); // 64 bytes (d || z); protect like a secret key
final (pk, sk) = kem.generateKeyPair(seed64);Use this for reproducible backup/restore and cross-implementation validation. See Validation & Interoperability.
encapsulate gives you a 32-byte shared secret, not ciphertext for your
data. To encrypt application data you must:
- derive a key from the shared secret with a KDF (HKDF), and
- encrypt with an AEAD (AES-GCM / ChaCha20-Poly1305).
Neither HKDF nor AEAD is part of pqcrypto. The full, correct pattern is the
"Encrypt to a public key (KEM-DEM)" recipe in the Cookbook.
- ML-KEM alone is not authenticated transport. It establishes a secret with whoever holds the public key — authenticate the public key (pinning, certificates, signed bundle) or you may be talking to an attacker.
- Pair with a classical exchange (X25519) via a KDF for a conservative hybrid — see Serverpod & Flutter.
- This is KAT + interop evidence, not a CMVP/FIPS 140 validation; zeroization and side-channel resistance are best-effort in Dart. See Security Posture.
pqcrypto — pure Dart, zero-dependency post-quantum cryptography (ML-KEM FIPS 203 · ML-DSA FIPS 204) for Dart, Flutter, and the web · MIT License · pub.dev · Repository · Documentation Index
Algorithm/KAT-conformance and interoperability evidence — not a CMVP/FIPS 140 module validation.
pqcrypto Wiki
Getting started
Algorithms
Design & internals
Assurance
Integration
Project
Links