Zum Inhalt springen
ecdsa.com
← Alle Artikel

ML-DSA für Entwickler: FIPS 204, Schlüssel, Signatur, Code

Die Post-Quanten-Signatur, die du tatsächlich einsetzen wirst. Was die drei Parametersätze an Bytes kosten, welche Optionen die Signatur verändern, warum sich ein Schlüssel aus einer Bibliothek nicht in eine andere laden lässt – und getesteter Code für sechs Stacks.

Kurz gesagt. ML-DSA ist das gitterbasierte Signaturverfahren, das in FIPS 204 (August 2024) standardisiert wurde, früher CRYSTALS-Dilithium. Es hat drei Parametersätze: ML-DSA-44, ML-DSA-65 und ML-DSA-87. Die Signaturen sind 2.420, 3.309 und 4.627 Byte groß, gegenüber 64 bei Ed25519 oder ECDSA P-256. Signiert wird standardmäßig hedged (randomisiert); ein deterministischer Modus ändert nur, was der Signierer erzeugt. Ein optionaler Kontextstring von bis zu 255 Byte und eine separate Pre-Hash-Variante (HashML-DSA) ändern, was ein Verifizierer tun muss, deshalb müssen sich beide Seiten darauf einigen. OpenSSL 3.5, Go 1.27, Java 24+, .NET 10, BouncyCastle und @noble/post-quantum implementieren es alle. Am wenigsten einig sind sie sich bei privaten Schlüsseln. Speichere den 32-Byte-Seed.

Warum ML-DSA und warum gerade jetzt

ECDSA-, EdDSA- und RSA-Signaturen kann jeder fälschen, der einen ausreichend großen Quantencomputer hat. Anders als verschlüsselter Datenverkehr lässt sich eine Signatur nicht heute abgreifen und später so brechen, dass es rückwirkend Schaden anrichtet. Eine gefälschte Signatur zählt in dem Moment, in dem sich jemand auf sie verlässt. Deshalb war die Migration beim Schlüsselaustausch dringend, und deshalb haben Signaturen mehr Zeit. Aber „mehr Zeit“ hat Grenzen:

  • Langlebige Roots of Trust wie Firmware-Signaturschlüssel, Root-CAs, Codesignatur- und Dokumentensignaturschlüssel müssen noch zehn oder zwanzig Jahre nach ihrer Erzeugung vertrauenswürdig sein.
  • Der Entwurf des NIST-Übergangsplans, IR 8547, würde klassische Signaturen mit 112-Bit-Sicherheit nach 2030 als veraltet einstufen und alle quantenanfälligen Signaturen nach 2035 verbieten. Die Fristen und wie du sie einplanst, beschreibt Post-Quanten-Migration 2026.

ML-DSA ist die Antwort des NIST für den allgemeinen Einsatz. Die verwandten Verfahren besetzen Nischen. SLH-DSA nach FIPS 205 ist ein hashbasiertes Verfahren mit konservativen Annahmen, kleinen Schlüsseln und sehr großen oder langsamen Signaturen. Die zustandsbehafteten hashbasierten Verfahren LMS und XMSS (SP 800-208) werden für Firmware eingesetzt. FN-DSA auf Basis von Falcon ist als FIPS 206 geplant. Bis Oktober 2026 hatte das NIST auf csrc.nist.gov noch keinen Entwurf dazu veröffentlicht.

Die drei Parametersätze

ML-DSA-44ML-DSA-65ML-DSA-87
NIST-Sicherheitskategorie235
Öffentlicher Schlüssel1.312 B1.952 B2.592 B
Signatur2.420 B3.309 B4.627 B
Expandierter privater Schlüssel2.560 B4.032 B4.896 B
Seed des privaten Schlüssels32 B32 B32 B
Öffentlicher Schlüssel als SPKI (DER)1.334 B1.974 B2.614 B
OID (RFC 9881)2.16.840.1.101.3.4.3.17…3.18…3.19

