Crittografia simmetrica

La crittografia simmetrica utilizza la stessa chiave per cifrare e decifrare. L'idea più importante di questa pagina è la differenza tra un cifrario semplice e un cifrario AEAD (Authenticated Encryption with Associated Data). AES-CBC o AES-CTR da soli offrono riservatezza e nient'altro: un attaccante che non può leggere il plaintext può comunque alterare dei bit nel ciphertext, e il destinatario decifrerà il risultato manomesso senza alcuna indicazione che qualcosa non va. AES-GCM e ChaCha20-Poly1305 autenticano oltre a cifrare, quindi la manomissione viene rilevata e la decifratura fallisce apertamente. Preferire un cifrario AEAD a meno che qualcosa di specifico non imponga una modalità semplice.

Tutte le funzioni risiedono in sgcCrypto_AES, sgcCrypto_Modes, sgcCrypto_ChaCha e sgcCrypto_Poly1305. Non riutilizzare mai un IV o un nonce con la stessa chiave: per CTR e i cifrari AEAD, il riutilizzo non si limita a indebolire la riservatezza, per GCM in particolare compromette del tutto la garanzia di autenticazione. Generarne uno nuovo con sgcRandomBytes per ogni messaggio.

AES-GCM (consigliato)

La modalità AEAD predefinita per la maggior parte dei protocolli. Il nonce è di 12 byte; generarlo casualmente per ogni messaggio. aAAD è autenticato ma non cifrato, il che è il modo in cui un ciphertext viene vincolato al proprio contesto, ad esempio un id di messaggio o una versione di protocollo, senza nascondere quel contesto a chiunque possa vedere il ciphertext.


