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