Post-Quanten-Kryptografie

Ein ausreichend großer Quantencomputer würde RSA, X25519/X448 und ECDSA/ECDH auf jeder Kurve der Seite Public-Key-Kryptografie brechen: Keine dieser mathematischen Grundlagen widersteht Shors Algorithmus. Er muss heute nicht existieren, um heute ein Problem zu sein, ein Angreifer kann verschlüsselten Datenverkehr jetzt aufzeichnen und ihn entschlüsseln, sobald eine solche Maschine existiert, eine Strategie, die üblicherweise „harvest now, decrypt later" genannt wird. Alles, was über Jahre, nicht Tage, vertraulich bleiben muss, sollte bereits zu einem Post-Quanten-Schlüsselaustausch wechseln.

 

NIST hat drei Verfahren standardisiert, alle hier implementiert, und alle ausschließlich auf SHAKE aufgebaut (siehe Hashing & Nachrichtenauthentifizierung), sodass ihre Sicherheit nur davon abhängt, dass der Hash solide ist: keine Gitter- oder Zahlentheorie-Annahme, die gebrochen werden könnte.

 

Verfahren Zweck Standard Unit
ML-KEM Schlüsselkapselung, ersetzt Diffie-Hellman FIPS 203 sgcCrypto_MLKEM
ML-DSA Signaturen, gitterbasiert, die Standardwahl FIPS 204 sgcCrypto_MLDSA
SLH-DSA Signaturen, ausschließlich hashbasiert, konservative Rückfalllösung FIPS 205 sgcCrypto_SLHDSA

 

ML-KEM und die hybride Konstruktion (empfohlen)

 

ML-KEM ersetzt einen Diffie-Hellman-Austausch: Der Sender kapselt ein gemeinsames Geheimnis an den öffentlichen Schlüssel des Empfängers, der Empfänger entkapselt es mit dem privaten Schlüssel, und beide erhalten dieselben 32 Byte. Es gibt drei Parametersätze, größer bedeutet eine größere Sicherheitsmarge und größere Schlüssel.

 


procedure sgcMLKEM_GenerateKeyPair(aParams: TsgcMLKEMParams; out aPublicKey, aPrivateKey: TBytes);
procedure sgcMLKEM_Encapsulate(aParams: TsgcMLKEMParams; const aPublicKey: TBytes; out aCiphertext, aSharedSecret: TBytes);
function sgcMLKEM_Decapsulate(aParams: TsgcMLKEMParams; const aPrivateKey, aCiphertext: TBytes): TBytes;

var
  oPub, oPriv, oCiphertext, oSecretA, oSecretB: TBytes;
begin
  sgcMLKEM_GenerateKeyPair(mlkem768, oPub, oPriv);
  sgcMLKEM_Encapsulate(mlkem768, oPub, oCiphertext, oSecretA);   { sender }
  oSecretB := sgcMLKEM_Decapsulate(mlkem768, oPriv, oCiphertext); { recipient }
  { oSecretA = oSecretB }
end;

 

Die Entkapselung gemäß FIPS 203 schlägt nie fehl.

Ein manipulierter oder bösartiger Chiffretext löst keinen Fehler aus, sondern gibt stillschweigend ein anderes Geheimnis zurück, abgeleitet aus einem impliziten Ablehnungswert, der in den privaten Schlüssel eingebacken ist. Stattdessen einen Fehler zurückzugeben, würde einem Angreifer ein Entschlüsselungsorakel liefern, um den privaten Schlüssel zu untersuchen; der Standard vermeidet das bewusst durch dieses Design. Was auch immer das abgeleitete Geheimnis schützt, muss die Abweichung selbst erkennen, zum Beispiel indem ein AEAD-Tag weiter unten in der Verarbeitung nicht verifiziert.

 

