A biblioteca de criptografia Ring fornece uma implementação de PBKDF2, uma função de derivação de chave padronizada que pode ser usada para exibir senhas de hash com segurança. Usando o módulo ring::pbkdf2
, podemos derivar um hash de senha e também verificar se um hash corresponde ou não a uma determinada senha.
Introdução ao PBKDF2
Função de Derivação de Chave Baseada em Senha 2 - Password Based Key Derivation Function 2 no original, (PBKDF2), é especificada na Seção 5.2 da RFC 2898. A PBKDF2 não é um algoritmo muito moderno, mas ainda é considerado seguro, desde que seja usado corretamente com parâmetros cuidadosamente escolhidos. A PBKDF2 aplica uma função pseudo-aleatória (como um HMAC) à senha de entrada e ao valor do salt e repete o processo várias vezes para produzir uma chave derivada que pode ser usada como chave criptográfica em operações subsequentes. O trabalho computacional adicional das inúmeras iterações dificulta muito a quebra de senha. A PBKDF2 também é recomendada pelo NIST e possui implementações em conformidade com o FIPS-140.
O documento RFC define a função PBKDF2 da seguinte maneira:
PBKDF2 (P, S, c, dkLen) -> DK
Estes são os parâmetros da função PBKDF2:
Opções: | PRF | função pseudoaleatória subjacente (hLen denota o comprimento em octetos da saída da função) |
Entrada: | P | Senha, uma string octeto |
S | salt, uma string octeto | |
c | contagem de iteração, um número inteiro positivo | |
dkLen | comprimento pretendido em octetos do derivado chave, um número inteiro positivo, no máximo (2^32 - 1) * hLen | |
Saída: | DK | chave derivada, uma string dkLen-octet |
Diretrizes de uso da PBKDF2
Senhas fracas podem ser vulneráveis a ataques de força bruta, portanto, os usuários devem definir senhas fortes que sejam longas e contenham entropia suficiente.
O uso de um salt com baixa entropia pode tornar a tecla derivada do hash (vulnerável a ataques de pré-computação usando tabelas arco-íris. O parâmetro salt melhora a segurança da PBKDF2 alterando a saída do hash para qualquer senha de entrada especificada. O salt deve ser imprevisível e ser selecionado para ser único para cada usuário e banco de dados (essa parte às vezes é chamada de pimenta).
Usar um número baixo de iterações também pode tornar o hash vulnerável a ataques de força bruta. Quanto maior o número de iterações selecionadas, mais tempo leva para executar a função PBKDF2, e portanto, requer mais trabalho para decifrar com êxito as senhas. O número de iterações que você deve selecionar é um alvo em movimento e aumenta com o tempo à medida que a energia da CPU aumenta. OWASP fornece um bom recurso nos parâmetros recomendados aqui. Atualmente, para PBKDF2-HMAC-SHA256, a contagem de iterações recomendadas é de 600000 e para PBKDF2-HMAC-SHA512 é de 210000.
O comprimento da chave de saída precisa ser longo o suficiente para evitar ataques de força bruta e, portanto, recomenda-se um comprimento mínimo de chave de pelo menos 128 bits. Geralmente, o comprimento da saída deve ser definido como igual ao comprimento da função de hash escolhida.
Quando a PBKDF2 é usada com um HMAC, pode ser necessário um pré-hashing manual da senha em alguns casos para evitar certas vulnerabilidades de negação de serviço. Isso pode ocorrer quando a senha de entrada é maior que o tamanho do bloco da função de hash . Veja aqui para mais detalhes.
Resumo dos tipos
O módulo ring::pbkdf2
contém os seguintes tipos e funções:
algoritmo de estrutura – O tipo de algoritmo PBKDF2. Os algoritmos PBKDF2_HMAC_SHA1
, PBKDF2_HMAC_SHA256
, PBKDF2_HMAC_SHA384
e PBKDF2_HMAC_SHA512
são suportados.
fn derive – O algoritmo PBKDF2 usado para derivar um hash de senha de uma determinada senha. Passamos no algoritmo escolhido o número de iterações a serem usadas, um salt e um segredo(senha). O valor do hash é armazenado na matriz de bytes fornecida, out
. O tamanho do array out
determina o tamanho do valor do hash retornado.
pub fn derive(
algorithm: Algorithm,
iterations: NonZeroU32,
salt: &[u8],
secret: &[u8],
out: &mut [u8]
)
fn verify– Verifica se um determinado segredo(senha) corresponde a um hash derivado anteriormente. Passamos no algoritmo, o número de iterações, o salt, o segredo a ser verificado e o hash que foi derivado anteriormente usando a função pbkdf2::derive
.
pub fn verify(
algorithm: Algorithm,
iterations: NonZeroU32,
salt: &[u8],
secret: &[u8],
previously_derived: &[u8]
) -> Result<(), Unspecified>
Importações Rust
Vamos começar importando os tipos necessários para o nosso projeto.
use std::num::NonZeroU32;
use ring::digest::SHA256_OUTPUT_LEN;
use ring::digest::SHA512_OUTPUT_LEN;
use ring::pbkdf2;
use ring::pbkdf2::Algorithm;
use ring::pbkdf2::PBKDF2_HMAC_SHA1;
use ring::pbkdf2::PBKDF2_HMAC_SHA256;
use ring::pbkdf2::PBKDF2_HMAC_SHA384;
use ring::pbkdf2::PBKDF2_HMAC_SHA512;
Preparar iterações, salt e o segredo
No código abaixo, declaramos que os parâmetros devem ser passados para a função pbkdf2::derive
. Neste exemplo, estamos usando o algoritmo PBKDF2_HMAC_SHA256
e as iterações são definidas como 600000, conforme recomendado pela OWASP. A variável interations é do tipo NonZeroU32
na biblioteca padrão Rust, que impede a definição de uma contagem de iteração zero.
const PBKDF2_HMAC_SHA256_ITERATIONS: u32 = 600_000; // number recommended by OWASP for PBKDF2 with SHA256
let iterations = NonZeroU32::new(PBKDF2_HMAC_SHA256_ITERATIONS).unwrap();
let salt = b"random salt"; // isso deve ser gerado aleatoriamente, usando algum componente específico do usuário e componente específico do banco de dados
let secret = b"strong password"; // seleciona uma senha forte
println!("Secret/password value: {}", hex::encode(secret)); // não imprima isso em produção
Derive o Hash da Senha e armazenamento
Em seguida, chamamos a função pbkdf2::derive
com nossos parâmetros escolhidos e armazenamos o hash em uma matriz de bytes. Neste exemplo, o tamanho do hash de saída terá o mesmo comprimento que a saída do SHA256, mas poderíamos prolongar isso, se necessário. O comprimento é definido escolhendo o tamanho da matriz password_hash
.
let mut password_hash = [0u8; SHA256_OUTPUT_LEN]; //inicializa com zeros
pbkdf2::derive(PBKDF2_HMAC_SHA256, iterations, salt, secret, &mut password_hash);
println!("Password hash: {}", hex::encode(password_hash)); // não imprima isso em produção
Verifique se uma senha corresponde à senha do hash
Usando os mesmos parâmetros de entrada acima, podemos chamar a função pbkdf2::verify
para verificar se uma determinada senha corresponde ao hash derivado anteriormente. Se a senha corresponder ao hash, a função de verificação retornará o tipo Result::Ok
contendo um ()
, caso contrário, retornará umResult::Err
contendo error::Unspecified
( definido no módulo Ring error
).
pbkdf2::verify(PBKDF2_HMAC_SHA256, iterations, salt, secret, &password_hash).unwrap(); // caso de sucesso
pbkdf2::verify(PBKDF2_HMAC_SHA256, iterations, salt, "wrong password".as_bytes(), &password_hash).unwrap(); // caso de falha
Código de amostra completo
Aqui está a amostra completa do código para referência. O exemplo do cenário 2 abaixo usa PBKDF2_HMAC_SHA512
com a contagem de iteração recomendada de 210000 e usa um tamanho de hash de saída igual ao tamanho do SHA512.
use std::num::NonZeroU32;
use ring::digest::SHA256_OUTPUT_LEN;
use ring::digest::SHA512_OUTPUT_LEN;
use ring::pbkdf2;
use ring::pbkdf2::Algorithm;
use ring::pbkdf2::PBKDF2_HMAC_SHA1;
use ring::pbkdf2::PBKDF2_HMAC_SHA256;
use ring::pbkdf2::PBKDF2_HMAC_SHA384;
use ring::pbkdf2::PBKDF2_HMAC_SHA512;
fn main() {
// cenário 1 - PBKDF2_HMAC_SHA256
const PBKDF2_HMAC_SHA256_ITERATIONS: u32 = 600_000; //número recomendado pela OWASP para PBKDF2 com SHA256
//Prepara as interações, salt e o segredo
let iterations = NonZeroU32::new(PBKDF2_HMAC_SHA256_ITERATIONS).unwrap();
let salt = b"random salt"; //isso deve ser gerado aleatoriamente, usando algum componente específico do usuário e componente específico do banco de dados
let secret = b"strong password"; // seleciona uma senha forte
println!("Secret/password value: {}", hex::encode(secret)); // não imprima isso em produção
// Deriva o _hash_ da senha e o armazena
let mut password_hash = [0u8; SHA256_OUTPUT_LEN]; // inicializa com zeros
pbkdf2::derive(PBKDF2_HMAC_SHA256, iterations, salt, secret, &mut password_hash);
println!("Password hash: {}", hex::encode(password_hash)); // não imprima isso em produção
// Verifica se uma senha corresponde ou não ao _hash_ de senha armazenada
pbkdf2::verify(PBKDF2_HMAC_SHA256, iterations, salt, secret, &password_hash).unwrap(); // caso de sucesso //pbkdf2::verify(PBKDF2_HMAC_SHA256, iterations, salt, "wrong password".as_bytes(), &password_hash).unwrap(); // caso de falha
// cenário 2 - PBKDF2_HMAC_SHA512
const PBKDF2_HMAC_SHA512_ITERATIONS: u32 = 210_000; // número recomendado pela OWASP para PBKDF2 com SHA512
// Prepara as interações, salta e o segredo
let iterations = NonZeroU32::new(PBKDF2_HMAC_SHA512_ITERATIONS).unwrap();
let salt = b"random salt"; //isso deve ser gerado aleatoriamente, usando algum componente específico do usuário e componente específico do banco de dados
let secret = b"strong password"; // seleciona uma senha forte
println!("Secret/password value: {}", hex::encode(secret)); // não imprima isso em produção
// Deriva o _hash_ da senha e o armazena
let mut password_hash = [0u8; SHA512_OUTPUT_LEN]; /inicializa com zeros
pbkdf2::derive(PBKDF2_HMAC_SHA512, iterations, salt, secret, &mut password_hash);
println!("Password hash: {}", hex::encode(password_hash)); // não imprima isso em produção
// Verifica se uma senha corresponde ou não ao _hash_ de senha armazenada
pbkdf2::verify(PBKDF2_HMAC_SHA512, iterations, salt, secret, &password_hash).unwrap(); //caso de sucesso
//pbkdf2::verify(PBKDF2_HMAC_SHA512, iterations, salt, "wrong password".as_bytes(), &password_hash).unwrap(); // caso de falha
}
Conclusão
Nesta postagem, introduzimos o algoritmo PBKDF2, explicamos como ele funciona e descrevemos como escolher corretamente os parâmetros para a função PBKDF2. Explicamos então como usar o módulo Ring pbkdf2
para derivar um hash de senha usando a função pbkdf2::derive
e verificar se uma senha corresponde a um hash de senha usando a funçãopbkdf2::verify
. Os exemplos mostram dois cenários usando os algoritmos PBKDF2 mais utilizados; PBKDF2_HMAC_SHA256
e PBKDF2_HMAC_SHA512
.
Este artigo foi escrito por Web3 Developer e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.
Latest comments (0)