Die Rohgrößen stammen aus Tabelle 2 von FIPS 204. Die SPKI-Größen hat OpenSSL 3.5.7 für frisch erzeugte Schlüssel ausgegeben. Zum Vergleich: Ein öffentlicher Ed25519-Schlüssel hat 32 Byte, seine Signatur 64. Ein öffentlicher RSA-2048-Schlüssel hat in SPKI 294 Byte, seine Signatur 256.

Welchen Parametersatz du wählen solltest:

  • ML-DSA-65 ist die übliche Standardwahl. JEP 497 macht ML-DSA-65 zur Voreinstellung in Java, wenn kein Parametersatz angegeben wird, und die meisten Empfehlungen sehen Kategorie 3 als komfortablen Mittelweg.
  • ML-DSA-87 ist das, was die CNSA 2.0 der NSA für nationale Sicherheitssysteme der USA vorschreibt („Level-V-Parameter“ für alle Geheimhaltungsstufen).
  • ML-DSA-44 ist der kleinste Parametersatz. Er passt dorthin, wo jedes Byte zählt und die Daten kurzlebig sind. Die Go-Dokumentation sagt sogar: „Die meisten Anwendungen sollten MLDSA44 verwenden.“

In der Praxis zählen die Bytes mehr als die Geschwindigkeit. Auf der arm64-Linux-VM, die für diesen Artikel verwendet wurde, ergab ein 2-Sekunden-Lauf von openssl speed auf einem einzelnen Kern (OpenSSL 3.5.7) diese Werte:

AlgorithmusSignaturen/sPrüfungen/s
ML-DSA-651.4888.019
RSA-20481.82574.119
ECDSA P-25665.46524.446

ML-DSA-65 signiert etwa so schnell wie RSA-2048 und prüft eine Größenordnung langsamer als RSA, was für die meisten Dienste schnell genug ist. Die Größe ist die größere Veränderung. Eine Zertifikatskette, ein JWT oder ein signierter Logeintrag wird um Kilobytes größer. Ein ML-DSA-65-Leaf-Zertifikat, das von einer ML-DSA-65-CA signiert wurde, kam in unserem Test auf 5.514 Byte in DER.

Hedged, deterministisch, Kontext: was eine Signatur verändert

Drei Optionen in FIPS 204 entscheiden, welche Bytes aus Sign herauskommen und was Verify wissen muss.

Hedged oder deterministisch. Innerhalb von ML-DSA leitet das Signieren aus dem privaten Schlüssel, der Nachricht und einem 32-Byte-Wert rnd ein Geheimnis pro Signatur ab. Standardmäßig („hedged“) ist rnd ein frischer Zufallswert. In der deterministischen Variante sind es 32 Nullbytes. FIPS 204 §3.4 erläutert die Abwägung. Der vorab berechnete Anteil schützt vor einem defekten Zufallszahlengenerator, und der frische Anteil erschwert Seitenkanal- und Fehlerinjektionsangriffe. Zusätzlich heißt es dort, die deterministische Variante „sollte nicht auf Plattformen verwendet werden, auf denen Seitenkanalangriffe ein Problem darstellen“. Die Prüfung ist für beide identisch, die Wahl betrifft also nur den Signierer. OpenSSL zeigt den Unterschied direkt:

OpenSSL 3.5.7: ML-DSA-65 hedged vs. deterministisch
$ openssl pkeyutl -sign -rawin -inkey k65.pem -in msg.txt | sha256sum
f5c30f1347b231f64f7adf48a4dfeb77d1c259cfc568420ad1f2238335e28616  -
$ openssl pkeyutl -sign -rawin -inkey k65.pem -in msg.txt | sha256sum
c0eada83bdaa8e278defdd974cbd849fac583f7519a075ee53ec2a3d93adc6ba  -
$ openssl pkeyutl -sign -rawin -inkey k65.pem -in msg.txt -pkeyopt deterministic:1 | sha256sum
c179822bec24d34a38c16257b41624cfb3a9ed40b7d7c79a5b5f2bdf0b87d4bf  -
$ openssl pkeyutl -sign -rawin -inkey k65.pem -in msg.txt -pkeyopt deterministic:1 | sha256sum
c179822bec24d34a38c16257b41624cfb3a9ed40b7d7c79a5b5f2bdf0b87d4bf  -

