Hoje veremos como fazer o hash de senhas usando a linguagem C#. |
Algoritmos de hash são funções unilaterais. Eles transformam qualquer quantidade de dados em uma "impressão digital" de comprimento fixo que não pode ser revertida.
Se a entrada mudar mesmo que seja um pouquinho, o hash resultante será completamente diferente. Isso é ótimo para proteger senhas porque queremos armazená-las de uma forma que as proteja mesmo se a própria senha for comprometida.
Geralmente isso envolve duas etapas:
Uma função hash é uma função unilateral (você não pode transformar a entrada transformada de volta ao jeito que era) que transforma uma sequência de caracteres em uma série de comprimento fixo de caracteres e números.
Um salt é uma série de caracteres aleatórios que são anexados à string antes de aplicar uma função hash a ela. A razão para isso é evitar ataques de dicionário (ataques em que muitas senhas comuns são testadas)
A seguir veremos:
1- Gerando uma chave salt aleatória usando um CSPRNG
Um CSPRNG ou Cryptographically Secure Pseudorandom Number Generator é um algoritmo que produz uma sequência pseudo-aleatória de bytes. O que o torna criptograficamente seguro é que é muito difícil para alguém distinguir sua saída da verdadeira aleatoriedade. Cada senha terá seu próprio salt aleatório anexado a ela.
Para gerar o salt aleatório, usaremos a classe RNGCryptoServiceProvider(), cujo único trabalho é gerar números aleatórios. Vamos armazenar os gerados números em uma matriz de bytes. O que GetBytes faz é colocar os bytes no array fornecido:
using System;
using System.Security.Cryptography;
class Program { private static RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider(); static void Main(string[] args) { byte[] salt = new byte[16]; rngCsp.GetBytes(salt); Console.ReadKey();
}
}
|
2- Concatenando a senha com o salt e gerando o hash com a função de hash
Vamos usar o algoritmo de hash chamado PBKDF2 pois ele oferece a vantagem de ser lento por design. Estranho não é mesmo ?
Mas é mais
vantajoso para um algoritmo de criptografia de senha ser lento, porque quanto
mais lento, mais tempo leva para um invasor tentar usar a força bruta. A
lentidão do algoritmo determina quantas senhas um invasor pode tentar por
minuto.
Obviamente, é importante que o algoritmo não seja tão lento a ponto de incomodar
o usuário, por isso podemos configurar o quão rápido ou lento ele é.
A classe Rfc2989DeriveBytes será responsável pelo
hashing e o valor 10000 usado representa o número
de iterações que o algoritmo irá realizar (ele continuará fazendo o hash do
hash anterior este número de vezes. Isso é o que o torna mais lento por design).
terá seu próprio sa
using System; using System.Security.Cryptography;
class Program Console.WriteLine("Informe a senha"); var pbkdf2 = new Rfc2898DeriveBytes(senha, salt, 1000); Console.ReadKey(); |
Agora vamos armazenar o hash da senha gerado usando, mais uma vez, uma matriz de bytes, vamos adicionar o salt na frente da senha com hash (isso é chamado de prefixação).
Obs: Adicionar o sal na frente do hash é simplesmente preferência, ele pode ser adicionado em qualquer lugar da string, tecnicamente falando.
O tamanho do array
será de 36 bytes, porque tanto o hash quanto o salt têm comprimento fixo:
o hash tem 20 bytes e o salt 16.
Depois de adicionar a senha com hash e seu salt ao array de Bytes, devemos
convertê-la em uma string e poderemos exibir o seu valor e também estamos
prontos para armazenar o seu valor em qualquer meio de armazenamento que
desejarmos.
using System; using System.Security.Cryptography;
class Program Console.WriteLine("Informe a senha"); var pbkdf2 = new Rfc2898DeriveBytes(senha, salt, 1000);
byte[] hash = pbkdf2.GetBytes(20); |
Executando o projeto e informando uma senha teremos o resultado abaixo:
E estamos conversados...
"Quando eu disse: O meu pé vacila; a
tua benignidade, Senhor, me susteve."
Salmos 94:18
Referências:
ADO .NET - Acesso Assíncrono aos dados -
C# - Programação Funcional - Exemplos
C# - Coleções Imutáveis - Macoratti
C# 9.0 - Apresentando Records
C# - Os 10 Erros mais comuns dos iniciantes
C# - Otimizando o código