Post-quantumcryptografie

Een voldoende grote quantumcomputer zou RSA, X25519/X448 en ECDSA/ECDH breken op elke curve op de pagina Public key-cryptografie: geen van die wiskunde weerstaat het algoritme van Shor. Hij hoeft vandaag niet te bestaan om vandaag al een probleem te zijn, een tegenstander kan nu versleuteld verkeer opnemen en het ontsleutelen zodra zo'n machine bestaat, een strategie die meestal "harvest now, decrypt later" wordt genoemd. Alles wat jarenlang vertrouwelijk moet blijven, niet slechts dagen, zou nu al moeten overstappen op post-quantum sleuteluitwisseling.

NIST heeft drie schema's gestandaardiseerd, allemaal hier geïmplementeerd, en allemaal uitsluitend gebaseerd op SHAKE (zie Hashing & berichtauthenticatie), zodat hun beveiliging alleen berust op de degelijkheid van de hash: geen lattice- of getaltheorie-aanname om te doorbreken.

Schema Doel Standaard Unit
ML-KEM Sleutelinkapseling, vervangt Diffie-Hellman FIPS 203 sgcCrypto_MLKEM
ML-DSA Handtekeningen, roostergebaseerd, de standaardkeuze FIPS 204 sgcCrypto_MLDSA
SLH-DSA Handtekeningen, uitsluitend hashgebaseerd, conservatieve terugval FIPS 205 sgcCrypto_SLHDSA

ML-KEM en de hybride constructie (aanbevolen)

ML-KEM vervangt een Diffie-Hellman-uitwisseling: de verzender kapselt een gedeeld geheim in naar de publieke sleutel van de ontvanger, de ontvanger kapselt het uit met de privésleutel, en beiden komen uit op dezelfde 32 bytes. Het is er in drie parametersets, waarbij groter een grotere beveiligingsmarge en grotere sleutels betekent.


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;

FIPS 203-uitkapseling faalt nooit.

Een corrupte of kwaadaardige ciphertext genereert geen fout, ze geeft stilzwijgend een ander geheim terug, afgeleid van een impliciete afwijzingswaarde die in de privésleutel is ingebakken. Wel een fout teruggeven zou een aanvaller een ontsleutelingsorakel geven om de privésleutel mee af te tasten; de standaard vermijdt dat met opzet. Wat het afgeleide geheim ook beschermt, het moet de mismatch zelf detecteren, bijvoorbeeld doordat een AEAD-tag verderop niet blijkt te verifiëren.

Wat vandaag daadwerkelijk moet worden ingezet, is de hybride constructie: X25519 gecombineerd met ML-KEM-768, zodat het gedeelde geheim van beide afhangt. Ze blijft veilig als ML-KEM ooit wordt gebroken door een cryptanalytische doorbraak, en ze blijft veilig als een quantumcomputer X25519 breekt; een aanvaller heeft beide tegelijk nodig om te falen.


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 en de hybride TLS 1.3-groepen

sgcCrypto_MLKEM_Hybrid bevat drie hybride families. De hierboven genoemde sgcHybrid_-functies vormen het oorspronkelijke sgcCrypto-formaat en blijven behouden voor compatibiliteit. X-Wing is de gestandaardiseerde hybride KEM voor algemeen gebruik, X25519 gecombineerd met ML-KEM-768, en is degene die u in nieuwe code buiten TLS moet gebruiken. De derde familie bouwt de hybride TLS 1.3-sleuteldelen en verder niets.

X-Wing (draft-connolly-cfrg-xwing-kem-10) bewaart slechts één geheim: de privésleutel is een 32-byte seed, en elke bewerking breidt die uit tot het ML-KEM-768- en X25519-materiaal dat ze nodig heeft. De publieke sleutel is 1216 bytes, de ciphertext 1120 bytes en het gedeelde geheim 32 bytes. Inkapseling voert de FIPS 203-controle van de inkapselingssleutel uit en geeft een fout wanneer de publieke sleutel die niet doorstaat.


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;

De hybride TLS 1.3-sleuteldelen van RFC 10024 zijn X25519MLKEM768, SecP256r1MLKEM768 en SecP384r1MLKEM1024, IANA-codepunten 0x11EC, 0x11EB en 0x11ED. Dit zijn geen KEM's die u op zichzelf kunt gebruiken. Het gedeelde geheim is een gewone aaneenschakeling van de twee helften, wat alleen veilig is omdat TLS het samen met het handshake-transcript in zijn sleutelschema invoert, gebruik ze dus om een TLS-sleuteldeel te bouwen en voor niets anders. De engine Native TLS biedt standaard als eerste X25519MLKEM768 aan, zodat een client of server die deze gebruikt al een post-quantum sleuteluitwisseling onderhandelt zonder extra code.