Schreibe keine Tests, die eine frische Signatur mit einer gespeicherten vergleichen, außer du signierst deterministisch. Hedged-Signaturen unterscheiden sich bei jedem Aufruf, und das ist so gewollt.

Kontextstring. Sowohl Sign als auch Verify nehmen einen Kontextstring ctx von höchstens 255 Byte entgegen, standardmäßig ist er leer. ML-DSA signiert 0x00 ‖ len(ctx) ‖ ctx ‖ M, nicht M allein, deshalb besteht eine Signatur, die unter einem Kontext erstellt wurde, die Prüfung unter einem anderen nicht. Der Kontext ist ein günstiges Werkzeug zur Domain-Separation. Zwei Protokolle, die sich einen Schlüssel teilen, lassen sich nicht dazu bringen, die Signaturen des jeweils anderen zu akzeptieren, wenn jedes seinen eigenen Kontext verwendet. Der Kontext lässt die Prüfung aber auch auf verwirrende Weise scheitern, wenn eine Seite ihn setzt und die andere nicht:

OpenSSL 3.5.7: mit Kontext signiert, ohne Kontext geprüft
$ openssl pkeyutl -sign -rawin -inkey k65.pem -in msg.txt -pkeyopt context-string:invoice-v1 -out sigctx.bin
$ openssl pkeyutl -verify -rawin -pubin -inkey p65.pem -in msg.txt -sigfile sigctx.bin -pkeyopt context-string:invoice-v1
Signature Verified Successfully
$ openssl pkeyutl -verify -rawin -pubin -inkey p65.pem -in msg.txt -sigfile sigctx.bin
...:error:030000EA:digital envelope routines:EVP_DigestVerify:provider signature failure:../crypto/evp/m_sigver.c:781:ML-DSA-65 digest_verify:
Signature Verification Failure

Protokolle legen den Kontext fest, du wählst ihn also selten selbst. ML-DSA in X.509 (RFC 9881) sowie in JOSE und COSE (RFC 9964, Mai 2026) verwenden beide den leeren Kontext. RFC 9964 schreibt: „Der Parameter ctx MUSS der leere String sein“.

HashML-DSA. FIPS 204 §5.4 definiert außerdem eine Pre-Hash-Variante. Die Nachricht wird zuerst mit SHA-512, SHA-256, SHAKE128 oder einer anderen zugelassenen Funktion gehasht, und die Signatur deckt den Digest zusammen mit der OID der Hashfunktion und einem anderen Byte zur Domain-Separation ab (1 statt 0). Es ist ein eigenes Signaturverfahren mit eigenen OIDs. Eine HashML-DSA-Signatur ist keine gültige ML-DSA-Signatur über dieselbe Nachricht. FIPS 204 sagt, „die ‚reine‘ ML-DSA-Version wird bevorzugt“, und RFC 9881 erlaubt HashML-DSA in Zertifikaten nicht. Auch RFC 9964 definiert es weder für JOSE noch für COSE. Verwende es nur, wenn ein Protokoll es verlangt.

Für sehr große Nachrichten gibt es einen dritten Weg, externes μ. Der Signierer berechnet den 64-Byte-Nachrichtenrepräsentanten μ außerhalb des Signaturmoduls und übergibt nur diesen. RFC 9881 beschreibt das Verfahren. Go 1.27 (crypto.MLDSAMu) und .NET 10 (SignMu) stellen es bereit. Dabei entstehen gewöhnliche reine ML-DSA-Signaturen, Verifizierer brauchen also nichts Besonderes.

Formate privater Schlüssel: Speichere den Seed

