ML-KEM¶
ML-KEM (Module-Lattice-Based Key-Encapsulation Mechanism) is the post-quantum key encapsulation mechanism standardized by NIST in FIPS 203. It is derived from CRYSTALS-Kyber.
This module generates, imports and exports ML-KEM keys. It does not offer encapsulation or decapsulation on their own: to encrypt data with an ML-KEM key, use HPKE (see Usage).
Its security is based on the hardness of the Module Learning With Errors problem (Module-LWE), which is believed to be hard even for quantum computers. Compared to ECC, keys and ciphertexts are much larger.
FIPS 203 defines three parameter sets:
Parameter set |
Constant |
NIST security category |
Public key |
Expanded private key |
Ciphertext |
|---|---|---|---|---|---|
ML-KEM-512 |
|
1 |
800 bytes |
1632 bytes |
768 bytes |
ML-KEM-768 |
|
3 |
1184 bytes |
2400 bytes |
1088 bytes |
ML-KEM-1024 |
|
5 |
1568 bytes |
3168 bytes |
1568 bytes |
Pass one of these constants as the param_set argument.
They are the members of the enum Crypto.PublicKey.MLKEM.ParamSet,
and the value of each member is the name of its parameter set (for instance, 'ML-KEM-768').
If unsure, use ML-KEM-768, the most widely deployed.
Some policies require ML-KEM-1024 (for instance, NSA’s CNSA 2.0).
A private key can be represented in two ways:
the seed (
MLKEMKey.seed): the 64 random bytes the key is generated from. It is the most compact form, and the one recommended by RFC 9935.the expanded private key (
MLKEMKey.expanded_dk): the decapsulation key defined in FIPS 203. It cannot be converted back to the seed.
The public key is MLKEMKey.ek (the encapsulation key).
The names follow the specifications (see Names in the specifications).
The implementation is based on mlkem-native.
Generation¶
You can generate a new ML-KEM key:
>>> from Crypto.PublicKey import MLKEM
>>>
>>> key = MLKEM.generate(param_set=MLKEM.ML_KEM_768)
>>> seed = key.seed # 64 bytes, keep it secret
>>> ek = key.ek # 1184 bytes, the public key
and rebuild it later from its seed (or from the expanded private key), or get the public key:
>>> key = MLKEM.construct(param_set=MLKEM.ML_KEM_768, seed=seed)
>>> pub_key = MLKEM.construct(param_set=MLKEM.ML_KEM_768, ek=ek)
>>> pub_key == key.public_key()
True
construct() checks that the key is valid (see the checks in its description).
To store a private key, prefer MLKEMKey.export_key() with a passphrase
(see the next section): the seed on its own is not protected.
Import and Export¶
You can export an ML-KEM key as defined in RFC 9935:
a public key as an X.509 SubjectPublicKeyInfo, a private key as a
PKCS#8 OneAsymmetricKey, optionally protected with a passphrase.
These are the usual formats for public and private keys,
also used by OpenSSL (the keys of this module were tested with OpenSSL 4.0.3).
The encoding is DER (binary, the default) or PEM (text, RFC 7468):
with open("myprivatekey.pem", "wt") as f:
f.write(key.export_key(format="PEM", passphrase="secret"))
with open("mypublickey.der", "wb") as f:
f.write(key.public_key().export_key())
and import it later (the format is detected automatically):
with open("myprivatekey.pem", "rt") as f:
key = MLKEM.import_key(f.read(), "secret")
A private key is exported as its seed (or as the expanded private key, if the seed is not known).
import_key() also accepts the other forms of RFC 9935 and X.509 certificates,
and it checks that all the parts of the key match.
Usage¶
Currently, ML-KEM keys can only be used with HPKE
(Crypto.Protocol.HPKE), alone or combined with ECDH in a
hybrid KEM.
Names in the specifications¶
The specifications disagree on the meaning of the name dk, so this module never uses it on its own:
This module |
FIPS 203 |
||
|---|---|---|---|
|
\(d \| z\) |
|
|
|
|
|
|
|
|
|
public key |
API¶
ML-KEM keys (FIPS 203): the parameter sets, the key class, and the functions to generate, build and import keys.
- class Crypto.PublicKey.MLKEM.ParamSet(value)¶
The ML-KEM parameter sets (FIPS 203, Section 8). The value of each member is its name in FIPS 203.
- ML_KEM_512 = 'ML-KEM-512'¶
NIST security category 1
- ML_KEM_768 = 'ML-KEM-768'¶
NIST security category 3
- ML_KEM_1024 = 'ML-KEM-1024'¶
NIST security category 5
- Crypto.PublicKey.MLKEM.ML_KEM_512 = ParamSet.ML_KEM_512¶
Same as
ParamSet.ML_KEM_512
- Crypto.PublicKey.MLKEM.ML_KEM_768 = ParamSet.ML_KEM_768¶
Same as
ParamSet.ML_KEM_768
- Crypto.PublicKey.MLKEM.ML_KEM_1024 = ParamSet.ML_KEM_1024¶
Same as
ParamSet.ML_KEM_1024
- class Crypto.PublicKey.MLKEM.MLKEMKey(*, param_set, seed=None, expanded_dk=None, ek=None)¶
Class defining an ML-KEM key (private or public). Do not instantiate directly. Use
generate()orconstruct()instead.- Variables:
param_set (
ParamSet) – The parameter set.
- property seed: bytes¶
The 64-byte seed of the private key (\(d \| z\) in FIPS 203).
Not available if the private key was created from the expanded form only.
- export_key(*, format='DER', passphrase=None, protection=None, prot_params=None, randfunc=None, threads=1)¶
Export this ML-KEM key, as defined in RFC 9935.
A public key is encoded as an X.509
SubjectPublicKeyInfo.A private key is encoded as a PKCS#8
OneAsymmetricKey(in the clear, or encrypted ifpassphraseis set). It contains the 64-byte seed (the format recommended by RFC 9935), or the expanded private key if the seed of this key is not known (seeseed).- Parameters:
format (string) –
'DER'(default): binary encoding, asbytes.'PEM': textual encoding of RFC 7468, as a string, with the labelPUBLIC KEY,PRIVATE KEYorENCRYPTED PRIVATE KEY.
passphrase (bytes or string) – (Private keys only) The passphrase to protect the private key with PBES2 (PKCS#8
EncryptedPrivateKeyInfo).protection (string) – (Private keys only) The encryption scheme, when
passphraseis set. For all possible protection schemes, refer to the encryption parameters of PKCS#8. The default is'PBKDF2WithHMAC-SHA256AndAES256-CBC'.prot_params (dict) – (Private keys only) The parameters to derive the encryption key from the passphrase, when
passphraseis set. For all possible values, refer to the encryption parameters of PKCS#8. The defaults meet the OWASP recommendations (with the defaultprotection): there is no need to set them.randfunc (callable) – (Private keys only) The RNG for the salt and the IV of the encryption. If
None,Crypto.Random.get_random_bytes()is used.threads (integer) – (Private keys only) The maximum number of threads used to derive the encryption key with scrypt (default: 1, no extra threads; 0 for all CPU cores). Only useful with a scrypt
protectionand aparallelizationlarger than 1 inprot_params. The output does not depend on the number of threads.
Warning
If you don’t provide a passphrase, the private key will be exported in the clear!
- Crypto.PublicKey.MLKEM.generate(*, param_set, randfunc=None)¶
Generate a new ML-KEM private key (FIPS 203, Algorithm 19).
- Parameters:
param_set (
ParamSet) – Mandatory. The parameter set, for instanceMLKEM.ML_KEM_768.randfunc (callable) – Optional. The RNG to read the 64-byte seed from. If
None,Crypto.Random.get_random_bytes()is used.
- Returns:
a new private key
- Return type:
- Crypto.PublicKey.MLKEM.construct(*, param_set, seed=None, expanded_dk=None, ek=None)¶
Build an ML-KEM key (private or public) from its components.
- Parameters:
param_set (
ParamSet) – Mandatory. The parameter set, for instanceMLKEM.ML_KEM_768.seed (bytes) – The 64-byte seed of a private key: \(d \| z\) in FIPS 203 (the input of
ML-KEM.KeyGen_internal, Algorithm 16).expanded_dk (bytes) – The expanded decapsulation key (dk in FIPS 203). It must pass the hash check of FIPS 203 (Section 7.3), and a pairwise consistency check (an encapsulation and a decapsulation), unless the seed is also present.
ek (bytes) – The encapsulation key, that is, the public key (ek in FIPS 203). It must pass the modulus check of FIPS 203 (Section 7.2).
At least one of
seed,expanded_dkandekmust be present. If more than one is present, they must match, otherwiseValueErroris raised.- Returns:
a new ML-KEM key object
- Return type:
- Crypto.PublicKey.MLKEM.import_key(encoded, passphrase=None, *, max_iteration_count=None, threads=1, max_memory=None)¶
Import an ML-KEM key (public or private), as defined in RFC 9935.
- Parameters:
encoded (bytes or string) –
The ML-KEM key to import, in DER format (
bytes), or in PEM format (RFC 7468, as a string or asbytes). It can be:For a public key: an X.509
SubjectPublicKeyInfo, or an X.509 certificate (the signature of the certificate is not checked).For a private key: a PKCS#8
OneAsymmetricKey(version 1 or 2, in the clear or encrypted). The private key can contain the seed, the expanded private key or both.
The parameters of the
AlgorithmIdentifiermust be absent (orNULL, which is tolerated).In PEM format, the label must match the content:
PUBLIC KEY,CERTIFICATE(orX509 CERTIFICATE,X.509 CERTIFICATE),PRIVATE KEYorENCRYPTED PRIVATE KEY. The legacy PEM encryption (with theProc-Typeheader) is not supported.The same checks as in
construct()apply. If the private key contains more than one among the seed, the expanded private key and the public key (in thepublicKeyfield of aOneAsymmetricKeyversion 2), they must match.passphrase (bytes or string) – The passphrase to decrypt an encrypted private key (PKCS#8
EncryptedPrivateKeyInfo). It is ignored if the key is not encrypted.max_iteration_count (integer) – The maximum value allowed for the PBKDF2 iteration count or the scrypt cost N·r·p/8 (that is, N for the usual r=8 and p=1) of an encrypted private key. A malicious key could require an extremely high number of iterations, tying up the CPU for hours. The default value is None: a limit for each KDF, which keeps the key derivation to about one minute at most (see the limits for decryption). Set it to
0to disable the check.threads (integer) – The maximum number of threads used to derive the decryption key with scrypt (default: 1, no extra threads; 0 for all CPU cores). Only a private key protected with scrypt and a parallelization parameter larger than 1 can use them.
max_memory (integer) – The maximum memory (in bytes) for the key derivation of an encrypted private key, as a limit on the total of all threads together. The default value is None (a limit of 256 MiB). Set it to
0to disable the check.
- Returns:
a new ML-KEM key object
- Return type:
- Raises:
PassphraseRequiredError – (a
ValueError) when the private key is encrypted, but no passphrase was provided.IncorrectPassphraseError – (a
ValueError) when the private key cannot be decrypted with the passphrase (most likely, the passphrase is wrong).ValueError – when the key cannot be parsed, it is not an ML-KEM key, it fails the checks, or its components do not match.