Was heute tatsächlich eingesetzt werden sollte, ist die hybride Konstruktion: X25519 kombiniert mit ML-KEM-768, sodass das gemeinsame Geheimnis von beiden abhängt. Sie bleibt sicher, falls ML-KEM jemals durch einen kryptoanalytischen Fortschritt gebrochen wird, und sie bleibt sicher, falls ein Quantencomputer X25519 bricht; ein Angreifer benötigt beides gleichzeitig, um zu versagen.

 


procedure sgcHybrid_GenerateKeyPair(out aPublicKey, aPrivateKey: TBytes);
procedure sgcHybrid_Encapsulate(const aPublicKey: TBytes; out aCiphertext, aSharedSecret: TBytes);
function sgcHybrid_Decapsulate(const aPrivateKey, aCiphertext: TBytes; out aSharedSecret: TBytes): Boolean;

 

X-Wing und die hybriden TLS-1.3-Gruppen

 

sgcCrypto_MLKEM_Hybrid enthält drei Hybrid-Familien. Die oben genannten sgcHybrid_-Funktionen sind das ursprüngliche sgcCrypto-Format und bleiben aus Kompatibilitätsgründen erhalten. X-Wing ist der standardisierte Allzweck-Hybrid-KEM, X25519 kombiniert mit ML-KEM-768, und er ist derjenige, den Sie außerhalb von TLS in neuem Code verwenden sollten. Die dritte Familie erzeugt die hybriden TLS-1.3-Key-Shares und sonst nichts.

 

X-Wing (draft-connolly-cfrg-xwing-kem-10) speichert nur ein einziges Geheimnis: Der private Schlüssel ist ein 32 Byte langer Seed, und jede Operation expandiert ihn zu dem ML-KEM-768- und X25519-Material, das sie benötigt. Der öffentliche Schlüssel ist 1216 Byte groß, der Chiffretext 1120 Byte und das gemeinsame Geheimnis 32 Byte. Die Kapselung führt die Prüfung des Kapselungsschlüssels gemäß FIPS 203 durch und löst einen Fehler aus, wenn der öffentliche Schlüssel sie nicht besteht.

 


procedure sgcXWing_GenerateKeyPair(out aPublicKey, aPrivateKey: TBytes);
procedure sgcXWing_Encapsulate(const aPublicKey: TBytes; out aCiphertext, aSharedSecret: TBytes);
function sgcXWing_Decapsulate(const aPrivateKey, aCiphertext: TBytes; out aSharedSecret: TBytes): Boolean;

var
  oPub, oPriv, oCiphertext, oSecretA, oSecretB: TBytes;
begin
  sgcXWing_GenerateKeyPair(oPub, oPriv);
  sgcXWing_Encapsulate(oPub, oCiphertext, oSecretA);      { sender }
  if sgcXWing_Decapsulate(oPriv, oCiphertext, oSecretB) then
    { oSecretA = oSecretB };
end;

 

Die hybriden TLS-1.3-Key-Shares aus RFC 10024 sind X25519MLKEM768, SecP256r1MLKEM768 und SecP384r1MLKEM1024, IANA-Codepoints 0x11EC, 0x11EB und 0x11ED. Das sind keine KEMs, die Sie eigenständig einsetzen können. Das gemeinsame Geheimnis ist eine einfache Verkettung der beiden Hälften, was nur deshalb sicher ist, weil TLS es zusammen mit dem Handshake-Transkript in seinen Schlüsselplan einspeist. Verwenden Sie sie also ausschließlich, um einen TLS-Key-Share aufzubauen. Die Engine Native TLS bietet standardmäßig X25519MLKEM768 zuerst an, sodass ein Client oder ein Server, der sie verwendet, bereits ohne zusätzlichen Code einen Post-Quanten-Schlüsselaustausch aushandelt.

 

