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_MU if your message is larger than 4096 bytes.
  • The same μ generation logic applies for both signing and verification.