Crittografia post-quantistica in Delphi

· Componenti
Crittografia post-quantistica in Delphi

C'è una domanda che vale la pena porsi sui dati che oggi la vostra applicazione gestisce: per quanto tempo restano sensibili? Una cartella clinica, un contratto, un cedolino paga, un insieme di credenziali. Se la risposta è più di qualche anno, allora la minaccia interessante non è un attaccante che rompe la vostra cifratura adesso. È un attaccante che registra il traffico adesso, lo conserva e lo decifra più avanti, quando gli strumenti si saranno messi al passo. Questa abitudine ha un nome, harvest now, decrypt later (raccogliere ora, decifrare dopo), e non richiede che qualcuno costruisca oggi un computer quantistico. Richiede solo che qualcuno creda che qualcun altro lo farà.

Per questo gli standard si sono mossi per primi. NIST ha pubblicato gli algoritmi nel 2024, browser e CDN hanno attivato lo scambio di chiavi ibrido nel corso del 2025, e le scadenze per il ritiro dell'accordo di chiavi con RSA e curva ellittica sono ormai scritte nelle linee guida pubbliche. sgcWebSockets 2026.10 porta l'intero insieme in Delphi, scritto in Object Pascal all'interno del pacchetto sgcCrypto, senza alcuna libreria esterna da installare o distribuire.

Tre algoritmi, un pacchetto

I tre standard NIST svolgono compiti diversi, e normalmente userete i primi due:

Concordare una chiave con ML-KEM

Un meccanismo di incapsulamento di chiavi è più semplice da usare di Diffie-Hellman. Il mittente prende la chiave pubblica del destinatario e produce due cose: un nuovo segreto condiviso e un testo cifrato che lo trasporta. Il destinatario trasforma quel testo cifrato di nuovo nello stesso segreto.

uses
  sgcCrypto_MLKEM;

var
  oPublicKey, oPrivateKey, oCiphertext, oSecret, oSame: TBytes;
begin
  sgcMLKEM_GenerateKeyPair(mlkem768, oPublicKey, oPrivateKey);

  // the sender, who only ever sees the public key
  sgcMLKEM_Encapsulate(mlkem768, oPublicKey, oCiphertext, oSecret);

  // the recipient, who holds the private key, gets the same 32 bytes
  oSame := sgcMLKEM_Decapsulate(mlkem768, oPrivateKey, oCiphertext);
end;

I set di parametri sono mlkem512, mlkem768 e mlkem1024. I controlli sull'input richiesti da FIPS 203 vengono eseguiti prima di qualunque altra cosa, così una chiave pubblica malformata genera un'eccezione invece di produrre silenziosamente un segreto, e un testo cifrato che non si decifra correttamente restituisce un segreto pseudocasuale invece di un errore, che è esattamente il rifiuto implicito richiesto dallo standard.

X-Wing, quando non volete scegliere

Adottare un nuovo algoritmo significa fidarsi di esso. Le costruzioni ibride eliminano questa decisione: combinano un algoritmo classico con uno post-quantistico, così il risultato resta sicuro finché anche solo una delle due metà regge. X-Wing abbina X25519 a ML-KEM-768 ed espone un'unica coppia di chiavi.

uses
  sgcCrypto_MLKEM_Hybrid;

var
  oPublicKey, oPrivateKey, oCiphertext, oSecret: TBytes;
begin
  sgcXWing_GenerateKeyPair(oPublicKey, oPrivateKey);
  sgcXWing_Encapsulate(oPublicKey, oCiphertext, oSecret);
  oSecret := sgcXWing_Decapsulate(oPrivateKey, oCiphertext);
end;

Firmare con ML-DSA

La firma richiede il set di parametri, la chiave privata, il messaggio e una stringa di contesto, che normalmente è vuota:

uses
  sgcCrypto_MLDSA;

var
  oSeed, oPublicKey, oPrivateKey, oSignature: TBytes;
begin
  sgcMLDSA_GenerateKeyPairAndSeed(mldsa65, oSeed, oPublicKey, oPrivateKey);

  oSignature := sgcMLDSA_Sign(mldsa65, oPrivateKey, aMessage, nil);

  if sgcMLDSA_Verify(mldsa65, oPublicKey, aMessage, oSignature, nil) then
    ShowMessage('signature is valid');