Die hybriden Gruppen gibt es nur in TLS 1.3. Die native Engine spricht auch TLS 1.2, und eine Verbindung, die darauf zurückfällt, weil die Gegenstelle kein TLS 1.3 kann, verwendet eine klassische Gruppe, X25519, secp256r1 oder secp384r1, ohne Post-Quanten-Schutz. Setzen Sie TLSOptions.Version auf einem Client oder SSLOptions.Version auf einem Server auf tls1_3, und listen Sie in NativeTLS_Options.Groups nur hybride Gruppen auf, wenn der Schlüsselaustausch immer Post-Quanten sein muss.

 

Die Post-Quanten-TLS-Demo, Demos\18.Cryptography\08.PostQuantum_TLS, hat eine Auswahl der TLS-Version mit den Optionen TLS 1.3 und TLS 1.2, nur TLS 1.3 und nur TLS 1.2, sowie eine Cipher-Suite-Auswahl, und ihre Statuszeile zeigt die ausgehandelte Version, Gruppe und Cipher Suite. Die Server-Demo kann mit einem ML-DSA-44-Zertifikat signieren, das nur TLS-1.3-Clients bedient, weil TLS 1.2 kein ML-DSA-Signaturschema hat.

 


type
  TsgcTLSHybridGroup = (thgX25519MLKEM768, thgSecP256r1MLKEM768, thgSecP384r1MLKEM1024);

function sgcTLSHybrid_GroupID(aGroup: TsgcTLSHybridGroup): Word;
function sgcTLSHybrid_GroupName(aGroup: TsgcTLSHybridGroup): string;
function sgcTLSHybrid_Available(aGroup: TsgcTLSHybridGroup): Boolean;

procedure sgcTLSHybrid_ClientKeyShare(aGroup: TsgcTLSHybridGroup; out aKeyShare, aPrivateState: TBytes);
function sgcTLSHybrid_ServerKeyShare(aGroup: TsgcTLSHybridGroup; const aClientKeyShare: TBytes;
  out aKeyShare, aSharedSecret: TBytes): Boolean;
function sgcTLSHybrid_ClientSharedSecret(aGroup: TsgcTLSHybridGroup;
  const aPrivateState, aServerKeyShare: TBytes; out aSharedSecret: TBytes): Boolean;

 

Der Client hält aPrivateState geheim, übergibt ihn unverändert an sgcTLSHybrid_ClientSharedSecret und löscht ihn anschließend; sein Aufbau ist nicht Teil der API. Die beiden booleschen Funktionen geben bei jeder ungültigen Eingabe der Gegenstelle False zurück und lassen alle Ausgaben leer: ein Key-Share falscher Länge, ein ML-KEM-Kapselungsschlüssel, der die Prüfung gemäß FIPS 203 nicht besteht, ein Punkt, der nicht auf der Kurve liegt, oder ein X25519-Ergebnis, das ausschließlich aus Nullen besteht. RFC 10024 verlangt, dass die Verbindung in jedem dieser Fälle mit einem illegal_parameter-Alert abgebrochen wird.

 

Alles, was auf X25519 aufbaut, also die sgcHybrid_-Funktionen, X-Wing und die Gruppe X25519MLKEM768, wird in einem SGC_CRYPTO_FIPS-Build herauskompiliert, siehe FIPS-Modus. Die beiden SecP-Gruppen bleiben erhalten, weil P-256, P-384 und ML-KEM allesamt zugelassene Algorithmen sind. Rufen Sie sgcTLSHybrid_Available auf, bevor Sie eine Gruppe anbieten oder akzeptieren, statt anzunehmen, dass sie vorhanden ist.

 

ML-DSA (empfohlen für Signaturen)

 

Das Signieren zieht einen Maskierungsvektor und versucht, eine Signatur zu erstellen, wobei ein Neustart erfolgt, wenn der Versuch Informationen über den Schlüssel preisgeben würde; die erwartete Anzahl an Versuchen ist klein, aber nicht fest, sodass das Signieren eine variable Zeit benötigt. Das liegt am Verfahren selbst und ist kein Seitenkanal auf den Schlüssel. Signaturen sind groß: 2420 Byte für ML-DSA-44 gegenüber 64 bei Ed25519, und öffentliche Schlüssel 1312 Byte gegenüber 32, kalkulieren Sie diesen Unterschied also ein, bevor Sie es in einem größenempfindlichen Protokoll einsetzen.

 