Ein ML-DSA-Schlüsselpaar wird aus einem 32-Byte-Seed ξ erzeugt. Der „expandierte“ private Schlüssel mit 2.560–4.896 Byte wird aus diesem Seed berechnet, ebenso der öffentliche Schlüssel. RFC 9881 definiert drei PKCS#8-Kodierungen:

  • seed: nur die 32 Byte, [0] IMPLICIT OCTET STRING. Das ist die empfohlene Form. Als PKCS#8 umfasst sie 54 Byte.
  • expandedKey: nur der expandierte Schlüssel.
  • both: eine Sequenz aus Seed und expandiertem Schlüssel.

RFC 9881 empfiehlt, dass Implementierungen „den Seed aufbewahren und exportieren“. Die Expansion lässt sich nicht umkehren, ein ohne seinen Seed gespeicherter Schlüssel lässt sich also nie zurück in die kleine Form umwandeln. Die Bibliotheken haben unterschiedliche Voreinstellungen gewählt, und daran scheitern Importe. OpenSSL 3.5 schreibt standardmäßig both:

openssl asn1parse: ML-DSA-65-Schlüssel im Standardformat von OpenSSL, 4.098 Byte (Hex gekürzt)
    0:d=0  hl=4 l=4094 cons: SEQUENCE
    4:d=1  hl=2 l=   1 prim: INTEGER           :00
    7:d=1  hl=2 l=  11 cons: SEQUENCE
    9:d=2  hl=2 l=   9 prim: OBJECT            :ML-DSA-65
   20:d=1  hl=4 l=4074 prim: OCTET STRING      [HEX DUMP]:30820FE60420A58A79B197C3...

Go 1.27 lehnt diese Form ab und sagt dir, was zu tun ist. Akzeptiert wird nur der Seed:

Go 1.27.3: x509.ParsePKCS8PrivateKey mit Ausgaben von OpenSSL
both.pem (4098 bytes): x509: ML-DSA private keys with both seed and expanded key are not supported, use e.g. "openssl pkey -provparam ml-dsa.output_formats=seed-only" to convert to a seed-only key
seed-only.pem (54 bytes): *mldsa.PrivateKey
expanded-only.pem (4060 bytes): x509: semi-expanded ML-DSA private keys without seed are not supported

Java 25 (JEP 497), .NET 10 und Go haben in unseren Tests durchweg PKCS#8-Schlüssel mit 54 Byte exportiert, die nur den Seed enthalten. Wähle die Seed-Form schon an der Quelle:

OpenSSL 3.5: privaten Schlüssel nur mit Seed erzeugen oder umwandeln
openssl genpkey -algorithm ML-DSA-65 -provparam ml-dsa.output_formats=seed-only -out key.pem
openssl pkey -in both.pem -provparam ml-dsa.output_formats=seed-only -out key.pem

Dieselbe Wahl gilt für HSMs und KMS-Produkte. Bevor du einen langlebigen Schlüssel erzeugst, prüfe, ob du den Seed exportieren oder sichern kannst. Ein Schlüssel, der nur in expandierter Form vorliegt, bindet dich an Implementierungen, die expandierte Schlüssel akzeptieren. @noble/post-quantum ist ein Beispiel dafür: Sein secretKey ist die expandierte Kodierung (4.032 Byte bei ML-DSA-65), und sein keygen(seed) nimmt den 32-Byte-Seed entgegen. Bewahre den Seed selbst auf.

Willst du prüfen, was ein Schlüssel oder ein Zertifikat tatsächlich enthält, dann dekodiert der Post-Quanten-Zertifikatsinspektor Schlüssel und Zertifikate mit ML-DSA und SLH-DSA, und der ASN.1-Explorer zeigt, welche der drei Formen des privaten Schlüssels du vor dir hast.

Code, der heute funktioniert

