ML-DSA¶
ML-DSA (Module-Lattice-Based Digital Signature Algorithm) is the post-quantum signature algorithm standardized by NIST in FIPS 204. It is derived from CRYSTALS-Dilithium.
Its security is based on the hardness of lattice problems (Module-LWE and Module-SIS), which are believed to be hard even for quantum computers. Compared to RSA and ECC, keys and signatures are much larger.
FIPS 204 defines three parameter sets:
Parameter set |
Constant |
NIST security category |
Public key |
Expanded private key |
Signature |
|---|---|---|---|---|---|
ML-DSA-44 |
|
2 |
1312 bytes |
2560 bytes |
2420 bytes |
ML-DSA-65 |
|
3 |
1952 bytes |
4032 bytes |
3309 bytes |
ML-DSA-87 |
|
5 |
2592 bytes |
4896 bytes |
4627 bytes |
Pass one of these constants as the param_set argument.
They are the members of the enum Crypto.PublicKey.MLDSA.ParamSet,
and the value of each member is the name of its parameter set (for instance, 'ML-DSA-65').
A private key can be represented in two ways:
the seed: the 32 random bytes the key is generated from (\(\xi\) in FIPS 204). It is the most compact form, and the one recommended by RFC 9881.
the expanded private key: the encoding defined in FIPS 204 (Algorithm 24). It cannot be converted back to the seed.
With this module you can generate a new ML-DSA key:
>>> from Crypto.PublicKey import MLDSA
>>>
>>> key = MLDSA.generate(param_set=MLDSA.ML_DSA_65)
>>> seed = key.seed # 32 bytes, keep it secret
>>> pk = key.pk # 1952 bytes
and rebuild it later from its seed (or from the expanded private key), or get the public key:
>>> key = MLDSA.construct(param_set=MLDSA.ML_DSA_65, seed=seed)
>>> pub_key = MLDSA.construct(param_set=MLDSA.ML_DSA_65, pk=pk)
>>> pub_key == key.public_key()
True
You can export an ML-DSA key as defined in RFC 9881:
a public key as an X.509 SubjectPublicKeyInfo, a private key as a
PKCS#8 OneAsymmetricKey, optionally protected with a passphrase.
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 = MLDSA.import_key(f.read(), "secret")
The private key in the OneAsymmetricKey is the seed
or, if the seed is not known, the expanded private key.
It does not contain the public key (the optional publicKey field of
OneAsymmetricKey version 2), which can always be computed again.
import_key() accepts all the variants of RFC 9881
(the seed, the expanded private key, or both, with or without the public key),
and it checks that the seed, the expanded private key and the public key match.
To sign messages and verify signatures with these keys,
use Crypto.Signature.mldsa.
The implementation is based on mldsa-native,
ML-DSA keys (FIPS 204)
- class Crypto.PublicKey.MLDSA.MLDSAKey(*, param_set, seed=None, sk=None, pk=None)¶
Class defining an ML-DSA key (private or public). Do not instantiate directly. Use
generate()orconstruct()instead.- Variables:
param_set (
ParamSet) – The parameter set.
- export_key(*, format='DER', passphrase=None, protection=None, prot_params=None, randfunc=None, threads=1)¶
Export this ML-DSA key, as defined in RFC 9881.
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 32-byte seed (the format recommended by RFC 9881), 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.MLDSA.ML_DSA_44 = ParamSet.ML_DSA_44¶
Same as
ParamSet.ML_DSA_44
- Crypto.PublicKey.MLDSA.ML_DSA_65 = ParamSet.ML_DSA_65¶
Same as
ParamSet.ML_DSA_65
- Crypto.PublicKey.MLDSA.ML_DSA_87 = ParamSet.ML_DSA_87¶
Same as
ParamSet.ML_DSA_87
- class Crypto.PublicKey.MLDSA.ParamSet(value)¶
The ML-DSA parameter sets (FIPS 204, Section 4). The value of each member is its name in FIPS 204.
- ML_DSA_44 = 'ML-DSA-44'¶
NIST security category 2
- ML_DSA_65 = 'ML-DSA-65'¶
NIST security category 3
- ML_DSA_87 = 'ML-DSA-87'¶
NIST security category 5
- Crypto.PublicKey.MLDSA.construct(*, param_set, seed=None, sk=None, pk=None)¶
Build an ML-DSA key (private or public) from its components.
- Parameters:
param_set (
ParamSet) – Mandatory. The parameter set, for instanceMLDSA.ML_DSA_65.seed (bytes) – The 32-byte seed of a private key (\(\xi\) in FIPS 204).
sk (bytes) – The expanded private key, encoded as in FIPS 204 (Algorithm 24). Its consistency is checked (but not whether it was generated correctly).
pk (bytes) – The public key, encoded as in FIPS 204 (Algorithm 22).
At least one of
seed,skandpkmust be present. If more than one is present, they must match, otherwiseValueErroris raised.- Returns:
a new ML-DSA key object
- Return type:
- Crypto.PublicKey.MLDSA.generate(*, param_set, randfunc=None)¶
Generate a new ML-DSA private key (FIPS 204, Algorithm 1).
- Parameters:
param_set (
ParamSet) – Mandatory. The parameter set, for instanceMLDSA.ML_DSA_65.randfunc (callable) – Optional. The RNG to read the 32-byte seed from. If
None,Crypto.Random.get_random_bytes()is used.
- Returns:
a new private key
- Return type:
- Crypto.PublicKey.MLDSA.import_key(encoded, passphrase=None, *, max_iteration_count=None, threads=1, max_memory=None)¶
Import an ML-DSA key (public or private), as defined in RFC 9881.
- Parameters:
encoded (bytes or string) –
The ML-DSA 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.If the private key contains more than one among the seed, the expanded private key and the public key (in the
publicKeyfield 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-DSA 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-DSA key, or its components do not match.