procedure sgcMLDSA_GenerateKeyPair(aParams: TsgcMLDSAParams; out aPublicKey, aPrivateKey: TBytes);
function sgcMLDSA_Sign(aParams: TsgcMLDSAParams; const aPrivateKey, aMessage, aContext: TBytes): TBytes;
function sgcMLDSA_Verify(aParams: TsgcMLDSAParams; const aPublicKey, aMessage, aSignature, aContext: TBytes): Boolean;

var
  oPub, oPriv, oSignature, oEmpty: TBytes;
begin
  sgcMLDSA_GenerateKeyPair(mldsa44, oPub, oPriv);
  oSignature := sgcMLDSA_Sign(mldsa44, oPriv, oMessage, oEmpty);
  if sgcMLDSA_Verify(mldsa44, oPub, oMessage, oSignature, oEmpty) then
    { valid };
end;

 

SLH-DSA (konservative Rückfalllösung)

 

SLH-DSA beruht ausschließlich auf der Hash-Funktion: kein Gitterproblem, das jemals, zu irgendeinem Zeitpunkt in der Zukunft, gebrochen werden könnte. Diese Garantie ist teuer. Eine Signatur reicht von 7856 Byte (die kleinste, SLH-DSA-SHAKE-128s) bis 49856 Byte (die größte, SLH-DSA-SHAKE-256f), und das Signieren eines s-Parametersatzes (small) durchläuft Hunderttausende von Hash-Operationen und kann Dutzende Sekunden dauern; die f-Sätze (fast) signieren in etwa ein bis sieben Sekunden bei ungefähr doppelter Signaturgröße. Die Verifikation ist für jeden Parametersatz schnell, was sich für einen Selten-signieren-, oft-verifizieren-Anwendungsfall wie Firmware- oder Codesignierung besser eignet als für ein interaktives Protokoll.

 


procedure sgcSLHDSA_GenerateKeyPair(aParams: TsgcSLHDSAParams; out aPublicKey, aPrivateKey: TBytes);
function sgcSLHDSA_Sign(aParams: TsgcSLHDSAParams; const aPrivateKey, aMessage, aContext: TBytes;
  aDeterministic: Boolean = True): TBytes;
function sgcSLHDSA_Verify(aParams: TsgcSLHDSAParams; const aPublicKey, aMessage, aSignature, aContext: TBytes): Boolean;

 

Schlüsselformate: SubjectPublicKeyInfo, PKCS#8 und PEM

 

Die rohen Byte-Folgen, die die FIPS-Algorithmen entgegennehmen, sind kein Speicherformat, und keine andere Implementierung wird sie lesen. Um einen Schlüssel auf die Festplatte zu schreiben, ihn in ein Zertifikat zu legen oder ihn an eine andere Bibliothek zu übergeben, exportieren Sie ihn in den Standard-Containern: ein X.509 SubjectPublicKeyInfo für einen öffentlichen Schlüssel, ein PKCS#8 OneAsymmetricKey für einen privaten Schlüssel und eine PEM-Umhüllung (RFC 7468) um beide, beschriftet mit PUBLIC KEY und PRIVATE KEY. Die drei Verfahren stellen dieselben acht Funktionen bereit, jedes gemäß dem RFC, der seine Kodierung festlegt.

 

