Ces quatre fonctions résolvent deux problèmes différents qu'il est facile de confondre.
Stocker un mot de passe doit être lent et gourmand en mémoire, afin que le vol de la base de données ne livre pas les mots de passe. Utilisez Argon2id en premier, scrypt en second, et PBKDF2 uniquement lorsqu'une norme l'exige spécifiquement.
Dériver une clé à partir d'un secret déjà aléatoire et à forte entropie, par exemple la sortie d'un accord de clé X25519, doit être rapide : HKDF est l'outil approprié, et diriger un hachage de mot de passe lent vers un secret déjà aléatoire gaspille du temps processeur sans aucun bénéfice de sécurité.
Toutes les fonctions résident dans sgcCrypto_KDF, sgcCrypto_Scrypt, sgcCrypto_Argon2 et sgcCrypto_HKDF.
PBKDF2
Étire un mot de passe en répétant un HMAC de nombreuses fois. Cela ne coûte que du temps processeur, si bien qu'un matériel dédié (GPU, FPGA) l'attaque bien plus économiquement que ce qu'il en coûte à un serveur légitime pour vérifier un mot de passe. C'est le plus faible des trois hachages de mots de passe présentés ici, conservé car certaines normes (WPA2, plusieurs formats PKCS) l'exigent encore par son nom.
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 est le paramètre de coût mémoire/processeur : 16384 avec r=8 nécessite environ 16 Mo de RAM par hachage, ce qui rend une attaque GPU à grande échelle coûteuse plutôt que simplement lente, car l'attaquant doit payer pour la mémoire, pas seulement pour les cœurs de calcul.
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 a remporté la Password Hashing Competition de 2015 et est actuellement le premier choix pour stocker des mots de passe. Argon2id est la variante à utiliser : elle combine l'adressage dépendant des données d'Argon2d (résistant au cassage par GPU) avec la première passe indépendante des données d'Argon2i (résistant à un attaquant par canal auxiliaire capable d'observer les motifs d'accès mémoire). La RFC 9106 recommande au moins 19 Mio de mémoire avec 2 itérations pour les connexions interactives, ou 64 Mio avec 3 itérations lorsque le coût supplémentaire est acceptable.
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 et Argon2i sont également disponibles via le point d'entrée général pour les appelants qui ont besoin d'une variante spécifique :
function sgcArgon2(aKind: TsgcArgon2Kind; const aPassword, aSalt, aSecret, aAssociated: TBytes;
aMemoryKB, aIterations, aParallelism, aTagLen: Integer): TBytes;
HKDF
HKDF divise un bon secret en autant de clés indépendantes que le nécessite un protocole. Le paramètre aInfo est ce qui les maintient indépendantes : le même secret avec une chaîne d'information différente produit une clé sans rapport, ce qui empêche précisément qu'une clé dérivée pour un usage (par exemple, chiffrer un message) ne soit également utilisable pour un autre (par exemple, en authentifier un).
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;
Les étapes d'extraction et d'expansion derrière sgcHKDF_SHA256 sont également disponibles séparément, pour les protocoles qui doivent les exécuter à des moments différents :
function sgcHKDF_Extract_SHA256(const aSalt, aIKM: TBytes): TBytes;
function sgcHKDF_Expand_SHA256(const aPRK, aInfo: TBytes; aLen: Integer): TBytes;
KDF1, KDF2 et ANSI X9.63
Trois fonctions de dérivation de clé en mode compteur issues de l'ISO 18033-2 et de l'IEEE 1363a, également dans sgcCrypto_KDF. Ce sont celles que nomment RSA-KEM, ECIES et les anciens schémas ANSI, ce qui est la seule raison d'y recourir. HKDF, ci-dessus, reste le meilleur choix pour tout ce qui est nouveau, car il sépare l'extraction de l'expansion au lieu de hacher directement le secret dans la sortie.
KDF1 et KDF2 ne diffèrent que sur un seul point, et c'est un véritable piège d'interopérabilité : KDF1 démarre son compteur à 0, KDF2 à 1. Les sorties sont totalement différentes, chaque fonction paraît correcte prise isolément, et une spécification ne précise pas toujours laquelle des deux elle vise, vérifiez donc la spécification plutôt que le nom. X9.63 est KDF2 avec une chaîne shared-info ajoutée après le compteur, la forme que spécifient ECIES et les anciennes suites TLS ECDH.
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;