A segurança das informações é fundamental no desenvolvimento de aplicações. Entre os dados mais sensíveis, as senhas se destacam, pois são usadas para autenticar usuários e proteger recursos restritos. Armazenar senhas em texto simples representa um grande risco, pois, se comprometidas, podem expor credenciais valiosas. Para evitar isso, a criptografia de senhas tornou-se uma prática essencial, garantindo a proteção das informações de forma eficaz e segura. Neste artigo, veremos como implementar a criptografia de senhas em C#, utilizando técnicas modernas e recomendadas.

Por que senhas devem ser criptografadas (ou melhor, "hashadas")

Como falado anteriormente, armazenar senhas em texto simples representa um grande risco à segurança. Se um banco de dados for comprometido, os invasores terão acesso direto às credenciais dos usuários. Em vez de armazenar senhas em texto simples, a prática recomendada é utilizar hashing, que transforma a senha em uma string irreversível, tornando a recuperação impossível em caso de vazamento.

Sem o uso de hashing, senhas ficam vulneráveis a ataques de força bruta e ataques de dicionário, onde os invasores tentam todas as combinações possíveis ou utilizam listas comuns de senhas. O uso de algoritmos de hashing fortes e salt aumenta a segurança, dificultando a quebra da senha, garantindo maior proteção contra vazamentos e ataques.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.

O que é salt e por que usá-lo

Salt é um valor aleatório adicionado à senha antes de aplicar o algoritmo de hash. Seu principal objetivo é garantir que senhas idênticas tenham hashes diferentes, dificultando ataques como rainbow table, que são baseados em tabelas pré computadas de hashes comuns. Ao adicionar salt, mesmo que dois usuários escolham a mesma senha, seus hashes serão únicos, aumentando a segurança do armazenamento de senhas.

Hash vs criptografia

A diferença fundamental entre hash e criptografia está no fato de que a criptografia é um processo reversível, enquanto o hash é irreversível.

A criptografia transforma dados em uma forma que pode ser revertida, ou seja, os dados criptografados podem ser descriptografados usando uma chave específica. Isso é útil quando precisamos recuperar a informação original, como em comunicações seguras. No entanto, quando aplicada a senhas, a criptografia pode apresentar riscos, pois, se a chave de descriptografia for comprometida, as senhas podem ser recuperadas facilmente.

Já o hashing gera um valor fixo e único a partir da senha, mas sem a possibilidade de reverter esse processo. Isso significa que, mesmo que o hash da senha seja exposto, não é possível obter a senha original a partir dele. O uso de algoritmos de hash fortes, juntamente com técnicas como salt, torna o processo muito mais seguro, dificultando ataques como força bruta ou dicionário.

Por essas razões, o hashing é a abordagem mais segura para senhas, pois não há risco de reversão e a integridade das senhas permanece protegida mesmo em caso de vazamento dos dados hashados.

Usando PBKDF2, BCrypt ou Argon2

Para garantir uma boa segurança ao armazenar senhas, é essencial usar algoritmos de hashing robustos, juntamente com técnicas como salt para proteger contra ataques de rainbow table. Vamos explorar alguns dos algoritmos mais comuns e como implementá-los em C#.

PBKDF2

O  PBKDF2 (Password-Based Key Derivation Function 2) é uma função de derivação de chave projetada especificamente para fortalecer senhas através de várias iterações e a adição de salt. Isso torna o processo de hashing muito mais demorado e seguro contra ataques de força bruta e rainbow table.

				
					public static string HashPassword(string password, string salt)
    {
        
            byte[] saltBytes = Encoding.UTF8.GetBytes(salt);

            var pbkdf2 = new Rfc2898DeriveBytes(password, saltBytes, 10000);
            byte[] hashedPassword = pbkdf2.GetBytes(32);

            return Convert.ToBase64String(hashedPassword);
        
    }

				
			

Neste exemplo, utilizamos a classe “Rfc2898DeriveBytes”, que é uma implementação do algoritmo PBKDF2. Ela recebe a senha, o salt e o número de iterações (geralmente recomendado um valor elevado, como 10.000). Quanto maior o número de iterações, mais difícil se torna realizar um ataque de força bruta, pois o processo de hashing fica mais demorado e complexo. Após o processamento, os bytes resultantes são armazenados em uma variável, e então retornamos a string do hash gerado.

Como armazenar o salt e o hash

