Szyfrowanie symetryczne używa tego samego klucza do szyfrowania i odszyfrowywania. Najważniejsza idea na tej stronie to różnica między zwykłym szyfrem a szyfrem AEAD (Authenticated Encryption with Associated Data, szyfrowanie uwierzytelnione z danymi powiązanymi). Sam AES-CBC czy AES-CTR dają wyłącznie poufność i nic więcej: atakujący, który nie może odczytać tekstu jawnego, wciąż może odwrócić bity w szyfrogramie, a odbiorca odszyfruje sfałszowany wynik bez żadnej wskazówki, że coś jest nie tak. AES-GCM i ChaCha20-Poly1305 uwierzytelniają tak samo, jak szyfrują, więc manipulacja jest wykrywana, a odszyfrowanie kończy się jawnym niepowodzeniem. Preferuj szyfr AEAD, chyba że coś konkretnie wymusza tryb zwykły.
Wszystkie funkcje znajdują się w jednostkach sgcCrypto_AES, sgcCrypto_Modes, sgcCrypto_ChaCha i sgcCrypto_Poly1305. Nigdy nie używaj ponownie IV ani nonce z tym samym kluczem: dla CTR i szyfrów AEAD ponowne użycie nie tylko osłabia poufność, a w przypadku GCM wręcz całkowicie łamie gwarancję uwierzytelnienia. Generuj świeżą wartość za pomocą sgcRandomBytes dla każdej wiadomości.
AES-GCM (zalecany)
Tryb AEAD, którego domyślnie używa większość protokołów. Nonce ma 12 bajtów; generuj go losowo dla każdej wiadomości. aAAD jest uwierzytelniane, ale nieszyfrowane, co pozwala związać szyfrogram z jego kontekstem, na przykład identyfikatorem wiadomości lub wersją protokołu, bez ukrywania tego kontekstu przed kimkolwiek, kto widzi szyfrogram.
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 i XChaCha20-Poly1305
Para AEAD z RFC 8439. Tam, gdzie AES-GCM opiera się na sprzętowym przyspieszeniu AES, ChaCha20-Poly1305 działa ze stałą szybkością w czystym oprogramowaniu, dlatego TLS i WireGuard domyślnie go stosują na urządzeniach mobilnych i wbudowanych bez AES-NI. Nonce ma 12 bajtów, to samo ograniczenie co w GCM. XChaCha20-Poly1305 rozszerza nonce do 24 bajtów, na tyle dużo, by dobierać go losowo przez cały okres życia klucza bez śledzenia licznika, co ma znaczenie, gdy wielu niezależnych nadawców dzieli jeden klucz i nie może uzgodnić między sobą wspólnego licznika.
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 oraz NIST SP 800-38C. To szyfr AEAD, który faworyzują stosy ograniczone zasobowo i IoT, ponieważ potrzebuje wyłącznie samego szyfru blokowego i żadnej tablicy GHASH: Zigbee, 802.15.4, Bluetooth i zestawy szyfrów CCM w TLS wszystkie go określają. Tam, gdzie jest miejsce, GCM pozostaje lepszym wyborem, ponieważ CCM wykonuje dwa przejścia po danych i nie może być potokowany.
Nonce ma od 7 do 13 bajtów, a jego długość nie jest dowolnym wyborem: 15 minus długość nonce to rozmiar pola długości, więc to on ustala maksymalny rozmiar wiadomości, a 13-bajtowy nonce ogranicza tekst jawny do 65535 bajtów. Nonce nigdy nie może się powtórzyć pod tym samym kluczem, dokładnie tak samo jak w GCM. aTagLen wynosi 4, 6, 8, 10, 12, 14 lub 16 bajtów.
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 to dokładnie AES-GCM uruchomiony z pustym tekstem jawnym, przy czym cała wiadomość jest przekazana jako dane dodatkowe, dokładnie tak definiuje to standard. Dziedziczy więc każdą z reguł nonce obowiązujących w GCM: aIV ma zwykle 12 bajtów i nigdy nie może się powtórzyć pod tym samym kluczem. Ponowne użycie IV w GMAC nie tylko go osłabia, lecz ujawnia podklucz skrótu i pozwala atakującemu wprost fałszować znaczniki, więc porażka jest całkowita, a nie stopniowa. AES-CMAC ze strony Haszowanie i uwierzytelnianie wiadomości w ogóle nie potrzebuje IV i jest bezpieczniejszym wyborem, chyba że GCM jest już częścią protokołu. Znacznik ma 16 bajtów.
function sgcAES_GMAC(const aKey, aIV, aData: TBytes): TBytes;
function sgcAES_GMAC_Verify(const aKey, aIV, aData, aTag: TBytes): Boolean;
Tryby blokowe AES
Te dają wyłącznie poufność, bez żadnego uwierzytelnienia: połącz je z kodem MAC z Haszowanie i uwierzytelnianie wiadomości, jeśli tekst jawny wymaga integralności, albo użyj zamiast tego powyższego 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 i sgcAES_OFB są swoją własną odwrotnością: wywołaj tę samą funkcję ponownie z tym samym kluczem i IV, aby odszyfrować. ECB jest dołączony wyłącznie dla interoperacyjności ze starymi formatami, które go wymagają; identyczne bloki tekstu jawnego dają identyczne bloki szyfrogramu, co ujawnia strukturę danych, więc nie powinien być wybierany do niczego nowego.
CBC jest też dostępny bez żadnego dopełnienia, w trybie, który Java zapisuje jako AES/CBC/NoPadding. Długość danych wejściowych musi być dokładną wielokrotnością 16, a gdy nią nie jest, zgłaszany jest wyjątek, zamiast żeby wywołanie po cichu dopełniało dane i tworzyło szyfrogram, którego druga strona nie odczyta. Jest to bezpieczne wyłącznie tam, gdzie protokół już gwarantuje dane wyrównane do bloku.
function sgcAES_CBC_Encrypt_NoPad(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CBC_Decrypt_NoPad(const aKey, aIV, aCipher: TBytes): TBytes;
Ciphertext stealing pozwala uniknąć narzutu dopełnienia w CBC dla danych, które nie są wielokrotnością rozmiaru bloku:
function sgcAES_CTS_Encrypt(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CTS_Decrypt(const aKey, aIV, aCipher: TBytes): TBytes;
Schematy dopełniania
Tryb blokowy wymaga, aby tekst jawny był wielokrotnością 16 bajtów, a to właśnie schemat dopełniania sprawia, że tak się dzieje. PKCS#7 jest jedynym właściwym wyborem do czegokolwiek nowego; pozostałych sześć istnieje po to, by odczytywać i zapisywać dane z systemu, który wybrał już inaczej. padPKCS5 nie ma na tej liście, ponieważ dla bloku 16-bajtowego jest tożsamy z PKCS#7, obie nazwy różnią się wyłącznie rozmiarem bloku, dla którego zostały zdefiniowane.
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 i sgcAES_ECB_Encrypt wyżej są skrótem PKCS#7 dla tych funkcji. sgcPad_Remove zwraca False przy zniekształconym dopełnieniu zamiast zgłaszać wyjątek i przechodzi tę samą ścieżkę dla każdego rodzaju niepowodzenia, ponieważ ich rozróżnienie jest dokładnie tym, czego potrzebuje padding oracle. Zachowaj szczególną ostrożność z padZero: nie potrafi odróżnić bajtu dopełnienia od końcowego zera w tekście jawnym, więc usuwa każde końcowe zero, a dane, które w uzasadniony sposób mogą kończyć się zerem, wracają obcięte.
Sprzętowe przyspieszenie AES (AES-NI)
W kompilacjach Windows Win32 i Win64 tworzonych w Delphi, sgcCrypto_AES sprawdza procesor w czasie działania i, gdy zgłasza on AES-NI, PCLMULQDQ i SSSE3, uruchamia szyfrowanie pojedynczego bloku, AES-CTR, AES-GCM (tryb licznikowy i GHASH) oraz szyfrowanie AES-CBC na tych instrukcjach, które z założenia działają w stałym czasie. Bez nich, oraz na Linux64, macOS, iOS i Android, używany jest jak dotąd stałoczasowy rdzeń programowy. Deszyfrowanie CBC zawsze korzysta z rdzenia programowego. Natywny silnik TLS korzysta z tego automatycznie.
sgcAES_HardwareAvailable zwraca True, gdy ta kompilacja ma dostępną ścieżkę sprzętową, a procesor ją obsługuje. Zmienna globalna sgcAES_UseHardware, domyślnie True, ustawiona na False wymusza rdzeń programowy, na przykład aby porównać obie ścieżki. Ustaw ją raz, przy starcie.
var sgcAES_UseHardware: Boolean = True;
function sgcAES_HardwareAvailable: Boolean;
if sgcAES_HardwareAvailable then
Memo1.Lines.Add('AES-NI in use');
AES Key Wrap
Zawijanie klucza chroni klucz, a nie wiadomość: nie potrzebuje IV, jest deterministyczne i uwierzytelnia zawinięty klucz. To standardowy sposób przechowywania klucza szyfrowania danych pod kluczem głównym, na przykład klucz główny HSM lub KMS zawijający klucz dla pojedynczego pliku. RFC 3394 wymaga, aby zawijany klucz był wielokrotnością 8 bajtów; RFC 5649 dodaje dopełnienie dla kluczy o dowolnej długości.
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 i Salsa20 (surowe szyfry strumieniowe)
Szyfry strumieniowe stojące za powyższymi konstrukcjami AEAD, udostępnione bezpośrednio dla protokołów, które wymagają surowego szyfru. Żaden z nich nie uwierzytelnia: niemal zawsze to warianty AEAD z Poly1305 opisane wyżej na tej stronie są tym, czego naprawdę potrzebujesz. Salsa20 jest poprzednikiem ChaCha20, zachowanym dla zgodności z formatami z ery NaCl; do wszystkiego nowego wybieraj ChaCha20.
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;
Starsze: DES i NTLM
sgcCrypto_Legacy udostępnia również pojedynczy DES w trybie ECB oraz wyprowadzanie klucza NTLM v1 oparte na DES. DES ma efektywny klucz 56-bitowy, możliwy do złamania siłowo na współczesnym sprzęcie w kilka godzin; istnieje wyłącznie dla interoperacyjności z protokołami, które wymieniają go z nazwy (zazwyczaj jest to uwierzytelnianie NTLM). Nie wybieraj go do niczego nowego; zamiast tego stosuj AES.
function sgcDES_EncryptECB(const aKey, aData: TBytes): TBytes;
function sgcDES_DecryptECB(const aKey, aData: TBytes): TBytes;
function sgcDES_NTLM7to8(const aKey7: TBytes): TBytes;