Te cztery funkcje rozwiązują dwa różne problemy, które łatwo ze sobą pomylić.
Przechowywanie hasła musi być powolne i wymagające dużo pamięci, tak aby kradzież bazy danych nie wydała haseł. Używaj w pierwszej kolejności Argon2id, w drugiej scrypt, a PBKDF2 tylko tam, gdzie konkretny standard tego wymaga.
Wyprowadzanie klucza z już losowego, wysokoentropijnego sekretu, na przykład wyniku uzgodnienia klucza X25519, musi być szybkie: HKDF jest tu właściwym narzędziem, a kierowanie powolnego skrótu hasła na sekret, który już jest losowy, marnuje czas CPU bez żadnej korzyści dla bezpieczeństwa.
Wszystkie funkcje znajdują się w jednostkach sgcCrypto_KDF, sgcCrypto_Scrypt, sgcCrypto_Argon2 i sgcCrypto_HKDF.
PBKDF2
Rozciąga hasło, wielokrotnie powtarzając HMAC. Kosztuje wyłącznie czas CPU, więc niestandardowy sprzęt (GPU, FPGA) atakuje go znacznie taniej, niż kosztuje legalny serwer sprawdzenie jednego hasła. Jest najsłabszym z trzech opisanych tu skrótów haseł, zachowanym dlatego, że niektóre standardy (WPA2, kilka formatów PKCS) nadal wymagają go z nazwy.
function sgcPBKDF2_SHA1(const aPassword, aSalt: TBytes; aIterations, aDKLen: Integer): TBytes;
function sgcPBKDF2_SHA256(const aPassword, aSalt: TBytes; aIterations, aDKLen: Integer): TBytes;
function sgcPBKDF2_SHA384(const aPassword, aSalt: TBytes; aIterations, aDKLen: Integer): TBytes;
function sgcPBKDF2_SHA512(const aPassword, aSalt: TBytes; aIterations, aDKLen: Integer): TBytes;
scrypt
N to parametr kosztu pamięci/CPU: 16384 przy r=8 wymaga w przybliżeniu 16 MB pamięci RAM na jeden skrót, co sprawia, że atak GPU na dużą skalę jest kosztowny, a nie tylko powolny, ponieważ atakujący musi zapłacić za pamięć, a nie tylko za rdzenie obliczeniowe.
function sgcScrypt(const aPassword, aSalt: TBytes; aN, aR, aP, aDKLen: Integer): TBytes;
var
oHash: TBytes;
begin
oHash := sgcScrypt(TBytes(oPassword), oSalt, 16384, 8, 1, 32);
end;
Argon2
Argon2 wygrał konkurs Password Hashing Competition w 2015 roku i jest obecnie pierwszym wyborem do przechowywania haseł. Argon2id to wariant, którego należy używać: łączy adresowanie zależne od danych z Argon2d (odporne na łamanie za pomocą GPU) z niezależnym od danych pierwszym przebiegiem z Argon2i (odporne na atakującego wykorzystującego kanał boczny, który może obserwować wzorce dostępu do pamięci). RFC 9106 zaleca co najmniej 19 MiB pamięci przy 2 iteracjach dla logowań interaktywnych albo 64 MiB przy 3 iteracjach tam, gdzie dodatkowy koszt jest akceptowalny.
function sgcArgon2id(const aPassword, aSalt: TBytes; aMemoryKB, aIterations, aParallelism, aTagLen: Integer): TBytes;
var
oStored, oCandidate: TBytes;
begin
{ at signup }
oStored := sgcArgon2id(TBytes(oPassword), oSalt, 19456, 2, 1, 32);
{ at login }
oCandidate := sgcArgon2id(TBytes(oTypedPassword), oSalt, 19456, 2, 1, 32);
if sgcConstantTimeEquals(oStored, oCandidate) then
{ password accepted };
end;
Argon2d i Argon2i są również dostępne przez ogólny punkt wejścia dla wywołujących, którzy potrzebują konkretnego wariantu:
function sgcArgon2(aKind: TsgcArgon2Kind; const aPassword, aSalt, aSecret, aAssociated: TBytes;
aMemoryKB, aIterations, aParallelism, aTagLen: Integer): TBytes;
HKDF
HKDF dzieli jeden dobry sekret na tyle niezależnych kluczy, ile potrzebuje protokół. Parametr aInfo jest tym, co utrzymuje ich niezależność: ten sam sekret z innym ciągiem info daje niepowiązany klucz, co dokładnie uniemożliwia, aby klucz wyprowadzony do jednego celu (powiedzmy, szyfrowania wiadomości) był też przydatny do innego (powiedzmy, uwierzytelniania tej wiadomości).
function sgcHKDF_SHA256(const aIKM, aSalt, aInfo: TBytes; aLen: Integer): TBytes;
function sgcHKDF_SHA384(const aIKM, aSalt, aInfo: TBytes; aLen: Integer): TBytes;
var
oSharedSecret, oSalt, oClientKey, oServerKey: TBytes;
begin
oClientKey := sgcHKDF_SHA256(oSharedSecret, oSalt, TBytes(AnsiString('client write key')), 32);
oServerKey := sgcHKDF_SHA256(oSharedSecret, oSalt, TBytes(AnsiString('server write key')), 32);
end;
Kroki extract i expand, na których opiera się sgcHKDF_SHA256, są również dostępne osobno, dla protokołów, które muszą je wykonywać w różnych momentach:
function sgcHKDF_Extract_SHA256(const aSalt, aIKM: TBytes): TBytes;
function sgcHKDF_Expand_SHA256(const aPRK, aInfo: TBytes; aLen: Integer): TBytes;
KDF1, KDF2 i ANSI X9.63
Trzy funkcje wyprowadzania klucza w trybie licznikowym z ISO 18033-2 i IEEE 1363a, również w sgcCrypto_KDF. To te, które wymieniają z nazwy RSA-KEM, ECIES i starsze schematy ANSI, i to jedyny powód, by po nie sięgać. HKDF powyżej pozostaje lepszym wyborem do wszystkiego nowego, ponieważ oddziela ekstrakcję od rozwinięcia, zamiast haszować sekret wprost na wyjście.
KDF1 i KDF2 różnią się dokładnie jednym szczegółem, i jest to prawdziwa pułapka interoperacyjności: KDF1 zaczyna licznik od 0, KDF2 od 1. Wyniki są zupełnie różne, każda z funkcji wygląda poprawnie w oderwaniu od drugiej, a specyfikacja nie zawsze precyzuje, o którą z nich chodzi, więc sprawdzaj specyfikację, a nie nazwę. X9.63 to KDF2 z ciągiem shared-info doklejonym po liczniku, w postaci, jaką określają ECIES i starsze zestawy szyfrów ECDH w TLS.
function sgcKDF1(const aSeed: TBytes; aLen: Integer; aHash: TsgcKDFHash): TBytes;
function sgcKDF2(const aSeed: TBytes; aLen: Integer; aHash: TsgcKDFHash): TBytes;
function sgcKDF_X963(const aSharedSecret, aSharedInfo: TBytes; aLen: Integer; aHash: TsgcKDFHash): TBytes;
var
oSharedSecret, oSharedInfo, oKey: TBytes;
begin
{ ECIES: współrzędna X z ECDH plus shared info zdefiniowane przez schemat }
oKey := sgcKDF_X963(oSharedSecret, oSharedInfo, 32, khSHA256);
end;