WEB3DEV

Cover image for Hashing de senha com PBKDF2 com Rust usando Ring
Adriano P. Araujo
Adriano P. Araujo

Posted on

Hashing de senha com PBKDF2 com Rust usando Ring

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



Enter fullscreen mode Exit fullscreen mode

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]

)

Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

}

Enter fullscreen mode Exit fullscreen mode

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)