C# - Fazendo o Hash de Senhas

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:

  1. Gerar uma Salt Key
  2. Gerar o hash da senha

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. Como gerar uma chave salt
  2. Concatenar a senha com salt e gerar o hash
  3. Salvar o salt e o hash

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
    {
        private static RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider();

        static void Main(string[] args)
        {
            byte[] salt = new byte[16];
            rngCsp.GetBytes(salt);

            Console.WriteLine("Informe a senha");
            var senha = Console.ReadLine();

            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
    {
        private static RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider();

        static void Main(string[] args)
        {
            byte[] salt = new byte[16];
            rngCsp.GetBytes(salt);

            Console.WriteLine("Informe a senha");
            var senha = Console.ReadLine();

            var pbkdf2 = new Rfc2898DeriveBytes(senha, salt, 1000);

            byte[] hash = pbkdf2.GetBytes(20);
            byte[] hashBytes = new byte[36];

            Array.Copy(salt, 0, hashBytes, 0, 16);
            Array.Copy(hash, 0, hashBytes, 16, 20);

            string hashSenha = Convert.ToBase64String(hashBytes);

            Console.WriteLine($"\nHash da senha gerado : {hashSenha}");

            Console.ReadKey();
        }
    }

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:

 


José Carlos Macoratti