Symmetrische versleuteling

Symmetrische versleuteling gebruikt dezelfde sleutel om te versleutelen en te ontsleutelen. Het idee dat op deze pagina het meest telt, is het verschil tussen een gewoon cijfer en een AEAD-cijfer (Authenticated Encryption with Associated Data). AES-CBC of AES-CTR alleen bieden vertrouwelijkheid en verder niets: een aanvaller die de platte tekst niet kan lezen, kan nog steeds bits in de ciphertext omdraaien, en de ontvanger ontsleutelt het gemanipuleerde resultaat zonder enige aanwijzing dat er iets mis is. AES-GCM en ChaCha20-Poly1305 authenticeren naast versleutelen, zodat manipulatie wordt gedetecteerd en ontsleuteling regelrecht mislukt. Geef de voorkeur aan een AEAD-cijfer, tenzij iets specifieks een gewone modus afdwingt.

Alle functies bevinden zich in sgcCrypto_AES, sgcCrypto_Modes, sgcCrypto_ChaCha en sgcCrypto_Poly1305. Hergebruik nooit een IV of een nonce met dezelfde sleutel: voor CTR en de AEAD-cijfers verzwakt hergebruik niet alleen de vertrouwelijkheid, voor GCM specifiek doorbreekt het de authenticatiegarantie regelrecht. Genereer voor elk bericht een verse IV/nonce met sgcRandomBytes.

AES-GCM (aanbevolen)

De AEAD-modus waarnaar de meeste protocollen standaard grijpen. De nonce is 12 bytes; genereer hem willekeurig per bericht. aAAD wordt geauthenticeerd maar niet versleuteld, en zo wordt een ciphertext gebonden aan zijn context, bijvoorbeeld een bericht-id of een protocolversie, zonder die context te verbergen voor iedereen die de ciphertext kan zien.


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 en XChaCha20-Poly1305

Het AEAD-paar van RFC 8439. Waar AES-GCM leunt op AES-hardwareversnelling, draait ChaCha20-Poly1305 met constante snelheid in pure software, wat de reden is waarom TLS en WireGuard er standaard voor kiezen op mobiele en embedded apparaten zonder AES-NI. De nonce is 12 bytes, dezelfde beperking als bij GCM. XChaCha20-Poly1305 breidt de nonce uit tot 24 bytes, groot genoeg om willekeurig te kiezen voor de hele levensduur van een sleutel zonder een teller bij te houden, wat van belang is wanneer veel onafhankelijke schrijvers één sleutel delen en geen teller onderling kunnen coördineren.


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 en NIST SP 800-38C. Dit is de AEAD-modus waar constrained en IoT-stacks de voorkeur aan geven, omdat er alleen het blokcijfer zelf voor nodig is en geen GHASH-tabel: Zigbee, 802.15.4, Bluetooth en de TLS CCM-suites schrijven het allemaal voor. Waar er ruimte is, blijft GCM de betere keuze, aangezien CCM twee keer over de data gaat en niet gepipelined kan worden.

De nonce is 7 tot 13 bytes en de lengte ervan is geen vrije keuze: 15 min de nonce-lengte is de grootte van het lengteveld, wat dus de maximale berichtgrootte vastlegt, en een nonce van 13 bytes beperkt de platte tekst tot 65535 bytes. Een nonce mag nooit worden hergebruikt onder dezelfde sleutel, precies als bij GCM. aTagLen is 4, 6, 8, 10, 12, 14 of 16 bytes.


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 is precies AES-GCM uitgevoerd met een lege platte tekst en het hele bericht doorgegeven als additional data, wat de manier is waarop de standaard het definieert. Het erft daarom elke nonce-regel van GCM: aIV is normaal 12 bytes en mag nooit worden hergebruikt onder dezelfde sleutel. Een IV hergebruiken onder GMAC verzwakt het niet alleen, het lekt de hash-subsleutel en laat een aanvaller regelrecht tags vervalsen, zodat het falen totaal is in plaats van geleidelijk. AES-CMAC op de pagina Hashing & berichtauthenticatie heeft helemaal geen IV nodig en is de veiligere keuze tenzij GCM al in het protocol zit. De tag is 16 bytes.


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

AES-blokmodi

Deze bieden alleen vertrouwelijkheid, zonder authenticatie: combineer met een MAC uit Hashing & berichtauthenticatie als de platte tekst integriteit nodig heeft, of gebruik in plaats daarvan de hierboven beschreven AES-GCM.


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 en sgcAES_OFB zijn hun eigen inverse: roep dezelfde functie opnieuw aan met dezelfde sleutel en IV om te ontsleutelen. ECB is alleen opgenomen voor interoperabiliteit met oude formaten die het voorschrijven; identieke blokken platte tekst leveren identieke ciphertext-blokken op, wat de structuur van de gegevens lekt, dus het mag niet worden gekozen voor iets nieuws.