De hybride groepen bestaan alleen in TLS 1.3. De native engine spreekt ook TLS 1.2, en een verbinding die hierop terugvalt omdat de peer geen TLS 1.3 kan, gebruikt een klassieke groep, X25519, secp256r1 of secp384r1, zonder post-quantum bescherming. Stel TLSOptions.Version op een client of SSLOptions.Version op een server in op tls1_3, en vermeld alleen hybride groepen in NativeTLS_Options.Groups, wanneer de sleuteluitwisseling altijd post-quantum moet zijn.

De Post-Quantum TLS-demo, Demos\18.Cryptography\08.PostQuantum_TLS, heeft een TLS-versiekiezer met de keuzes TLS 1.3 en TLS 1.2, alleen TLS 1.3 en alleen TLS 1.2, en een cipher suite-kiezer, en de statusregel toont de onderhandelde versie, groep en cipher suite. De serverdemo kan ondertekenen met een ML-DSA-44-certificaat, dat alleen TLS 1.3-clients bedient, omdat TLS 1.2 geen ML-DSA-handtekeningschema heeft.


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;

De client houdt aPrivateState geheim, geeft die ongewijzigd door aan sgcTLSHybrid_ClientSharedSecret en wist die daarna; de indeling ervan maakt geen deel uit van de API. De twee Boolean-functies geven False terug, met elke uitvoer leeg, bij elke ongeldige invoer van de tegenpartij: een sleuteldeel met de verkeerde lengte, een ML-KEM-inkapselingssleutel die de FIPS 203-controle niet doorstaat, een punt dat niet op de curve ligt, of een X25519-resultaat dat volledig uit nullen bestaat. RFC 10024 vereist dat de verbinding in elk van die gevallen wordt afgebroken met een illegal_parameter-waarschuwing.

Alles wat op X25519 is gebouwd, dat wil zeggen de sgcHybrid_-functies, X-Wing en de groep X25519MLKEM768, wordt uit een SGC_CRYPTO_FIPS-build weggecompileerd, zie FIPS-modus. De twee SecP-groepen blijven, omdat P-256, P-384 en ML-KEM alle drie goedgekeurde algoritmen zijn. Roep sgcTLSHybrid_Available aan voordat u een groep aanbiedt of accepteert, in plaats van aan te nemen dat ze aanwezig is.

ML-DSA (aanbevolen voor handtekeningen)

Ondertekenen trekt een maskeringsvector en probeert een handtekening op te bouwen, en herstart wanneer de poging informatie over de sleutel zou lekken; het verwachte aantal pogingen is klein maar niet vast, zodat ondertekenen een variabele hoeveelheid tijd kost. Dat is inherent aan het schema en vormt geen zijkanaal op de sleutel. Handtekeningen zijn groot: 2420 bytes voor ML-DSA-44 tegenover 64 voor Ed25519, en publieke sleutels 1312 bytes tegenover 32, dus houd rekening met dat verschil voordat u het toepast in een protocol dat gevoelig is voor grootte.


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 (conservatieve terugval)

SLH-DSA berust uitsluitend op de hashfunctie: geen lattice-probleem om ooit, op enig moment in de toekomst, te breken. Die garantie is duur. Een handtekening loopt van 7856 bytes (de kleinste, SLH-DSA-SHAKE-128s) tot 49856 bytes (de grootste, SLH-DSA-SHAKE-256f), en het ondertekenen met een s-parameterset (small) doorloopt honderdduizenden hash-bewerkingen en kan tientallen seconden duren; de f-sets (fast) ondertekenen in ongeveer één tot zeven seconden bij ruwweg het dubbele van de handtekeninggrootte. Verificatie is snel voor elke parameterset, wat een use case van zelden-ondertekenen-vaak-verifiëren zoals firmware- of codeondertekening beter past dan een interactief protocol.


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;

Sleutelformaten: SubjectPublicKeyInfo, PKCS#8 en PEM

De rauwe bytereeksen die de FIPS-algoritmen verwerken vormen geen opslagformaat, en geen enkele andere implementatie zal ze lezen. Om een sleutel naar schijf te schrijven, in een certificaat te plaatsen of aan een andere bibliotheek door te geven, exporteert u die in de standaardcontainers: een X.509 SubjectPublicKeyInfo voor een publieke sleutel, een PKCS#8 OneAsymmetricKey voor een privésleutel, en een PEM-omhulling (RFC 7468) om beide, met het label PUBLIC KEY en PRIVATE KEY. De drie schema's bieden dezelfde acht functies, elk volgens de RFC die de codering ervan definieert.