Verfahren Container-RFC DER-Funktionen PEM-Funktionen
ML-DSA RFC 9881 sgcMLDSA_ExportSubjectPublicKeyInfo, sgcMLDSA_ImportSubjectPublicKeyInfo, sgcMLDSA_ExportPrivateKeyInfo, sgcMLDSA_ImportPrivateKeyInfo sgcMLDSA_ExportPublicKeyPEM, sgcMLDSA_ImportPublicKeyPEM, sgcMLDSA_ExportPrivateKeyPEM, sgcMLDSA_ImportPrivateKeyPEM
ML-KEM RFC 9935 sgcMLKEM_ExportSubjectPublicKeyInfo, sgcMLKEM_ImportSubjectPublicKeyInfo, sgcMLKEM_ExportPrivateKeyInfo, sgcMLKEM_ImportPrivateKeyInfo sgcMLKEM_ExportPublicKeyPEM, sgcMLKEM_ImportPublicKeyPEM, sgcMLKEM_ExportPrivateKeyPEM, sgcMLKEM_ImportPrivateKeyPEM
SLH-DSA RFC 9909 sgcSLHDSA_ExportSubjectPublicKeyInfo, sgcSLHDSA_ImportSubjectPublicKeyInfo, sgcSLHDSA_ExportPrivateKeyInfo, sgcSLHDSA_ImportPrivateKeyInfo sgcSLHDSA_ExportPublicKeyPEM, sgcSLHDSA_ImportPublicKeyPEM, sgcSLHDSA_ExportPrivateKeyPEM, sgcSLHDSA_ImportPrivateKeyPEM

 

Der Algorithmusbezeichner trägt den NIST-Objektbezeichner ohne Parameter, sodass der Parametersatz mit dem Schlüssel mitreist und der Import ihn zurückgibt. sgcMLDSA_OID, sgcMLKEM_OID und sgcSLHDSA_OID liefern den Punkt-String, und die passenden ParamsFromOID-Funktionen bilden einen solchen wieder zurück.

 

Drei Formen des privaten Schlüssels bei ML-DSA und ML-KEM.

Die Exportfunktionen nehmen einen Parameter aFormat vom Typ TsgcPQCPrivateKeyFormat entgegen, deklariert in sgcCrypto_DER. pqkfSeed, der Standardwert, schreibt nur den Seed, 32 Byte bei ML-DSA und die 64 Byte d || z bei ML-KEM; das ist die Form, die beide RFCs empfehlen, und sie ist mit Abstand die kleinste. pqkfExpanded schreibt den expandierten privaten FIPS-Schlüssel. pqkfBoth schreibt beide und löst einen Fehler aus, wenn der expandierte Schlüssel nicht die Expansion des Seeds ist, was die RFCs als fehlerhaften Schlüssel bezeichnen.

 

Ein Seed lässt sich nachträglich nicht aus einem expandierten Schlüssel zurückgewinnen, fordern Sie ihn also bei der Erzeugung mit sgcMLDSA_GenerateKeyPairAndSeed oder sgcMLKEM_GenerateKeyPairAndSeed an, wenn Sie die empfohlene Form speichern wollen. SLH-DSA hat eine einzige Form des privaten Schlüssels, SK.seed || SK.prf || PK.seed || PK.root, sein Export nimmt daher keinen Format-Parameter entgegen.

 


function sgcMLDSA_ExportPrivateKeyPEM(aParams: TsgcMLDSAParams; const aSeed, aPrivateKey: TBytes;
  aFormat: TsgcPQCPrivateKeyFormat = pqkfSeed): string;
function sgcMLDSA_ImportPrivateKeyPEM(const aPEM: string; out aParams: TsgcMLDSAParams;
  out aSeed, aPrivateKey, aPublicKey: TBytes): Boolean;

var
  vParams: TsgcMLDSAParams;
  oSeed, oPub, oPriv: TBytes;
  vPublicPEM, vPrivatePEM: string;