function sgcAES_GCM_Encrypt(const aKey, aIV, aPlain, aAAD: TBytes; out aTag: TBytes): TBytes;
function sgcAES_GCM_Decrypt(const aKey, aIV, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;

var
  oKey, oIV, oCipher, oTag, oPlain, oAAD: TBytes;
begin
  oIV := sgcRandomBytes(12);
  oCipher := sgcAES_GCM_Encrypt(oKey, oIV, oPlain, oAAD, oTag);
  { send oIV, oCipher and oTag to the recipient }
  if not sgcAES_GCM_Decrypt(oKey, oIV, oCipher, oAAD, oTag, oPlain) then
    { the ciphertext, the tag or the AAD was altered: reject it };
end;

ChaCha20-Poly1305 e XChaCha20-Poly1305

La coppia AEAD dell'RFC 8439. Laddove AES-GCM si appoggia sull'accelerazione hardware di AES, ChaCha20-Poly1305 gira a velocità costante interamente in software, motivo per cui TLS e WireGuard lo utilizzano per default sui dispositivi mobili ed embedded privi di AES-NI. Il nonce è di 12 byte, con la stessa restrizione di GCM. XChaCha20-Poly1305 estende il nonce a 24 byte, abbastanza grande da poter essere scelto casualmente per l'intera vita di una chiave senza dover tenere traccia di un contatore, il che conta quando molti writer indipendenti condividono una chiave e non possono coordinare un contatore tra loro.


function sgcChaCha20Poly1305_Encrypt(const aKey, aNonce, aPlain, aAAD: TBytes; out aTag: TBytes): TBytes;
function sgcChaCha20Poly1305_Decrypt(const aKey, aNonce, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;
function sgcXChaCha20Poly1305_Encrypt(const aKey, aNonce, aPlain, aAAD: TBytes; out aTag: TBytes): TBytes;
function sgcXChaCha20Poly1305_Decrypt(const aKey, aNonce, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;

AES-CCM

Counter with CBC-MAC, RFC 3610 e NIST SP 800-38C. È l'AEAD che gli stack constrained e IoT preferiscono, perché richiede solo il block cipher stesso e nessuna tabella GHASH: Zigbee, 802.15.4, Bluetooth e le cipher suite TLS CCM lo specificano tutti. Dove c'è margine, GCM resta la scelta migliore, poiché CCM esegue due passaggi sui dati e non può essere pipelined.

Il nonce è di 7-13 byte e la sua lunghezza non è una scelta libera: 15 meno la lunghezza del nonce è la dimensione del campo lunghezza, quindi fissa la dimensione massima del messaggio, e un nonce a 13 byte limita il plaintext a 65535 byte. Un nonce non deve mai ripetersi con la stessa chiave, esattamente come con GCM. aTagLen è di 4, 6, 8, 10, 12, 14 o 16 byte.


function sgcAES_CCM_Encrypt(const aKey, aNonce, aPlain, aAAD: TBytes; aTagLen: Integer; out aTag: TBytes): TBytes;
function sgcAES_CCM_Decrypt(const aKey, aNonce, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;

var
  oKey, oNonce, oCipher, oTag, oPlain, oAAD: TBytes;
begin
  oNonce := sgcRandomBytes(12);
  oCipher := sgcAES_CCM_Encrypt(oKey, oNonce, oPlain, oAAD, 16, oTag);
  { send oNonce, oCipher and oTag to the recipient }
  if not sgcAES_CCM_Decrypt(oKey, oNonce, oCipher, oAAD, oTag, oPlain) then
    { the ciphertext, the tag or the AAD was altered: reject it };
end;

AES-GMAC

GMAC è esattamente AES-GCM eseguito con un plaintext vuoto e l'intero messaggio passato come additional data, il modo in cui lo standard lo definisce. Eredita quindi ognuna delle regole sul nonce di GCM: aIV è normalmente di 12 byte e non deve mai ripetersi con la stessa chiave. Riutilizzare un IV con GMAC non si limita a indebolirlo, rivela la sottochiave hash e permette a un attaccante di forgiare tag apertamente, quindi il fallimento è totale anziché graduale. AES-CMAC nella pagina Hashing e autenticazione dei messaggi non richiede alcun IV ed è la scelta più sicura a meno che GCM non sia già presente nel protocollo. Il tag è di 16 byte.


function sgcAES_GMAC(const aKey, aIV, aData: TBytes): TBytes;
function sgcAES_GMAC_Verify(const aKey, aIV, aData, aTag: TBytes): Boolean;

Modalità a blocchi AES

Queste offrono solo riservatezza, senza autenticazione: abbinarle a un MAC dalla pagina Hashing e autenticazione dei messaggi se il plaintext necessita di integrità, oppure utilizzare AES-GCM sopra descritto.


function sgcAES_CBC_Encrypt(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CBC_Decrypt(const aKey, aIV, aCipher: TBytes): TBytes;
function sgcAES_CTR(const aKey, aIV, aData: TBytes): TBytes;
function sgcAES_CFB_Encrypt(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CFB_Decrypt(const aKey, aIV, aCipher: TBytes): TBytes;
function sgcAES_OFB(const aKey, aIV, aData: TBytes): TBytes;
function sgcAES_ECB_Encrypt(const aKey, aPlain: TBytes): TBytes;
function sgcAES_ECB_Decrypt(const aKey, aCipher: TBytes): TBytes;

sgcAES_CTR e sgcAES_OFB sono l'inverso di sé stesse: chiamare di nuovo la stessa funzione con la stessa chiave e lo stesso IV per decifrare. ECB è incluso solo per interoperare con formati datati che lo specificano; blocchi di plaintext identici producono blocchi di ciphertext identici, il che rivela la struttura dei dati, quindi non dovrebbe essere scelto per nulla di nuovo.

CBC è disponibile anche senza alcun padding, la modalità che Java scrive come AES/CBC/NoPadding. La lunghezza dell'input deve essere un multiplo esatto di 16 e viene sollevata un'eccezione quando non lo è, anziché far sì che la chiamata esegua silenziosamente il padding producendo un ciphertext che l'altra parte non può leggere. È sicuro solo dove il protocollo garantisce già dati allineati al blocco.


function sgcAES_CBC_Encrypt_NoPad(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CBC_Decrypt_NoPad(const aKey, aIV, aCipher: TBytes): TBytes;

Il ciphertext stealing evita l'overhead di padding di CBC per dati che non sono un multiplo della dimensione del blocco:


function sgcAES_CTS_Encrypt(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CTS_Decrypt(const aKey, aIV, aCipher: TBytes): TBytes;

Schemi di padding

Una modalità a blocchi richiede che il plaintext sia un multiplo di 16 byte, ed è lo schema di padding a farcelo arrivare. PKCS#7 è l'unica scelta corretta per qualcosa di nuovo; gli altri sei esistono per leggere e scrivere dati da un sistema che ha già scelto diversamente. padPKCS5 non è nell'elenco perché per un blocco a 16 byte coincide con PKCS#7, i due nomi differiscono solo nelle dimensioni di blocco per cui sono stati definiti.


type
  TsgcPadding = (padNone, padPKCS7, padZero, padANSIX923, padISO7816_4,
    padISO10126_2, padTBC);

function sgcPad_Add(const aData: TBytes; aBlockSize: Integer; aPadding: TsgcPadding): TBytes;
function sgcPad_Remove(const aData: TBytes; aBlockSize: Integer; aPadding: TsgcPadding; out aPlain: TBytes): Boolean;

function sgcAES_CBC_EncryptPad(const aKey, aIV, aPlain: TBytes; aPadding: TsgcPadding): TBytes;
function sgcAES_CBC_DecryptPad(const aKey, aIV, aCipher: TBytes; aPadding: TsgcPadding): TBytes;
function sgcAES_ECB_EncryptPad(const aKey, aPlain: TBytes; aPadding: TsgcPadding): TBytes;
function sgcAES_ECB_DecryptPad(const aKey, aCipher: TBytes; aPadding: TsgcPadding): TBytes;

var
  oKey, oIV, oPlain, oCipher: TBytes;
begin
  oCipher := sgcAES_CBC_EncryptPad(oKey, oIV, oPlain, padANSIX923);
end;

sgcAES_CBC_Encrypt e sgcAES_ECB_Encrypt più sopra sono la scorciatoia PKCS#7 per queste. sgcPad_Remove restituisce False su un pad malformato anziché sollevare un'eccezione, e segue lo stesso percorso per ogni tipo di errore, perché distinguerli è esattamente ciò di cui ha bisogno un oracolo di padding. Prestare particolare attenzione a padZero: non riesce a distinguere un byte di padding da uno zero finale del plaintext, quindi rimuove ogni zero finale, e dati che possono legittimamente terminare con uno zero tornano tronchi.

Accelerazione hardware AES (AES-NI)

Sulle build Windows Win32 e Win64 create con Delphi, sgcCrypto_AES verifica la CPU a runtime e, quando questa riporta AES-NI, PCLMULQDQ e SSSE3, esegue la cifratura a blocco singolo, AES-CTR, AES-GCM (la modalità contatore e GHASH) e la cifratura AES-CBC su quelle istruzioni, che sono a tempo costante per costruzione. Senza di esse, e su Linux64, macOS, iOS e Android, viene utilizzato come prima il nucleo software a tempo costante. La decifratura CBC utilizza sempre il nucleo software. Il motore TLS nativo ne beneficia automaticamente.

sgcAES_HardwareAvailable restituisce True quando questa build include il percorso hardware e la CPU lo supporta. La variabile globale sgcAES_UseHardware, True per impostazione predefinita, impostata a False forza il nucleo software, per esempio per confrontare entrambi. Impostarla una sola volta all'avvio.

var sgcAES_UseHardware: Boolean = True;
function sgcAES_HardwareAvailable: Boolean;

if sgcAES_HardwareAvailable then
  Memo1.Lines.Add('AES-NI in uso');

AES Key Wrap

Il key wrapping protegge una chiave, non un messaggio: non richiede alcun IV, è deterministico e autentica la chiave incapsulata. Questo è il modo standard per memorizzare una chiave di cifratura dei dati sotto una chiave master, ad esempio una chiave master di un HSM o di un KMS che incapsula una chiave per singolo file. L'RFC 3394 richiede che la chiave da incapsulare sia un multiplo di 8 byte; l'RFC 5649 aggiunge il padding per chiavi di qualsiasi lunghezza.


function sgcAES_KeyWrap(const aKEK, aPlainKey: TBytes): TBytes;
function sgcAES_KeyUnwrap(const aKEK, aWrapped: TBytes; out aPlainKey: TBytes): Boolean;
function sgcAES_KeyWrapPad(const aKEK, aPlainKey: TBytes): TBytes;
function sgcAES_KeyUnwrapPad(const aKEK, aWrapped: TBytes; out aPlainKey: TBytes): Boolean;

ChaCha20 e Salsa20 (stream cipher grezzi)

Gli stream cipher dietro le costruzioni AEAD sopra descritte, forniti direttamente per i protocolli che specificano il cifrario grezzo. Nessuno dei due autentica: quasi sempre le versioni AEAD con Poly1305 più in alto in questa pagina sono ciò che serve davvero. Salsa20 è il predecessore di ChaCha20, mantenuto per compatibilità con i formati dell'era NaCl; scegliere ChaCha20 per qualsiasi cosa nuova.


function sgcChaCha20(const aKey, aNonce, aData: TBytes; aCounter: Cardinal): TBytes;
function sgcXChaCha20(const aKey, aNonce, aData: TBytes; aCounter: Cardinal): TBytes;
function sgcSalsa20(const aKey, aNonce, aData: TBytes; aCounter: Int64): TBytes;
function sgcXSalsa20(const aKey, aNonce, aData: TBytes; aCounter: Int64): TBytes;

Legacy: DES e NTLM

sgcCrypto_Legacy fornisce anche il DES singolo in modalità ECB e la derivazione di chiave NTLM v1 basata su DES. DES ha una chiave effettiva di 56 bit, forzabile con brute force su hardware moderno in poche ore; esiste solo per interoperare con protocolli che lo specificano per nome (l'autenticazione NTLM è il motivo usuale). Non sceglierlo per nulla di nuovo; utilizzare invece AES.


function sgcDES_EncryptECB(const aKey, aData: TBytes): TBytes;
function sgcDES_DecryptECB(const aKey, aData: TBytes): TBytes;
function sgcDES_NTLM7to8(const aKey7: TBytes): TBytes;