Uma prática bastante utilizada para armazenar senhas de forma segura é armazenar o salt e o hash separadamente no banco de dados. O salt deve ser único para cada usuário. O hash gerado pela senha concatenada com o salt pode ser armazenado de forma segura.

				
					CREATE TABLE Users (
    Id INT PRIMARY KEY,
    Username VARCHAR(255),
    PasswordHash VARCHAR(255),
    Salt VARCHAR(255)
);

				
			

Validando a senha

Agora que vimos como criar e armazenar o hash e o salt, vamos ver como validar a senha fornecida pelo usuário durante o login. Para isso, você precisa comparar o hash da senha fornecida com o hash armazenado no banco de dados.

				
					public static bool VerifyPassword(string enteredPassword, string storedSalt, string storedHash)
{
    string enteredHash = HashPassword(enteredPassword, storedSalt);

    return enteredHash == storedHash;
}

				
			

No código acima, o salt armazenado é recuperado do banco de dados e combinado com a senha fornecida pelo usuário. O processo de hash é então repetido usando o mesmo algoritmo (como PBKDF2) para gerar um novo hash. Esse hash gerado é comparado com o hash armazenado. Se os dois hashes coincidirem, a senha fornecida é válida.

BCrypt

Agora vamos falar sobre o BCrypt que é um algoritmo de hashing de senha projetado para ser lento e resistente a ataques de força bruta. Ele usa salt de maneira automática e é muito eficaz para proteger senhas de maneira segura. BCrypt é amplamente recomendado para aplicações modernas devido à sua resistência a ataques.

Para usar o “BCrypt” em C#, você pode utilizar a biblioteca “BCrypt.Net” (disponível no NuGet).

Install-Package BCrypt.Net-Next

Para criar o hash de uma senha basta implementar o “BCrypt” da seguinte forma:

 

				
					public static string HashPassword(string password)
    {
        return BCrypt.HashPassword(password);
    }

				
			

Já para verificar se uma senha está correta, pode ser feito da seguinte forma:

				
					public static bool VerifyPassword(string password, string storedHash)
    {
        return BCrypt.Verify(password, storedHash);
    }

				
			

O “BCrypt” faz todo o trabalho para você, incluindo o gerenciamento do salt e a definição de um custo de computação que pode ser ajustado para tornar o processo de hash mais demorado, dificultando ataques.

Argon2

O Argon2 é um dos algoritmos de hash mais seguros disponíveis atualmente, projetado para ser resistente a ataques de força bruta e rainbow table. Ele é altamente configurável, permitindo que você defina parâmetros como o tempo de execução, o uso de memória e o número de iterações. 

Para usar “Argon2” em C#, você pode utilizar a biblioteca “Konscious.Security.Cryptography.Argon2” (disponível no NuGet).

Install-Package Konscious.Security.Cryptography.Argon2

Você pode implementá-lo da seguinte forma:

				
					public static string HashPassword(string password, string salt)
    {
        using (var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password)))
        {
            argon2.Salt = Encoding.UTF8.GetBytes(salt);
            argon2.DegreeOfParallelism = 8;  // Usando múltiplos núcleos de CPU
            argon2.MemorySize = 1024 * 1024; // Usando 1 GB de memória
            argon2.Iterations = 4;

            byte[] hashedPassword = argon2.GetBytes(32);
            return Convert.ToBase64String(hashedPassword);
        }
    }

				
			

No código acima, criamos uma instância de “Argon2id” passando os bytes da senha (password). Em seguida, configuramos o salt e ajustamos os parâmetros para o processo de hashing.

O “DegreeOfParallelism” define quantos núcleos de CPU serão usados, aumentando a segurança ao distribuir o processamento. O “MemorySize” controla a memória utilizada, dificultando ataques. O “Iterations” determina o número de repetições do algoritmo, tornando o processo mais seguro ao aumentar a complexidade e o tempo de execução.

Para realizar o  processo de validação da senha basta realizar o mesmo  processo de  criação da senha e comparar  o hash que foi criado com o que está armazenado  no banco de  dados.

Acelere a sua carreira conosco!

Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET StartClique aqui

Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET ExpertClique aqui

Conclusão

Utilizar algoritmos de hash robustos e práticas adequadas, como a adição de salt, é essencial para garantir a segurança no armazenamento de senhas. A escolha do algoritmo depende das necessidades específicas de segurança do sistema, e soluções como Argon2 oferecem alta resistência contra ataques, permitindo um controle detalhado sobre o uso de recursos. A validação correta das senhas, utilizando o mesmo salt e parâmetros de configuração usados durante o processo de hashing, garante que apenas as senhas corretas sejam aceitas, mantendo o sistema protegido contra acessos não autorizados.