Estas quatro funções resolvem dois problemas diferentes que são fáceis de confundir.
Armazenar uma senha precisa ser lento e exigente em memória, de modo que roubar o banco de dados não entregue as senhas de bandeja. Use Argon2id primeiro, scrypt em segundo lugar, e PBKDF2 apenas onde um padrão exigir especificamente isso.
Derivar uma chave a partir de um segredo já aleatório e de alta entropia, por exemplo a saída de um acordo de chave X25519, precisa ser rápido: HKDF é a ferramenta certa, e apontar um hash de senha lento para um segredo que já é aleatório desperdiça tempo de CPU sem nenhum benefício de segurança.
Todas as funções vivem em sgcCrypto_KDF, sgcCrypto_Scrypt, sgcCrypto_Argon2 e sgcCrypto_HKDF.
PBKDF2
Estica uma senha repetindo um HMAC muitas vezes. Ele só custa tempo de CPU, de modo que hardware customizado (GPUs, FPGAs) o ataca de forma muito mais barata do que custa a um servidor legítimo verificar uma senha. É o mais fraco dos três hashes de senha aqui, mantido porque alguns padrões (WPA2, vários formatos PKCS) ainda o exigem pelo nome.
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 é o parâmetro de custo de memória/CPU: 16384 com r=8 precisa de aproximadamente 16 MB de RAM por hash, o que é o que torna um ataque de GPU em larga escala caro em vez de apenas lento, porque o atacante tem que pagar por memória, não apenas por núcleos de computação.
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
O Argon2 venceu a Password Hashing Competition de 2015 e é a primeira escolha atual para armazenar senhas. Argon2id é a variante a ser utilizada: ela mistura o endereçamento dependente de dados do Argon2d (forte contra cracking por GPU) com a primeira passagem independente de dados do Argon2i (resistente a um atacante de canal lateral que consiga observar padrões de acesso à memória). A RFC 9106 recomenda pelo menos 19 MiB de memória com 2 iterações para logins interativos, ou 64 MiB com 3 iterações onde o custo extra for aceitável.
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 e Argon2i também estão disponíveis através do ponto de entrada geral, para chamadores que precisam de uma variante específica:
function sgcArgon2(aKind: TsgcArgon2Kind; const aPassword, aSalt, aSecret, aAssociated: TBytes;
aMemoryKB, aIterations, aParallelism, aTagLen: Integer): TBytes;
HKDF
O HKDF divide um bom segredo em quantas chaves independentes um protocolo precisar. O parâmetro aInfo é o que as mantém independentes: o mesmo segredo com uma string de info diferente produz uma chave sem relação, o que é exatamente o que impede que uma chave derivada para um propósito (digamos, criptografar uma mensagem) também possa ser usada para outro (digamos, autenticar uma).
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;
As etapas de extração e expansão por trás de sgcHKDF_SHA256 também estão disponíveis separadamente, para protocolos que precisam executá-las em momentos diferentes:
function sgcHKDF_Extract_SHA256(const aSalt, aIKM: TBytes): TBytes;
function sgcHKDF_Expand_SHA256(const aPRK, aInfo: TBytes; aLen: Integer): TBytes;
KDF1, KDF2 e ANSI X9.63
Três funções de derivação de chave em modo contador, da ISO 18033-2 e da IEEE 1363a, também em sgcCrypto_KDF. São as que o RSA-KEM, o ECIES e os esquemas ANSI mais antigos especificam pelo nome, e essa é a única razão para recorrer a elas. O HKDF acima continua sendo a melhor escolha para qualquer coisa nova, porque ele separa a extração da expansão em vez de fazer hash do segredo diretamente na saída.
O KDF1 e o KDF2 diferem em exatamente um detalhe, e é uma armadilha real de interoperabilidade: o KDF1 começa seu contador em 0, o KDF2 em 1. As saídas são completamente diferentes, cada função parece correta isoladamente, e uma especificação nem sempre deixa claro qual das duas ela quer dizer, portanto verifique a especificação em vez do nome. O X9.63 é o KDF2 com uma string de informação compartilhada (shared-info) anexada após o contador, que é a forma que o ECIES e os antigos conjuntos de cifras ECDH do TLS especificam.
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;