Diese vier Funktionen lösen zwei unterschiedliche Probleme, die leicht verwechselt werden.
Das Speichern eines Passworts muss langsam und speicherintensiv sein, damit der Diebstahl der Datenbank nicht die Passwörter preisgibt. Verwenden Sie zuerst Argon2id, dann scrypt, und PBKDF2 nur dort, wo ein Standard es ausdrücklich verlangt.
Das Ableiten eines Schlüssels aus einem bereits zufälligen, hochentropischen Geheimnis, zum Beispiel der Ausgabe eines X25519-Key-Agreements, muss schnell sein: HKDF ist das richtige Werkzeug, und einen langsamen Passwort-Hash auf ein bereits zufälliges Geheimnis anzuwenden, verschwendet CPU-Zeit ohne jeden Sicherheitsgewinn.
Alle Funktionen befinden sich in sgcCrypto_KDF, sgcCrypto_Scrypt, sgcCrypto_Argon2 und sgcCrypto_HKDF.
PBKDF2
Streckt ein Passwort, indem ein HMAC viele Male wiederholt wird. Es kostet nur CPU-Zeit, sodass spezielle Hardware (GPUs, FPGAs) es weit günstiger angreift, als es einen legitimen Server kostet, ein Passwort zu prüfen. Es ist der schwächste der drei hier vorgestellten Passwort-Hashes, wird aber beibehalten, da manche Standards (WPA2, mehrere PKCS-Formate) es weiterhin namentlich verlangen.
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 ist der Speicher-/CPU-Kostenparameter: 16384 mit r=8 benötigt etwa 16 MB RAM pro Hash, was einen groß angelegten GPU-Angriff teuer statt nur langsam macht, weil der Angreifer für Speicher bezahlen muss, nicht nur für Rechenkerne.
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 gewann 2015 die Password Hashing Competition und ist die aktuelle erste Wahl zur Speicherung von Passwörtern. Argon2id ist die zu verwendende Variante: Sie kombiniert die datenabhängige Adressierung von Argon2d (stark gegen GPU-Cracking) mit dem datenunabhängigen ersten Durchgang von Argon2i (widerstandsfähig gegen einen Seitenkanalangreifer, der Speicherzugriffsmuster beobachten kann). RFC 9106 empfiehlt mindestens 19 MiB Speicher mit 2 Iterationen für interaktive Anmeldungen, oder 64 MiB mit 3 Iterationen, wo die zusätzlichen Kosten akzeptabel sind.
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 und Argon2i sind ebenfalls über den allgemeinen Einstiegspunkt verfügbar, für Aufrufer, die eine bestimmte Variante benötigen:
function sgcArgon2(aKind: TsgcArgon2Kind; const aPassword, aSalt, aSecret, aAssociated: TBytes;
aMemoryKB, aIterations, aParallelism, aTagLen: Integer): TBytes;
HKDF
HKDF teilt ein gutes Geheimnis in so viele unabhängige Schlüssel auf, wie ein Protokoll benötigt. Der Parameter aInfo sorgt für diese Unabhängigkeit: Dasselbe Geheimnis mit einem anderen Info-String erzeugt einen nicht verwandten Schlüssel, was genau das ist, was verhindert, dass ein für einen Zweck abgeleiteter Schlüssel (etwa das Verschlüsseln einer Nachricht) auch für einen anderen (etwa deren Authentifizierung) verwendbar ist.
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;
Die Extract- und Expand-Schritte hinter sgcHKDF_SHA256 sind auch separat verfügbar, für Protokolle, die sie zu unterschiedlichen Zeitpunkten ausführen müssen:
function sgcHKDF_Extract_SHA256(const aSalt, aIKM: TBytes): TBytes;
function sgcHKDF_Expand_SHA256(const aPRK, aInfo: TBytes; aLen: Integer): TBytes;
KDF1, KDF2 und ANSI X9.63
Drei Zähler-basierte Schlüsselableitungsfunktionen aus ISO 18033-2 und IEEE 1363a, ebenfalls in sgcCrypto_KDF. Es sind diejenigen, die RSA-KEM, ECIES und die älteren ANSI-Schemata namentlich vorschreiben, was der einzige Grund ist, zu ihnen zu greifen. HKDF weiter oben bleibt die bessere Wahl für alles Neue, weil es die Extraktion von der Expansion trennt, statt das Geheimnis direkt in die Ausgabe zu hashen.
KDF1 und KDF2 unterscheiden sich in genau einem Detail, und es ist eine echte Interoperabilitätsfalle: KDF1 beginnt seinen Zähler bei 0, KDF2 bei 1. Die Ausgaben sind vollständig unterschiedlich, jede Funktion sieht für sich genommen korrekt aus, und eine Spezifikation buchstabiert nicht immer aus, welche davon gemeint ist, prüfen Sie daher die Spezifikation statt des Namens. X9.63 ist KDF2 mit einer Shared-Info-Zeichenkette, die nach dem Zähler angehängt wird, die Form, die ECIES und die älteren ECDH-TLS-Suiten vorschreiben.
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;