begin
  sgcMLDSA_GenerateKeyPairAndSeed(mldsa65, oSeed, oPub, oPriv);

  vPublicPEM := sgcMLDSA_ExportPublicKeyPEM(mldsa65, oPub);
  vPrivatePEM := sgcMLDSA_ExportPrivateKeyPEM(mldsa65, oSeed, oPriv, pqkfSeed);

  { reading it back: the parameter set comes from the key itself }
  if sgcMLDSA_ImportPrivateKeyPEM(vPrivatePEM, vParams, oSeed, oPriv, oPub) then
    { vParams = mldsa65, and oPriv and oPub are the expansion of oSeed };
end;

 

Der Import validiert, er vertraut nicht.

Jede Importfunktion gibt False zurück, statt einen Fehler auszulösen, und sie gibt False zurück bei fehlerhaftem DER, bei nachfolgenden Daten, bei einem unbekannten Objektbezeichner, bei Parametern, die vorhanden sind, obwohl sie fehlen müssen, und bei einem Schlüssel, dessen Länge nicht zum Parametersatz passt. Über die Struktur hinaus muss ein öffentlicher ML-KEM-Schlüssel die Modulus-Prüfung gemäß FIPS 203 bestehen, ein expandierter privater ML-KEM-Schlüssel muss die Hash-Prüfung und den paarweisen Konsistenztest bestehen, und ein expandierter privater ML-DSA-Schlüssel wird zu seinem öffentlichen Schlüssel zurückgerechnet, sodass die gespeicherten t0 und tr beide damit übereinstimmen müssen. Ein eingebetteter öffentlicher Schlüssel, der nicht zum privaten Schlüssel passt, wird abgelehnt. Wenn ein privater Schlüssel einen Seed enthält, kommen alle drei Ausgaben gefüllt zurück, ein Aufrufer muss ihn also nie gesondert expandieren.

 

sgcSLHDSA_ImportPrivateKeyInfo und sgcSLHDSA_ImportPrivateKeyPEM nehmen einen zusätzlichen Parameter aVerify entgegen, standardmäßig True, der PK.root aus dem privaten Seed neu berechnet und einen Schlüssel ablehnt, dessen gespeicherte Root davon abweicht. Diese Neuberechnung kostet so viel wie eine Schlüsselerzeugung, also Sekunden bei den s-Parametersätzen, übergeben Sie False daher nur bei Schlüsseln, die Ihre eigene Anwendung erzeugt hat oder denen sie bereits vertraut. Der Objektbezeichner, die Längen und die Struktur werden auch bei False geprüft.

 

Das sind die Container, die der Rest der Bibliothek liest. Ein privater PKCS#8-ML-DSA-Schlüssel ist das, womit der JWT-Client signiert, ein SubjectPublicKeyInfo ist das, womit der JWT-Server validiert, und beides ist das, was Zertifikate & PKI in ein Zertifikat oder einen Certificate Signing Request legt.

 

Einen Parametersatz wählen

 

Jeder Größenaufruf (sgcMLKEM_PublicKeySize, sgcMLDSA_SignatureSize, sgcSLHDSA_SignatureSize und ihre PrivateKeySize-Gegenstücke) nimmt dieselbe Parameter-Enumeration wie die Operation selbst entgegen, sodass ein Protokoll seine Puffer dimensionieren kann, bevor überhaupt Kryptografie stattfindet:

 

Parametersatz Öffentlicher Schlüssel Privater Schlüssel Chiffretext / Signatur
ML-KEM-5128001632768
ML-KEM-768118424001088
ML-KEM-1024156831681568
ML-DSA-44131225602420
ML-DSA-65195240323309
ML-DSA-87259248964627
SLH-DSA-SHAKE-128s32647856
SLH-DSA-SHAKE-128f326417088
SLH-DSA-SHAKE-192s489616224
SLH-DSA-SHAKE-192f489635664
SLH-DSA-SHAKE-256s6412829792
SLH-DSA-SHAKE-256f6412849856

 

Alle Größenangaben sind in Byte. Das gemeinsame ML-KEM-Geheimnis ist unabhängig vom Parametersatz immer 32 Byte lang (sgcMLKEM_SharedSecretSize).