Schema RFC van de container DER-functies PEM-functies
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

De algoritme-identificatie draagt de NIST-objectidentificatie met afwezige parameters, zodat de parameterset met de sleutel meereist en het importeren die teruggeeft. sgcMLDSA_OID, sgcMLKEM_OID en sgcSLHDSA_OID geven de tekenreeks met punten, en de bijbehorende ParamsFromOID-functies zetten er een terug.

Drie privésleutelvormen voor ML-DSA en ML-KEM.

De exportfuncties nemen een aFormat-parameter van het type TsgcPQCPrivateKeyFormat, gedeclareerd in sgcCrypto_DER. pqkfSeed, de standaardwaarde, schrijft alleen de seed, 32 bytes voor ML-DSA en de 64-byte d || z voor ML-KEM; dit is de vorm die beide RFC's aanbevelen en veruit de kleinste. pqkfExpanded schrijft de uitgebreide FIPS-privésleutel. pqkfBoth schrijft beide en geeft een fout wanneer de uitgebreide sleutel niet de uitbreiding van de seed is, wat de RFC's een misvormde sleutel noemen.

Een seed kan achteraf niet meer uit een uitgebreide sleutel worden afgeleid, vraag die dus bij het genereren op met sgcMLDSA_GenerateKeyPairAndSeed of sgcMLKEM_GenerateKeyPairAndSeed wanneer u van plan bent de aanbevolen vorm op te slaan. SLH-DSA heeft één enkele privésleutelvorm, SK.seed || SK.prf || PK.seed || PK.root, dus de export ervan neemt geen formaatparameter.


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;

Importeren valideert, het vertrouwt niet.

Elke importfunctie geeft False terug in plaats van een fout te genereren, en ze geeft False terug bij misvormde DER, bij overtollige gegevens aan het eind, bij een onbekende objectidentificatie, bij parameters die aanwezig zijn waar ze afwezig moeten zijn, en bij een sleutel waarvan de lengte niet bij de parameterset past. Naast de structuur moet een ML-KEM-publieke sleutel de FIPS 203-moduluscontrole doorstaan, moet een uitgebreide ML-KEM-privésleutel de hashcontrole en de paarsgewijze consistentietest doorstaan, en wordt een uitgebreide ML-DSA-privésleutel opnieuw tot zijn publieke sleutel opgebouwd, zodat de opgeslagen t0 en tr daar beide mee moeten overeenkomen. Een ingesloten publieke sleutel die niet bij de privésleutel past, wordt geweigerd. Wanneer een privésleutel een seed bevat, komen alle drie de uitvoerwaarden gevuld terug, zodat een aanroeper die nooit apart hoeft uit te breiden.

sgcSLHDSA_ImportPrivateKeyInfo en sgcSLHDSA_ImportPrivateKeyPEM nemen een extra aVerify-parameter, standaard True, die PK.root opnieuw berekent uit de privé-seed en een sleutel weigert waarvan de opgeslagen root afwijkt. Die herberekening kost evenveel als het genereren van een sleutel, wat seconden is voor de s-parametersets, geef dus alleen False mee voor sleutels die uw eigen toepassing heeft gegenereerd of al vertrouwt. De objectidentificatie, de lengtes en de structuur worden ook gecontroleerd wanneer die False is.

Dit zijn de containers die de rest van de bibliotheek leest. Een PKCS#8 ML-DSA-privésleutel is waarmee de JWT-client ondertekent, een SubjectPublicKeyInfo is waarmee de JWT-server valideert, en beide zijn wat Certificaten & PKI in een certificaat of een certificaataanvraag plaatst.

Een parameterset kiezen

Elke groottefunctie (sgcMLKEM_PublicKeySize, sgcMLDSA_SignatureSize, sgcSLHDSA_SignatureSize en hun PrivateKeySize-tegenhangers) neemt dezelfde parameter-enum als de bewerking zelf, zodat een protocol zijn buffers kan dimensioneren voordat er enige cryptografie plaatsvindt:

Parameterset Publieke sleutel Privésleutel Ciphertext / handtekening
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 groottes zijn in bytes. Het gedeelde geheim van ML-KEM is altijd 32 bytes, ongeacht de parameterset (sgcMLKEM_SharedSecretSize).