CBC is ook beschikbaar zonder enige padding, de modus die Java schrijft als AES/CBC/NoPadding. De invoerlengte moet een exact veelvoud van 16 zijn en er wordt een exception opgeworpen wanneer dat niet zo is, in plaats van dat de aanroep stilzwijgend padding toevoegt en een ciphertext produceert die de andere kant niet kan lezen. Dit is alleen veilig waar het protocol al blokuitgelijnde data garandeert.


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

Ciphertext stealing vermijdt de padding-overhead van CBC voor gegevens die geen veelvoud zijn van de blokgrootte:


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

Padding-schema's

Een blokmodus vereist dat de platte tekst een veelvoud van 16 bytes is, en het padding-schema is hoe dat wordt bereikt. PKCS#7 is de enige juiste keuze voor iets nieuws; de andere zes bestaan om data te lezen en te schrijven van een systeem dat al anders heeft gekozen. padPKCS5 staat niet in de lijst omdat het voor een blok van 16 bytes gewoon PKCS#7 is, de twee namen verschillen alleen in de blokgroottes waarvoor ze gedefinieerd zijn.


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 en sgcAES_ECB_Encrypt, hierboven beschreven, zijn simpelweg deze functies met PKCS#7 als vaste keuze. sgcPad_Remove geeft False terug bij een misvormde pad in plaats van een exception te gooien, en doorloopt daarbij voor elke soort fout hetzelfde pad, omdat het onderscheiden ervan is wat een padding-oracle nodig heeft. Wees vooral voorzichtig met padZero: het kan een paddingbyte niet onderscheiden van een afsluitende nul in de platte tekst, dus het verwijdert elke afsluitende nul, en data die legitiem op een nul kan eindigen, komt afgekapt terug.

AES-hardwareversnelling (AES-NI)

Op Windows Win32- en Win64-builds gemaakt met Delphi controleert sgcCrypto_AES de CPU tijdens runtime en, wanneer die AES-NI, PCLMULQDQ en SSSE3 meldt, draait single block-versleuteling, AES-CTR, AES-GCM (de countermodus en GHASH) en AES-CBC-versleuteling op die instructies, die door hun ontwerp constante tijd hebben. Zonder die instructies, en op Linux64, macOS, iOS en Android, wordt zoals voorheen de constante-tijd softwarekern gebruikt. AES-CBC-ontsleuteling gebruikt altijd de softwarekern. De native TLS-engine profiteert er automatisch van.

sgcAES_HardwareAvailable geeft True terug wanneer deze build het hardwarepad bevat en de CPU dit ondersteunt. De globale variabele sgcAES_UseHardware, standaard True, op False gezet dwingt de softwarekern af, bijvoorbeeld om beide te vergelijken. Stel dit eenmalig in bij het opstarten.

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

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

AES Key Wrap

Key wrapping beschermt een sleutel, geen bericht: het heeft geen IV nodig, is deterministisch, en authenticeert de gewrapte sleutel. Dit is de standaardmanier om een data-encryptiesleutel onder een mastersleutel op te slaan, bijvoorbeeld een HSM- of KMS-mastersleutel die een sleutel per bestand wrapt. RFC 3394 vereist dat de te wrappen sleutel een veelvoud van 8 bytes is; RFC 5649 voegt padding toe voor sleutels van elke lengte.


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 en Salsa20 (raw stroomcijfers)

De stroomcijfers achter de bovenstaande AEAD-constructies, rechtstreeks beschikbaar gesteld voor protocollen die het raw cijfer voorschrijven. Geen van beide authenticeert: bijna altijd zijn de Poly1305 AEAD-versies verderop op deze pagina wat u eigenlijk wilt. Salsa20 is de voorganger van ChaCha20, behouden voor compatibiliteit met formaten uit het NaCl-tijdperk; kies ChaCha20 voor iets nieuws.


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;

Verouderd: DES en NTLM

sgcCrypto_Legacy biedt ook single DES in ECB-modus en de op DES gebaseerde NTLM v1-sleutelafleiding. DES heeft een effectieve sleutel van 56 bit, met brute force te kraken op moderne hardware in enkele uren; het bestaat alleen voor interoperabiliteit met protocollen die het met naam voorschrijven (NTLM-authenticatie is meestal de reden). Kies het niet voor iets nieuws; gebruik in plaats daarvan AES.


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