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:
- ML-KEM (FIPS 203) concorda un segreto condiviso. Sostituisce il compito che oggi svolge Diffie-Hellman.
- ML-DSA (FIPS 204) firma. Sostituisce le firme RSA ed ECDSA.
- SLH-DSA (FIPS 205) firma anch'esso, costruito soltanto su funzioni hash, per il caso in cui vogliate l'assunzione più prudente disponibile e possiate permettervi la dimensione della firma.
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:
- TLS. Il motore nativo TLS 1.3 negozia i gruppi ibridi di scambio di chiavi di RFC 10024,
X25519MLKEM768,SecP256r1MLKEM768eSecP384r1MLKEM1024, e l'elenco predefinito comincia già con uno ibrido. Vedere Delphi TLS 1.3 senza DLL OpenSSL. - JWT. I token possono essere firmati e verificati con ML-DSA secondo RFC 9964. Vedere Firmare un JWT con ML-DSA in Delphi.
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
- Delphi TLS 1.3 senza DLL OpenSSL
- Firmare un JWT con ML-DSA in Delphi
- sgcWebSockets 2026.10, tutto il resto di questa versione
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.
