Respostas:
ATUALIZAÇÃO : ESTA RESPOSTA ESTÁ SERIAMENTE DESATIVADA . Use as recomendações de https://stackoverflow.com/a/10402129/251311 .
Você pode usar
var md5 = new MD5CryptoServiceProvider();
var md5data = md5.ComputeHash(data);
ou
var sha1 = new SHA1CryptoServiceProvider();
var sha1data = sha1.ComputeHash(data);
Para obter uma data
matriz de bytes, você pode usar
var data = Encoding.ASCII.GetBytes(password);
e para recuperar a corda de md5data
ousha1data
var hashedPassword = ASCIIEncoding.GetString(md5data);
md5
é bom o suficiente para quase todos os tipos de tarefas. Suas vulnerabilidades também se referem a situações muito específicas e quase exigem que o invasor saiba muito sobre criptografia.
A maioria das outras respostas aqui está um tanto desatualizada com as melhores práticas de hoje. Como tal, aqui está a aplicação de PBKDF2 / Rfc2898DeriveBytes
para armazenar e verificar senhas. O código a seguir está em uma classe autônoma nesta postagem: Outro exemplo de como armazenar um hash de senha com salt . O básico é muito fácil, então aqui está dividido:
PASSO 1 Crie o valor de sal com um PRNG criptográfico:
byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
ETAPA 2 Crie o Rfc2898DeriveBytes e obtenha o valor de hash:
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000);
byte[] hash = pbkdf2.GetBytes(20);
PASSO 3 Combine os bytes salt e password para uso posterior:
byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);
PASSO 4 Transforme o sal + hash combinado em uma string para armazenamento
string savedPasswordHash = Convert.ToBase64String(hashBytes);
DBContext.AddUser(new User { ..., Password = savedPasswordHash });
ETAPA 5 Verifique a senha inserida pelo usuário em relação a uma senha armazenada
/* Fetch the stored value */
string savedPasswordHash = DBContext.GetUser(u => u.UserName == user).Password;
/* Extract the bytes */
byte[] hashBytes = Convert.FromBase64String(savedPasswordHash);
/* Get the salt */
byte[] salt = new byte[16];
Array.Copy(hashBytes, 0, salt, 0, 16);
/* Compute the hash on the password the user entered */
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000);
byte[] hash = pbkdf2.GetBytes(20);
/* Compare the results */
for (int i=0; i < 20; i++)
if (hashBytes[i+16] != hash[i])
throw new UnauthorizedAccessException();
Nota: Dependendo dos requisitos de desempenho de sua aplicação específica, o valor 100000
pode ser reduzido. Um valor mínimo deve ser próximo 10000
.
Com base na ótima resposta do csharptest.net , escrevi uma aula para isso:
public static class SecurePasswordHasher
{
/// <summary>
/// Size of salt.
/// </summary>
private const int SaltSize = 16;
/// <summary>
/// Size of hash.
/// </summary>
private const int HashSize = 20;
/// <summary>
/// Creates a hash from a password.
/// </summary>
/// <param name="password">The password.</param>
/// <param name="iterations">Number of iterations.</param>
/// <returns>The hash.</returns>
public static string Hash(string password, int iterations)
{
// Create salt
byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[SaltSize]);
// Create hash
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations);
var hash = pbkdf2.GetBytes(HashSize);
// Combine salt and hash
var hashBytes = new byte[SaltSize + HashSize];
Array.Copy(salt, 0, hashBytes, 0, SaltSize);
Array.Copy(hash, 0, hashBytes, SaltSize, HashSize);
// Convert to base64
var base64Hash = Convert.ToBase64String(hashBytes);
// Format hash with extra information
return string.Format("$MYHASH$V1${0}${1}", iterations, base64Hash);
}
/// <summary>
/// Creates a hash from a password with 10000 iterations
/// </summary>
/// <param name="password">The password.</param>
/// <returns>The hash.</returns>
public static string Hash(string password)
{
return Hash(password, 10000);
}
/// <summary>
/// Checks if hash is supported.
/// </summary>
/// <param name="hashString">The hash.</param>
/// <returns>Is supported?</returns>
public static bool IsHashSupported(string hashString)
{
return hashString.Contains("$MYHASH$V1$");
}
/// <summary>
/// Verifies a password against a hash.
/// </summary>
/// <param name="password">The password.</param>
/// <param name="hashedPassword">The hash.</param>
/// <returns>Could be verified?</returns>
public static bool Verify(string password, string hashedPassword)
{
// Check hash
if (!IsHashSupported(hashedPassword))
{
throw new NotSupportedException("The hashtype is not supported");
}
// Extract iteration and Base64 string
var splittedHashString = hashedPassword.Replace("$MYHASH$V1$", "").Split('$');
var iterations = int.Parse(splittedHashString[0]);
var base64Hash = splittedHashString[1];
// Get hash bytes
var hashBytes = Convert.FromBase64String(base64Hash);
// Get salt
var salt = new byte[SaltSize];
Array.Copy(hashBytes, 0, salt, 0, SaltSize);
// Create hash with given salt
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations);
byte[] hash = pbkdf2.GetBytes(HashSize);
// Get result
for (var i = 0; i < HashSize; i++)
{
if (hashBytes[i + SaltSize] != hash[i])
{
return false;
}
}
return true;
}
}
Uso:
// Hash
var hash = SecurePasswordHasher.Hash("mypassword");
// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);
Um hash de amostra pode ser este:
$MYHASH$V1$10000$Qhxzi6GNu/Lpy3iUqkeqR/J1hh8y/h5KPDjrv89KzfCVrubn
Como você pode ver, também incluí as iterações no hash para facilitar o uso e a possibilidade de atualizá-lo, se precisarmos atualizar.
Se você estiver interessado em .net core, também tenho uma versão .net core no Code Review .
V1
e V2
qual o método que você precisa de verificação.
Eu uso um hash e um salt para a criptografia de minha senha (é o mesmo hash que o Asp.Net Membership usa):
private string PasswordSalt
{
get
{
var rng = new RNGCryptoServiceProvider();
var buff = new byte[32];
rng.GetBytes(buff);
return Convert.ToBase64String(buff);
}
}
private string EncodePassword(string password, string salt)
{
byte[] bytes = Encoding.Unicode.GetBytes(password);
byte[] src = Encoding.Unicode.GetBytes(salt);
byte[] dst = new byte[src.Length + bytes.Length];
Buffer.BlockCopy(src, 0, dst, 0, src.Length);
Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");
byte[] inarray = algorithm.ComputeHash(dst);
return Convert.ToBase64String(inarray);
}
public class CryptographyProcessor
{
public string CreateSalt(int size)
{
//Generate a cryptographic random number.
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] buff = new byte[size];
rng.GetBytes(buff);
return Convert.ToBase64String(buff);
}
public string GenerateHash(string input, string salt)
{
byte[] bytes = Encoding.UTF8.GetBytes(input + salt);
SHA256Managed sHA256ManagedString = new SHA256Managed();
byte[] hash = sHA256ManagedString.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
public bool AreEqual(string plainTextInput, string hashedInput, string salt)
{
string newHashedPin = GenerateHash(plainTextInput, salt);
return newHashedPin.Equals(hashedInput);
}
}
As respostas de @csharptest.net e Christian Gollhardt são ótimas, muito obrigado. Mas depois de executar esse código em produção com milhões de registros, descobri que há um vazamento de memória. As classes RNGCryptoServiceProvider e Rfc2898DeriveBytes são derivadas de IDisposable, mas não as descartamos. Escreverei minha solução como resposta caso alguém precise com versão descartada.
public static class SecurePasswordHasher
{
/// <summary>
/// Size of salt.
/// </summary>
private const int SaltSize = 16;
/// <summary>
/// Size of hash.
/// </summary>
private const int HashSize = 20;
/// <summary>
/// Creates a hash from a password.
/// </summary>
/// <param name="password">The password.</param>
/// <param name="iterations">Number of iterations.</param>
/// <returns>The hash.</returns>
public static string Hash(string password, int iterations)
{
// Create salt
using (var rng = new RNGCryptoServiceProvider())
{
byte[] salt;
rng.GetBytes(salt = new byte[SaltSize]);
using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations))
{
var hash = pbkdf2.GetBytes(HashSize);
// Combine salt and hash
var hashBytes = new byte[SaltSize + HashSize];
Array.Copy(salt, 0, hashBytes, 0, SaltSize);
Array.Copy(hash, 0, hashBytes, SaltSize, HashSize);
// Convert to base64
var base64Hash = Convert.ToBase64String(hashBytes);
// Format hash with extra information
return $"$HASH|V1${iterations}${base64Hash}";
}
}
}
/// <summary>
/// Creates a hash from a password with 10000 iterations
/// </summary>
/// <param name="password">The password.</param>
/// <returns>The hash.</returns>
public static string Hash(string password)
{
return Hash(password, 10000);
}
/// <summary>
/// Checks if hash is supported.
/// </summary>
/// <param name="hashString">The hash.</param>
/// <returns>Is supported?</returns>
public static bool IsHashSupported(string hashString)
{
return hashString.Contains("HASH|V1$");
}
/// <summary>
/// Verifies a password against a hash.
/// </summary>
/// <param name="password">The password.</param>
/// <param name="hashedPassword">The hash.</param>
/// <returns>Could be verified?</returns>
public static bool Verify(string password, string hashedPassword)
{
// Check hash
if (!IsHashSupported(hashedPassword))
{
throw new NotSupportedException("The hashtype is not supported");
}
// Extract iteration and Base64 string
var splittedHashString = hashedPassword.Replace("$HASH|V1$", "").Split('$');
var iterations = int.Parse(splittedHashString[0]);
var base64Hash = splittedHashString[1];
// Get hash bytes
var hashBytes = Convert.FromBase64String(base64Hash);
// Get salt
var salt = new byte[SaltSize];
Array.Copy(hashBytes, 0, salt, 0, SaltSize);
// Create hash with given salt
using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations))
{
byte[] hash = pbkdf2.GetBytes(HashSize);
// Get result
for (var i = 0; i < HashSize; i++)
{
if (hashBytes[i + SaltSize] != hash[i])
{
return false;
}
}
return true;
}
}
}
Uso:
// Hash
var hash = SecurePasswordHasher.Hash("mypassword");
// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);
Acho que usar KeyDerivation.Pbkdf2 é melhor do que Rfc2898DeriveBytes.
Exemplo e explicação: senhas de hash no ASP.NET Core
using System;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
public class Program
{
public static void Main(string[] args)
{
Console.Write("Enter a password: ");
string password = Console.ReadLine();
// generate a 128-bit salt using a secure PRNG
byte[] salt = new byte[128 / 8];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(salt);
}
Console.WriteLine($"Salt: {Convert.ToBase64String(salt)}");
// derive a 256-bit subkey (use HMACSHA1 with 10,000 iterations)
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: password,
salt: salt,
prf: KeyDerivationPrf.HMACSHA1,
iterationCount: 10000,
numBytesRequested: 256 / 8));
Console.WriteLine($"Hashed: {hashed}");
}
}
/*
* SAMPLE OUTPUT
*
* Enter a password: Xtw9NMgx
* Salt: NZsP6NnmfBuYeJrrAKNuVQ==
* Hashed: /OOoOer10+tGwTRDTrQSoeCxVTFr6dtYly7d0cPxIak=
*/
Este é um exemplo de código do artigo. E é um nível mínimo de segurança. Para aumentá-lo, eu usaria em vez do parâmetro KeyDerivationPrf.HMACSHA1
KeyDerivationPrf.HMACSHA256 ou KeyDerivationPrf.HMACSHA512.
Não comprometa o hash de senha. Existem muitos métodos matematicamente sólidos para otimizar o hack de senha. As consequências podem ser desastrosas. Uma vez que um malfeitor pode colocar as mãos na tabela de hash de senha de seus usuários, seria relativamente fácil para ele quebrar as senhas, dado que o algoritmo é fraco ou a implementação está incorreta. Ele tem muito tempo (tempo x capacidade do computador) para decifrar senhas. O hash de senha deve ser criptograficamente forte para transformar "muito tempo" em " quantidade de tempo irracional ".
Mais um ponto a adicionar
A verificação de hash leva tempo (e é bom). Quando o usuário insere um nome de usuário incorreto, não leva tempo para verificar se o nome de usuário está incorreto. Quando o nome de usuário está correto, iniciamos a verificação de senha - é um processo relativamente longo.
Para um hacker, seria muito fácil entender se o usuário existe ou não.
Certifique-se de não retornar uma resposta imediata quando o nome do usuário estiver errado.
Nem é preciso dizer: nunca responda o que está errado. Apenas "credenciais erradas" gerais.
using
instrução ou chamáClear()
-lo quando terminar de usar a implementação.