Module-Lattice-Based Digital Signature Algorithm (ML-DSA)¶
ML-DSA is the post-quantum digital signature scheme standardized by NIST in
FIPS 204.
Its keys are in Crypto.PublicKey.MLDSA.
FIPS 204 defines two variants:
ML-DSA, where the message is signed directly (recommended).
HashML-DSA, where the message is first hashed, and only the resulting digest is signed. This should only be used by applications that cannot process the whole message at once when signing, because it requires a hash function with enough collision resistance.
This module supports both variants, with any of the three parameter sets (ML-DSA-44, ML-DSA-65 and ML-DSA-87).
A signature also depends on a context: up to 255 bytes that separate different protocols or applications using the same key (empty by default). The same context must be used to sign and to verify.
By default, the signature is hedged: it also depends on fresh random bytes,
which protects against fault and side-channel attacks.
With deterministic=True, it only depends on the key, the context and the message.
Both kinds of signatures are verified in the same way.
A sender can use a private key (loaded from a file) to sign a message:
from Crypto.PublicKey import MLDSA
from Crypto.Signature import mldsa
message = b'I give my permission to order #4355'
key = MLDSA.import_key(open("private_mldsa.pem").read())
signer = mldsa.new(key)
signature = signer.sign(message)
The receiver can use the matching public key to verify the authenticity of the received message:
from Crypto.PublicKey import MLDSA
from Crypto.Signature import mldsa
message = b'I give my permission to order #4355'
key = MLDSA.import_key(open("public_mldsa.pem").read())
verifier = mldsa.new(key)
try:
verifier.verify(message, signature)
print("The message is authentic")
except ValueError:
print("The message is not authentic")
For HashML-DSA, pass a hash object that has processed the message, instead of the message itself:
from Crypto.Hash import SHA512
from Crypto.PublicKey import MLDSA
from Crypto.Signature import mldsa
message = b'I give my permission to order #4355'
key = MLDSA.import_key(open("private_mldsa.pem").read())
signer = mldsa.new(key, b'my application')
signature = signer.sign(SHA512.new(message))
The hash function can be SHA-2 (Crypto.Hash.SHA224, Crypto.Hash.SHA256,
Crypto.Hash.SHA384, Crypto.Hash.SHA512, also truncated),
SHA-3 (Crypto.Hash.SHA3_224, Crypto.Hash.SHA3_256,
Crypto.Hash.SHA3_384, Crypto.Hash.SHA3_512) or SHAKE
(Crypto.Hash.SHAKE128 and Crypto.Hash.SHAKE256, with 256 and 512 bits of output).
To keep the security level of the key, FIPS 204 (Section 5.4) asks for a function with
at least 128 bits of collision resistance for ML-DSA-44 (like SHA-256),
192 bits for ML-DSA-65 (like SHA-384) and 256 bits for ML-DSA-87 (like SHA-512).
To process a large message in pieces with ML-DSA (not HashML-DSA),
use an MLDSAMuHash. It computes the message representative \(\mu\)
of FIPS 204 (external mu) for a given key and context, and it can be passed
to MLDSASigScheme.sign() and MLDSASigScheme.verify() instead of the message.
The signature is the same as for the whole message:
from Crypto.PublicKey import MLDSA
from Crypto.Signature import mldsa
key = MLDSA.import_key(open("private_mldsa.pem").read())
mu_hash = mldsa.MLDSAMuHash(key, context=b'my application')
for chunk in message_chunks:
mu_hash.update(chunk)
signature = mldsa.new(key).sign(mu_hash)
# The receiver can verify with the whole message too
mldsa.new(key.public_key(), b'my application').verify(message, signature)
The context belongs to the MLDSAMuHash: the signature object must
then be created without one.
An MLDSAMuHash only needs the public key: \(\mu\) can also be computed
apart from the private key (see MLDSAMuHash.digest()).
ML-DSA and HashML-DSA signatures (FIPS 204)
- class Crypto.Signature.mldsa.MLDSAMuHash(key, *, data=None, context=None)¶
The message representative \(\mu\) of a message, for a given public key and context (FIPS 204, Algorithm 7), computed incrementally.
Pass it to
MLDSASigScheme.sign()orMLDSASigScheme.verify()instead of the message: the signature is the same (ML-DSA, not HashML-DSA), but the message can be processed in pieces, or apart from the private key (only the public key is needed).- Parameters:
key (
Crypto.PublicKey.MLDSA.MLDSAKey) – The key that signs or verifies (only its public part is used).- Keyword Arguments:
- copy()¶
Return a copy of this object, to compute \(\mu\) for messages that start with the same data.
- Return type:
- digest()¶
Return the 64-byte message representative \(\mu\) of the message processed so far.
The object can still be updated afterwards.
- Return type:
- digest_size = 64¶
The size of \(\\mu\) in bytes
- class Crypto.Signature.mldsa.MLDSASigScheme(key, context, deterministic, randfunc)¶
An ML-DSA signature object. Do not instantiate directly. Use
Crypto.Signature.mldsa.new().- can_sign()¶
Return
Trueif this signature object can be used for signing messages.- Return type:
- sign(msg_or_hash)¶
Compute the ML-DSA signature of a message.
- Parameters:
msg_or_hash (bytes, an
MLDSAMuHashor a hash object) –One of:
the message to sign (
bytes, for ML-DSA);an
MLDSAMuHashthat has processed the message (also for ML-DSA, with the same signature). The context is the one of theMLDSAMuHash, so this signature object must be created without one (not even an empty one);a hash object that has processed the message (for HashML-DSA), of
Crypto.Hash.SHA224,Crypto.Hash.SHA256,Crypto.Hash.SHA384,Crypto.Hash.SHA512(also truncated),Crypto.Hash.SHA3_224,Crypto.Hash.SHA3_256,Crypto.Hash.SHA3_384,Crypto.Hash.SHA3_512,Crypto.Hash.SHAKE128orCrypto.Hash.SHAKE256. The output of a SHAKE object must not have been read yet.
- Returns:
2420 bytes for ML-DSA-44, 3309 bytes for ML-DSA-65, and 4627 bytes for ML-DSA-87.
- Return type:
The signature as
bytes- Raises:
TypeError – if the key is not a private key.
- verify(msg_or_hash, signature)¶
Check if an ML-DSA signature is authentic.
- Parameters:
msg_or_hash (bytes, an
MLDSAMuHashor a hash object) – The message that was signed, as forsign(). A signature of a message (bytes) can also be verified with anMLDSAMuHashof that message, and vice versa.signature (
bytes) – The signature to verify.
- Raises:
ValueError – if the signature is not authentic.
- Return type:
- Crypto.Signature.mldsa.new(key, context=None, *, deterministic=False, randfunc=None)¶
Create a signature object
MLDSASigSchemethat can perform or verify an ML-DSA signature (FIPS 204).- Parameters:
key (
Crypto.PublicKey.MLDSA.MLDSAKey) – The key to use for computing the signature (private keys only) or for verifying one.context (bytes) – Up to 255 bytes of context, a constant byte string to separate different protocols or different applications of the same key. If
None(default),MLDSASigScheme.sign()andMLDSASigScheme.verify()use an empty context. It must beNoneto sign or verify with anMLDSAMuHash(which has its own context).
- Keyword Arguments:
deterministic (boolean) – If
False(default), the signature is hedged: it depends on fresh random bytes too (the recommended variant). IfTrue, the signature is deterministic: it only depends on the key, the context and the message. Verification is the same in both cases.randfunc (callable) – The RNG for the hedged signatures: a function that returns the requested number of random bytes. If
None,Crypto.Random.get_random_bytes()is used. It cannot be combined withdeterministic=True.
- Return type: