Deze vier functies lossen twee verschillende problemen op die gemakkelijk worden verward.
Een wachtwoord opslaan moet traag en geheugenintensief zijn, zodat het stelen van de database de wachtwoorden niet zomaar prijsgeeft. Gebruik eerst Argon2id, dan scrypt, en gebruik PBKDF2 alleen waar een standaard dit specifiek vereist.
Een sleutel afleiden van een reeds willekeurig, hoog-entropisch geheim, bijvoorbeeld de uitvoer van een X25519-sleuteluitwisseling, moet juist snel zijn: HKDF is dan het juiste gereedschap, en een trage wachtwoordhash toepassen op een geheim dat al willekeurig is, verspilt CPU-tijd zonder enig beveiligingsvoordeel.
Alle functies bevinden zich in sgcCrypto_KDF, sgcCrypto_Scrypt, sgcCrypto_Argon2 en sgcCrypto_HKDF.
PBKDF2
Rekt een wachtwoord uit door een HMAC vele malen te herhalen. Het kost alleen CPU-tijd, dus gespecialiseerde hardware (GPU's, FPGA's) valt het veel goedkoper aan dan het een legitieme server kost om één wachtwoord te controleren. Het is de zwakste van de drie wachtwoordhashes hier, behouden omdat sommige standaarden (WPA2, verscheidene PKCS-formaten) het nog steeds met naam vereisen.
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 is de kostenparameter voor geheugen/CPU: 16384 met r=8 heeft ongeveer 16 MB RAM per hash nodig, en dat is precies wat een grootschalige GPU-aanval duur maakt in plaats van alleen maar traag, omdat de aanvaller voor geheugen moet betalen, niet alleen voor rekenkernen.
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 won in 2015 de Password Hashing Competition en is momenteel de eerste keuze voor het opslaan van wachtwoorden. Argon2id is de variant om te gebruiken: hij combineert de gegevensafhankelijke adressering van Argon2d (sterk tegen GPU-kraken) met de gegevensonafhankelijke eerste doorgang van Argon2i (bestand tegen een zijkanaalaanvaller die geheugentoegangspatronen kan observeren). RFC 9106 beveelt minstens 19 MiB geheugen met 2 iteraties aan voor interactieve logins, of 64 MiB met 3 iteraties waar de extra kosten aanvaardbaar zijn.
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 en Argon2i zijn ook beschikbaar via het algemene toegangspunt voor aanroepers die een specifieke variant nodig hebben:
function sgcArgon2(aKind: TsgcArgon2Kind; const aPassword, aSalt, aSecret, aAssociated: TBytes;
aMemoryKB, aIterations, aParallelism, aTagLen: Integer): TBytes;
HKDF
HKDF splitst één goed geheim in zoveel onafhankelijke sleutels als een protocol nodig heeft. De parameter aInfo is wat ze onafhankelijk houdt: hetzelfde geheim met een andere info-string levert een niet-gerelateerde sleutel op, en dat is precies wat voorkomt dat een sleutel die voor het ene doel is afgeleid (bijvoorbeeld het versleutelen van een bericht) ook bruikbaar is voor een ander doel (bijvoorbeeld het authenticeren ervan).
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;
De extract- en expand-stappen achter sgcHKDF_SHA256 zijn ook afzonderlijk beschikbaar, voor protocollen die ze op verschillende momenten moeten uitvoeren:
function sgcHKDF_Extract_SHA256(const aSalt, aIKM: TBytes): TBytes;
function sgcHKDF_Expand_SHA256(const aPRK, aInfo: TBytes; aLen: Integer): TBytes;
KDF1, KDF2 en ANSI X9.63
Drie sleutelafleidingsfuncties op basis van een teller, uit ISO 18033-2 en IEEE 1363a, eveneens in sgcCrypto_KDF. Het zijn de functies die RSA-KEM, ECIES en de oudere ANSI-schema's met naam voorschrijven, wat de enige reden is om ernaar te grijpen. HKDF hierboven blijft de betere keuze voor iets nieuws, omdat het extractie scheidt van expansie in plaats van het geheim rechtstreeks in de uitvoer te hashen.
KDF1 en KDF2 verschillen op precies één punt, en dat is een echte valkuil voor interoperabiliteit: KDF1 begint zijn teller bij 0, KDF2 bij 1. De uitvoer is compleet verschillend, elke functie ziet er op zichzelf correct uit, en een specificatie vermeldt niet altijd expliciet welke van de twee wordt bedoeld, dus controleer de spec in plaats van de naam. X9.63 is KDF2 met een shared-info-string die na de teller wordt toegevoegd, wat de vorm is die ECIES en de oudere ECDH-TLS-suites voorschrijven.
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: the ECDH X coordinate plus the shared info the scheme defines }
oKey := sgcKDF_X963(oSharedSecret, oSharedInfo, 32, khSHA256);
end;