How to sign and verify with AWS KMS ML‑DSA using an external μ (MU)
AWS KMS supports post‑quantum ML‑DSA. ML‑DSA doesn’t sign your message M directly; it signs a fixed‑size message representative called μ (mu) derived from the public key and the message.
Normally, KMS computes μ for you (MessageType="RAW"). But KMS also supports a mode where you compute μ externally and pass that 64‑byte value to KMS to sign/verify. In AWS APIs this is MessageType="EXTERNAL_MU". When you use it, KMS skips the internal “hash (public key + message) → μ” step and treats your provided message as the μ.
Why would you need external μ?
AWS KMS limits the message size for signing to 4096 bytes. If the data you need to sign is larger than that, you cannot use MessageType="RAW". With ML‑DSA, the solution is to compute the 64‑byte message representative μ yourself and pass it to KMS using MessageType="EXTERNAL_MU". This lets you sign arbitrarily large messages while still using KMS to perform the cryptographic signing operation.
What is μ exactly?
In ML‑DSA, μ (“mu”) is a fixed‑size 64‑byte message representative that is actually signed, rather than the original message. It is computed by hashing together a hash of the public key and an encoded form of the message (and optional context). This differs from the standard KMS DIGEST option, where you provide a hash of the message alone; with ML‑DSA, μ is algorithm‑defined and key‑bound because the public key is part of its construction.
When you use EXTERNAL_MU, you must supply this 64‑byte μ directly, and KMS signs it without recomputing it.
End‑to‑end steps
1. Create an ML‑DSA KMS key
Create an asymmetric KMS key with one of the ML‑DSA key specs — ML_DSA_44, ML_DSA_65, or ML_DSA_87.
2. Fetch the public key and extract the raw key bytes
Use GetPublicKey to get a DER‑encoded SubjectPublicKeyInfo (SPKI) blob representing the public key. The external μ computation needs the raw public key bytes, so parse SPKI and extract the public‑key bitstring.
3. Compute μ
You can use a library such as mldsa‑mu (or custom code) to generate μ from the public key and the message.
4. Ask KMS to sign μ
Call the AWS KMS Sign API with your key ID, set MessageType="EXTERNAL_MU", and supply μ as the message.
5. Verify via KMS
To verify, recompute the same μ from the message and use Verify with the same parameters and the signature returned from Sign.
Example (Python + boto3)
import boto3
from botocore.client import BaseClient
import mldsa_mu
import base64
import random
class MLDSA:
def __init__(self, client: BaseClient, key_id: str):
self.client = client
self.key_id = key_id
self.public_key = None
def _get_public_key(self) -> bytes:
if self.public_key is not None:
return self.public_key
resp = self.client.get_public_key(KeyId=self.key_id)
pk = resp.get("PublicKey")
self.public_key = mldsa_mu.public_key_from_pkix(pk)
return self.public_key
def sign_message(self, message: bytes) -> bytes:
mu = mldsa_mu.generate(self._get_public_key(), message)
resp = self.client.sign(
KeyId=self.key_id,
Message=mu,
MessageType="EXTERNAL_MU",
SigningAlgorithm="ML_DSA_SHAKE_256",
)
return resp.get("Signature")
def verify_message(self, message: bytes, signature: bytes) -> bool:
mu = mldsa_mu.generate(self._get_public_key(), message)
resp = self.client.verify(
KeyId=self.key_id,
Message=mu,
MessageType="EXTERNAL_MU",
SigningAlgorithm="ML_DSA_SHAKE_256",
Signature=signature
)
return resp.get("SignatureValid")
if __name__ == '__main__':
kms = boto3.client("kms", region_name="eu-west-2")
mldsa = MLDSA(kms, "alias/external-mu-test")
message = bytes(random.getrandbits(8) for _ in range(5000))
sig = mldsa.sign_message(message)
valid = mldsa.verify_message(message, sig)
print("Signature valid:", valid)
Notes
- You only need
EXTERNAL_MUif your message is larger than 4096 bytes. - The same μ generation logic applies for both signing and verification.