Jedes der folgenden Snippets wurde am 20. Oktober 2026 in dem Container ausgeführt, der im Absatz davor genannt ist. Jedes signiert "hello" mit ML-DSA-65 und prüft das Ergebnis. Wo die Bibliothek es unterstützt, wird ein Kontextstring gesetzt. Wenn du das Verfahren ausprobieren willst, ohne etwas zu installieren, signiert und prüft das Post-Quanten-Signatur-Lab ML-DSA, SLH-DSA und Falcon im Browser.

OpenSSL 3.5 (node:24.22.0-trixie, OpenSSL 3.5.7). ML-DSA braucht -rawin, weil pkeyutl sonst einen vorab berechneten Digest erwarten würde:

OpenSSL 3.5: Schlüssel, Signatur, Prüfung
openssl genpkey -algorithm ML-DSA-65 -provparam ml-dsa.output_formats=seed-only -out key.pem
openssl pkey -in key.pem -pubout -out pub.pem
openssl pkeyutl -sign -rawin -inkey key.pem -in msg.txt -out sig.bin
openssl pkeyutl -verify -rawin -pubin -inkey pub.pem -in msg.txt -sigfile sig.bin
# Signature Verified Successfully

Go 1.27 (golang:1.27.3-trixie). Neu ist das Paket crypto/mldsa. crypto/x509 parst und serialisiert die Schlüssel, und crypto/tls akzeptiert ML-DSA-Zertifikate in TLS 1.3. Die Paketdokumentation warnt, dass das Paket unter dem Go Cryptographic Module v1.0.0 nach FIPS 140-3 nicht verfügbar ist.

Go 1.27: crypto/mldsa mit Kontext
sk, err := mldsa.GenerateKey(mldsa.MLDSA65())
if err != nil {
	log.Fatal(err)
}
opts := &mldsa.Options{Context: "invoice-v1"}
sig, err := sk.Sign(nil, []byte("hello"), opts) // 3309 bytes; the io.Reader is ignored
if err != nil {
	log.Fatal(err)
}
fmt.Println(mldsa.Verify(sk.PublicKey(), []byte("hello"), sig, opts)) // <nil>
fmt.Println(mldsa.Verify(sk.PublicKey(), []byte("hello"), sig, nil))  // mldsa: invalid signature

sk.SignDeterministic liefert die deterministische Variante. sk.Bytes() gibt den 32-Byte-Seed zurück.

Java, JDK 24 und neuer (eclipse-temurin:25.0.4.1_1-jdk-noble). JEP 497 hat ML-DSA zu den Standard-Providern hinzugefügt. Kontextstrings und HashML-DSA sind erklärte Nicht-Ziele dieses JEP, deshalb signiert das JDK nur mit dem leeren Kontext.

Java 25: ML-DSA mit dem integrierten Provider
KeyPair kp = KeyPairGenerator.getInstance("ML-DSA-65").generateKeyPair();
Signature signer = Signature.getInstance("ML-DSA");
signer.initSign(kp.getPrivate());
signer.update("hello".getBytes(StandardCharsets.UTF_8));
byte[] sig = signer.sign();                         // 3309 bytes
// kp.getPublic().getEncoded().length == 1974, kp.getPrivate().getEncoded().length == 54

BouncyCastle 1.86 (dasselbe JDK-Image). BouncyCastle unterstützt den Kontext über ContextParameterSpec und HashML-DSA unter einem eigenen Algorithmusnamen. Unser Test bestätigt, dass eine HashML-DSA-Signatur bei der Prüfung als reines ML-DSA durchfällt:

BouncyCastle 1.86: Kontextstring und HashML-DSA
Signature signer = Signature.getInstance("ML-DSA", "BC");
signer.initSign(kp.getPrivate());
signer.setParameter(new ContextParameterSpec("invoice-v1".getBytes(StandardCharsets.UTF_8)));
signer.update(msg);
byte[] sig = signer.sign();                         // verifies only with the same context

