Armazenar senhas de maneira segura no banco de dados é uma prática essencial para garantir a integridade e a segurança dos dados dos usuários. Neste artigo, vamos explorar como criar, armazenar senhas de forma segura e como validar uma senha utilizando .NET. Também abordaremos práticas recomendadas adicionais para desempenho e segurança.
Antes de mais nada, o que NÃO fazer
🔹 Armazenar senhas em texto simples: Guardar senhas em texto simples no banco de dados é uma prática extremamente perigosa, pois qualquer pessoa com acesso interno pode visualizá-las.
🔹 Armazenar hashes de senhas diretamente: Embora seja melhor do que armazenar senhas em texto simples, armazenar apenas o hash da senha não é suficiente. Isso porque hashes simples estão vulneráveis a ataques de precomputação, como tabelas arco-íris.
🔹 Salting inadequado: Para mitigar ataques de precomputação, é essencial adicionar um “sal” às senhas antes de hasheá-las.
Tá, mas o que é um Salt?
De acordo com as diretrizes da OWASP, “um salt é uma string única e aleatoriamente gerada que é adicionada a cada senha como parte do processo de hashing”. O objetivo do salt é garantir que o resultado do hash seja único para cada senha, mesmo que duas senhas iguais sejam usadas.
Como armazenar uma senha e um salt?
1️⃣ Armazenamento do Salt: O salt não precisa ser criptografado e pode ser armazenado em texto simples no banco de dados. Sua finalidade é assegurar que o hash resultante seja único para cada senha.
2️⃣ Armazenamento da Senha: A senha deve ser armazenada no banco de dados no seguinte formato: hash(senha + salt). Isso garante que cada hash é único, mesmo para senhas idênticas.
Funcionamento do Processo de Hashing com Salt
- Geração do Salt: Quando um usuário cria ou altera uma senha, um salt único e aleatório é gerado.
- Combinação com a Senha: O salt é concatenado (ou combinado de alguma forma) com a senha do usuário.
- Hashing: A senha combinada com o salt é então passada por uma função de hash criptográfica (como SHA-256, bcrypt, Argon2, etc.), resultando em um hash de senha único.
- Armazenamento: Tanto o salt quanto o hash resultante são armazenados no banco de dados. Quando o usuário tenta fazer login, o sistema pega a senha fornecida, adiciona o salt correspondente, faz o hash e compara com o hash armazenado.
Exemplo Prático
- Usuário Escolhe Senha: A senha escolhida é “minhaSenha123”.
- Geração do Salt: Um salt aleatório é gerado, por exemplo, “randomSalt12345”.
- Combinação: A senha e o salt são combinados, resultando em “minhaSenha123randomSalt12345”.
- Hashing: A combinação “minhaSenha123randomSalt12345” é passada por uma função de hash, resultando em algo como “5f4dcc3b5aa765d61d8327deb882cf99”.
- Armazenamento: O salt “randomSalt12345” e o hash “5f4dcc3b5aa765d61d8327deb882cf99” são armazenados no banco de dados.
Boas Práticas Adicionais
- Utilizar Algoritmos de Hashing Fortes: Utilize algoritmos como PBKDF2, bcrypt ou Argon2 que são projetados para serem computacionalmente intensivos e, portanto, mais resistentes a ataques de força bruta.
- Configurar Iterações Adequadas: Para PBKDF2, recomenda-se usar um número elevado de iterações (por exemplo, 10000 ou mais) para aumentar a dificuldade de ataques.
- Comprimento Adequado do Salt: Utilize um salt de pelo menos 16 bytes para garantir a segurança contra ataques de tabelas arco-íris.
- Atualizar Periodicamente: Atualize periodicamente o número de iterações e a complexidade do algoritmo de hashing conforme a capacidade computacional aumenta ao longo do tempo.
- Monitoramento e Auditoria: Implemente monitoramento e auditoria para detectar tentativas de acesso não autorizado e outras atividades suspeitas.
- Armazenamento Seguro: Utilize práticas de segurança adequadas para proteger o banco de dados onde os hashes de senhas e salts são armazenados, incluindo criptografia de dados em repouso.
- Evitar Reutilização de Salts: Certifique-se de que cada senha tem um salt único para prevenir que hashes idênticos revelem senhas iguais.