end;

Notate il seed. Una chiave privata ML-DSA può essere memorizzata come i 32 byte da cui è stata generata, come la chiave espansa, oppure come entrambe le cose, e la libreria legge e scrive tutte e tre le forme. Il seed è la forma che conviene tenere in un file di configurazione: è piccolo, e la chiave espansa viene derivata da esso in modo deterministico.

Chiavi che viaggiano

Un algoritmo con cui nessuno può scambiare chiavi non serve a molto, quindi le codifiche seguono i profili pubblicati: SubjectPublicKeyInfo e PKCS#8, in DER o PEM, secondo RFC 9935 per ML-KEM, RFC 9881 per ML-DSA e RFC 9909 per SLH-DSA.

var
  vPublicPEM, vPrivatePEM: string;
begin
  vPublicPEM := sgcMLDSA_ExportPublicKeyPEM(mldsa65, oPublicKey);
  vPrivatePEM := sgcMLDSA_ExportPrivateKeyPEM(mldsa65, oSeed, nil);
end;

Sono normali blocchi BEGIN PUBLIC KEY e BEGIN PRIVATE KEY, che altre implementazioni sanno leggere. Una chiave privata viene verificata al momento dell'importazione: la metà pubblica viene derivata di nuovo e confrontata, così una chiave che non appartiene al proprio certificato viene rifiutata subito, invece di produrre firme che nessuno può verificare.

Certificati e una CA post-quantistica

I certificati e le richieste di certificato possono portare chiavi post-quantistiche, e anche un'autorità di certificazione può averne una, cosicché un'intera catena può essere post-quantistica:

var
  vKey: TsgcX509SignKey;
  vOptions: TsgcX509Options;
  oCertDER: TBytes;
begin
  vKey := sgcX509_MLDSAKey(mldsa65, oPrivateKey, oPublicKey);
  vOptions.Subject.CommonName := 'My Post-Quantum CA';
  vOptions.KeyUsage := [kuKeyCertSign, kuCRLSign];
  oCertDER := sgcX509_CreateSelfSignedEx(vKey, vOptions);
end;

Le regole di utilizzo della chiave previste dai profili vengono applicate prima che venga firmato qualunque cosa, così una chiave post-quantistica a cui viene chiesta la cifratura di chiavi genera un'eccezione invece di produrre un certificato che viola il proprio profilo. La convalida della catena ora applica anche la lunghezza del percorso e i vincoli sui nomi dichiarati dai certificati emittenti.

Dove è già integrato

Ci sono due punti della libreria che gestiscono già tutto questo per voi, senza che dobbiate scrivere crittografia:

Come sappiamo che è corretto

L'implementazione di uno standard è solo un'affermazione finché qualcosa non la verifica. Tutti e tre gli algoritmi sono validati rispetto ai vettori di risposta nota di NIST, e questi vettori vengono distribuiti con la libreria come un insieme di test eseguibile, non come una frase in una scheda tecnica. Il firmatario ML-DSA è la variante deterministica, così la stessa chiave e lo stesso messaggio danno sempre la stessa firma, il che rende gli esempi pubblicati negli RFC riproducibili byte per byte.

Quale usare

Per l'accordo di chiavi, usate un ibrido: X-Wing da solo, oppure i gruppi ibridi di TLS se il traffico è TLS. Non rinunciate quasi a nulla e siete coperti qualunque sia l'assunzione che risulterà sbagliata. Per le firme, ML-DSA-65 è la scelta predefinita sensata, con ML-DSA-44 quando la dimensione conta e ML-DSA-87 quando non conta. Scegliete SLH-DSA quando la firma verrà verificata molti anni dopo da qualcosa che non potete aggiornare, come un firmware, e la dimensione della firma è sostenibile.

Aggiornamento

Tutto questo vive nel pacchetto sgcCrypto e non aggiunge alcuna dipendenza: niente OpenSSL, nessuna DLL, nulla da installare sulla macchina che esegue la vostra applicazione. Il codice esistente resta intatto finché non lo richiamate.

Continua a leggere

Guardalo

C'è un breve video su questo argomento sul canale eSeGeCe.

Domande, commenti o aiuto con la migrazione? Mettetevi in contatto — riceverete una risposta dalle persone che hanno scritto il codice.