Signature pre = Signature.getInstance("HASH-ML-DSA", "BC");  // SHA-512 pre-hash
pre.initSign(kp.getPrivate());
pre.update(msg);
byte[] preSig = pre.sign();                         // "ML-DSA".verify(preSig) == false

.NET 10 (mcr.microsoft.com/dotnet/sdk:10.0.402-alpine3.24). System.Security.Cryptography.MLDsa nutzt die Kryptobibliothek der Plattform, also unter Linux OpenSSL 3.5 oder neuer und unter Windows CNG mit PQC-Unterstützung. Dasselbe Programm meldete MLDsa.IsSupported: False im SDK-Image für Ubuntu 24.04 (10.0.402-noble, OpenSSL 3.0.13) und True unter Alpine 3.24 (OpenSSL 3.5.9). Prüfe IsSupported und dein Basis-Image, bevor du auslieferst. Im SDK 10.0.402 kompilieren Schlüsselerzeugung, Signieren und Prüfung sauber, aber die Methoden für den Schlüsselexport lassen den Build weiterhin mit error SYSLIB5006: ... is for evaluation purposes only scheitern, sofern du die Diagnose nicht unterdrückst.

.NET 10: MLDsa mit Kontext
using MLDsa key = MLDsa.GenerateKey(MLDsaAlgorithm.MLDsa65);
byte[] data = Encoding.UTF8.GetBytes("hello");
byte[] context = Encoding.UTF8.GetBytes("invoice-v1");
byte[] signature = key.SignData(data, context);               // 3309 bytes
Console.WriteLine(key.VerifyData(data, signature, context));  // True
Console.WriteLine(key.VerifyData(data, signature));           // False

#pragma warning disable SYSLIB5006 // the key export methods are still [Experimental] in .NET 10
byte[] pkcs8 = key.ExportPkcs8PrivateKey();                   // 54 bytes: the seed form

JavaScript und TypeScript. Native Unterstützung ist im Kommen. Node.js 24.22 erzeugt Schlüssel mit crypto.generateKeyPairSync("ml-dsa-65") und signiert mit crypto.sign(null, data, privateKey) über sein mitgeliefertes OpenSSL 3.5, und die WebCrypto-API von Node.js akzeptiert { name: "ML-DSA-65" } mit einer ExperimentalWarning. Chromium 156 (der Build in Playwright 1.64.1) hat auf einer HTTPS-Seite über crypto.subtle einen Schlüssel und eine Signatur mit 3.309 Byte erzeugt. Prüfe die anderen Browser-Engines, bevor du dich darauf verlässt. Für Code, der überall laufen muss, ist @noble/post-quantum 0.7.0 eine reine JS-Implementierung, die in Node.js und in jedem Browser funktioniert (ausgeführt in node:24.22.0-trixie):

@noble/post-quantum 0.7.0
import { ml_dsa65 } from "@noble/post-quantum/ml-dsa.js";
import { randomBytes } from "node:crypto";

const { secretKey, publicKey } = ml_dsa65.keygen(randomBytes(32)); // keep the 32-byte seed, not secretKey
const msg = new TextEncoder().encode("hello");
const context = new TextEncoder().encode("invoice-v1");
const sig = ml_dsa65.sign(msg, secretKey, { context });
ml_dsa65.verify(sig, msg, publicKey, { context });       // true
ml_dsa65.verify(sig, msg, publicKey);                    // false
ml_dsa65.sign(msg, secretKey, { extraEntropy: false });  // deterministic variant

ML-DSA-Zertifikate und TLS

OpenSSL 3.5 kann schon heute ML-DSA-Zertifikate ausstellen und in TLS 1.3 verwenden. Dieser Test erzeugt eine Root-CA und ein Leaf-Zertifikat mit ML-DSA-65 und verbindet sich mit s_client. Ein Client, der nur klassische Signaturalgorithmen anbietet, wird abgewiesen:

OpenSSL 3.5.7: TLS 1.3 mit einem ML-DSA-65-Zertifikat
leaf certificate: 5514 bytes DER
Protocol version: TLSv1.3
Signature type: mldsa65
Verification: OK
Negotiated TLS1.3 group: X25519MLKEM768
--- a classic-only client
error:0A000410:SSL routines:ssl3_read_bytes:ssl/tls alert handshake failure

Die TLS-Codepoints (mldsa44 0x0904, mldsa65 0x0905, mldsa87 0x0906) stammen aus draft-ietf-tls-mldsa. Im Oktober 2026 lag der Entwurf in der Warteschlange des RFC-Editors. Go 1.27 unterstützt dieselben drei Verfahren.

Für das öffentliche Web ist das noch nicht nutzbar. Browser akzeptieren keine ML-DSA-Zertifikate von öffentlichen CAs. Firefox schaltet sie nur über die Einstellung security.tls.enable_mldsa frei, die standardmäßig auf false steht. Chrome hat im Februar 2026 erklärt, dass vorerst nicht geplant ist, X.509-Zertifikate mit Post-Quanten-Schlüsseln in den Chrome Root Store aufzunehmen. Stattdessen arbeitet das Chrome-Team an Merkle Tree Certificates, mit denen nicht mehr in jedem Handshake mehrere kilobytegroße Signaturen übertragen werden müssen. Realistisch sind ML-DSA-Zertifikate heute in privaten PKIs: Service Meshes, Geräteidentitäten, internes mTLS, Codesignatur und Dokumentensignatur, wo du beide Enden in der Hand hast.

Häufige Fehler

  • Eine stabile Signatur erwarten. Die Hedged-Variante ist in allen oben genannten Bibliotheken die Voreinstellung (wir haben OpenSSL, Java und noble geprüft, indem wir zweimal signiert haben), dieselbe Eingabe ergibt also jedes Mal andere Bytes. Vergleiche Prüfergebnisse, nicht Signaturbytes.
  • Reines ML-DSA und HashML-DSA verwechseln. Das sind verschiedene Algorithmen mit verschiedenen OIDs. Ein Verifizierer, der für den einen konfiguriert ist, lehnt den anderen ab, und FIPS 204 empfiehlt, ein Schlüsselpaar nur für einen der beiden zu verwenden.
  • Den Kontext nur auf einer Seite setzen. Der Fehlschlag sieht genauso aus wie bei einer ungültigen Signatur. Schreibe den Kontext, den dein Protokoll verwendet, neben dem Algorithmusnamen ins Log.
  • Den Seed wegwerfen. Nur den expandierten Schlüssel zu exportieren funktioniert so lange, bis du ihn in Go oder eine andere Implementierung laden musst, die nur Seeds akzeptiert. Konvertiere mit -provparam ml-dsa.output_formats=seed-only, solange du noch kannst.
  • Annehmen, dass die Laufzeitumgebung ML-DSA mitbringt. .NET und andere Wrapper um die Systembibliothek erben deren Version. Eine Anwendung, die auf einem Entwicklungsrechner mit OpenSSL 3.5 läuft, kann in einem Container scheitern, der auf einem älteren Basis-Image gebaut ist.
  • Größenlimits vergessen. JWT-Header, Cookies, DNS-Einträge, X.509-Erweiterungen, Datenbankspalten, QR-Codes, HSM-Puffer: Alles, was für eine 64-Byte-ECDSA-Signatur bemessen ist, muss 3.309 Byte oder mehr aufnehmen.

Quellen

Die Befehlsausgaben entstanden am 20. Oktober 2026 in node:24.22.0-trixie (OpenSSL 3.5.7, Node.js 24.22.0), golang:1.27.3-trixie, eclipse-temurin:25.0.4.1_1-jdk-noble (mit bcprov-jdk18on 1.86), mcr.microsoft.com/dotnet/sdk:10.0.402-alpine3.24, mcr.microsoft.com/dotnet/sdk:10.0.402-noble und mcr.microsoft.com/playwright:v1.64.1-noble (Chromium 156).