Considerações de Desempenho
- Balanceamento de Iterações: O número de iterações deve ser alto o suficiente para ser seguro, mas também deve ser balanceado para não prejudicar o desempenho da aplicação.
- Uso de Cache: Considere o uso de cache para armazenar temporariamente salts e hashes se houver uma necessidade de validação frequente para melhorar a performance.
Exemplo completo de uma aplicação em .Net
Todo código está disponível aqui: https://github.com/jhomarolo/ValidacaoSenhas
Program.cs
Crie um novo projeto console application e adicione o seguinte código na classe principal:
using System;
namespace PasswordSecurityExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Cadastro de Usuário:");
            Console.Write("Digite uma senha: ");
            string password = Console.ReadLine();
            // Verificar a força da senha
            if (!PasswordManager.IsValidPassword(password))
            {
                Console.WriteLine("A senha deve ter no mínimo 6 caracteres e pelo menos 1 número, 1 letra e 1 caracter especial.");
                return;
            }
            // Gerar o hash e salt da senha
            var (hash, salt) = PasswordManager.HashPassword(password);
            Console.WriteLine($"Senha Hash: {hash}");
            Console.WriteLine($"Salt: {salt}");
            // Simular armazenamento no banco de dados
            // Normalmente você armazenaria hash e salt no banco de dados aqui
            // Validação de senha
            Console.WriteLine("\nValidação de Usuário:");
            Console.Write("Digite a senha novamente: ");
            string enteredPassword = Console.ReadLine();
            bool isValid = PasswordManager.ValidatePassword(enteredPassword, hash, salt);
            if (isValid)
            {
                Console.WriteLine("Senha válida!");
            }
            else
            {
                Console.WriteLine("Senha inválida!");
            }
        }
    }
}
PasswordManager.cs
Agora, crie um novo arquivo chamado PasswordManager.cs e adicione o seguinte código:
using System;
using System.Security.Cryptography;
using System.Text;
namespace PasswordSecurityExample
{
    public class PasswordManager
    {
        public static (string Hash, string Salt) HashPassword(string password)
        {
            // Gerando um salt aleatório
            byte[] saltBytes = new byte[16];
            using (var provider = new RNGCryptoServiceProvider())
            {
                provider.GetBytes(saltBytes);
            }
            string salt = Convert.ToBase64String(saltBytes);
            // Concatenando senha e salt e gerando o hash
            var hash = ComputeHash(password, salt);
            
            return (hash, salt);
        }
        public static bool ValidatePassword(string enteredPassword, string storedHash, string storedSalt)
        {
            // Gerar o hash da senha inserida com o salt armazenado
            var hashToValidate = ComputeHash(enteredPassword, storedSalt);
            
            // Comparar o hash gerado com o hash armazenado
            return hashToValidate == storedHash;
        }
        private static string ComputeHash(string password, string salt)
        {
            var pbkdf2 = new Rfc2898DeriveBytes(password, Encoding.UTF8.GetBytes(salt), 10000);
            byte[] hash = pbkdf2.GetBytes(20);
            return Convert.ToBase64String(hash);
        }
        public static bool IsValidPassword(string password)
        {
            // Verificar se a senha atende aos critérios de força
            if (password.Length < 6)
                return false;
            bool hasLetter = false, hasDigit = false, hasSpecialChar = false;
            foreach (char c in password)
            {
                if (char.IsLetter(c)) hasLetter = true;
                else if (char.IsDigit(c)) hasDigit = true;
                else hasSpecialChar = true;
            }
            return hasLetter && hasDigit && hasSpecialChar;
        }
    }
}
Funcionamento
- O programa solicitará que você digite uma senha para cadastro.
- Verificará se a senha atende aos critérios de força: no mínimo 6 caracteres, contendo pelo menos 1 número, 1 letra e 1 caractere especial.
- Ele exibirá o hash da senha e o salt gerados.
- Em seguida, solicitará que você digite a senha novamente para validação.
- O programa verificará se a senha digitada é válida comparando o hash gerado com o hash armazenado.
Implementando essas práticas em suas aplicações .NET, você aumentará significativamente a segurança das senhas armazenadas e protegerá melhor os dados dos seus usuários contra ataques maliciosos.
Dicas Adicionais para Gerenciamento de Senhas
Para aumentar ainda mais a segurança das senhas, aqui estão algumas práticas adicionais e recomendações que podem ser implementadas:
1. Políticas de Senhas Fortes
Além das verificações básicas mencionadas anteriormente, considere implementar políticas de senha mais robustas:
- Comprimento Mínimo: Requerer senhas com pelo menos 8-12 caracteres.
- Complexidade: Garantir a presença de letras maiúsculas, letras minúsculas, números e caracteres especiais.
- Proibição de Senhas Comuns: Utilizar uma lista de senhas comuns (como “123456”, “password”, etc.) e impedir que os usuários as utilizem.
- Proibição de Reutilização: Impedir que os usuários reutilizem suas senhas anteriores.
2. Expiração de Senhas
- Rotatividade de Senhas: Exigir que os usuários alterem suas senhas periodicamente (por exemplo, a cada 90 dias).
- Avisos de Expiração: Notificar os usuários antes que suas senhas expirem para incentivá-los a atualizá-las.
3. Proteção Contra Ataques de Força Bruta
- Limitação de Tentativas: Implementar uma limitação de tentativas de login, bloqueando temporariamente o usuário após um número definido de tentativas falhas.
- CAPTCHA: Adicionar CAPTCHA em formulários de login após um certo número de tentativas falhas.
4. Verificação Multifator (MFA)
- Autenticação de Dois Fatores (2FA): Requerer um segundo fator de autenticação, como um código enviado por SMS, email ou gerado por um aplicativo de autenticação.
- Hardware Tokens: Utilizar dispositivos de hardware (como YubiKey) para fornecer um fator de autenticação adicional.
5. Monitoramento e Auditoria
- Registro de Atividades: Registrar todas as tentativas de login (bem-sucedidas e falhas) e outras ações relacionadas à conta do usuário.
- Análise de Padrões: Monitorar padrões de login suspeitos, como tentativas de login de locais geográficos incomuns.
6. Segurança no Frontend
- Criptografia em Trânsito: Garantir que todas as comunicações entre o cliente e o servidor sejam criptografadas usando HTTPS.
- Proteção Contra CSRF: Implementar medidas para proteger contra ataques de falsificação de solicitação entre sites (CSRF).
- Sanitização de Entrada: Sanitizar todas as entradas de usuário para prevenir ataques de injeção (como SQL Injection).
Agora com essas dicas, acredito que você vai armazenar muito melhor suas senhas, não é mesmo?
Todo código está disponível aqui: https://github.com/jhomarolo/ValidacaoSenhas

 
		