Respuestas:
ACTUALIZACIÓN : ESTA RESPUESTA ES MUY ANTIGUA . En su lugar, utilice las recomendaciones de https://stackoverflow.com/a/10402129/251311 .
Puedes usar
var md5 = new MD5CryptoServiceProvider();
var md5data = md5.ComputeHash(data);
o
var sha1 = new SHA1CryptoServiceProvider();
var sha1data = sha1.ComputeHash(data);
Para obtener una data
matriz de bytes, puede usar
var data = Encoding.ASCII.GetBytes(password);
y recuperar cuerda de md5data
osha1data
var hashedPassword = ASCIIEncoding.GetString(md5data);
md5
es lo suficientemente bueno para casi todo tipo de tareas. Sus vulnerabilidades también se refieren a situaciones muy específicas y casi requieren que el atacante sepa mucho sobre criptografía.
La mayoría de las otras respuestas aquí están algo desactualizadas con las mejores prácticas actuales. Como tal, aquí está la aplicación de usar PBKDF2 / Rfc2898DeriveBytes
para almacenar y verificar contraseñas. El siguiente código está en una clase independiente en esta publicación: Otro ejemplo de cómo almacenar un hash de contraseña con sal . Los conceptos básicos son realmente fáciles, así que aquí se desglosa:
PASO 1 Cree el valor de sal con un PRNG criptográfico:
byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
PASO 2 Cree el Rfc2898DeriveBytes y obtenga el valor hash:
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000);
byte[] hash = pbkdf2.GetBytes(20);
PASO 3 Combine los bytes de sal y contraseña para su uso posterior:
byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);
PASO 4 Convierta la sal y el hachís combinados en una cuerda para almacenar
string savedPasswordHash = Convert.ToBase64String(hashBytes);
DBContext.AddUser(new User { ..., Password = savedPasswordHash });
PASO 5 Verifique la contraseña ingresada por el usuario con una contraseña almacenada
/* 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: Dependiendo de los requisitos de rendimiento de su aplicación específica, el valor 100000
se puede reducir. Debe haber un valor mínimo 10000
.
Basado en la gran respuesta de csharptest.net , he escrito una clase para esto:
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);
Un hash de muestra podría ser este:
$MYHASH$V1$10000$Qhxzi6GNu/Lpy3iUqkeqR/J1hh8y/h5KPDjrv89KzfCVrubn
Como puede ver, también he incluido las iteraciones en el hash para facilitar su uso y la posibilidad de actualizarlo, si es necesario actualizarlo.
Si está interesado en .net core, también tengo una versión .net core en Code Review .
V1
, y V2
el método que necesita verificación.
Utilizo un hash y un salt para el cifrado de mi contraseña (es el mismo hash que usa Asp.Net Membership):
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);
}
}
Las respuestas de @ csharptest.net y Christian Gollhardt son geniales, muchas gracias. Pero después de ejecutar este código en producción con millones de registros, descubrí que hay una pérdida de memoria. Las clases RNGCryptoServiceProvider y Rfc2898DeriveBytes se derivan de IDisposable pero no las desechamos. Escribiré mi solución como respuesta si alguien necesita una versión eliminada.
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);
Creo que usar KeyDerivation.Pbkdf2 es mejor que Rfc2898DeriveBytes.
Ejemplo y explicación: hash de contraseñas en 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 es un código de muestra del artículo. Y es un nivel mínimo de seguridad. Para aumentarlo, usaría en lugar del parámetro KeyDerivationPrf.HMACSHA1
KeyDerivationPrf.HMACSHA256 o KeyDerivationPrf.HMACSHA512.
No comprometa el hash de contraseñas. Hay muchos métodos matemáticamente sólidos para optimizar la piratería de hash de contraseñas. Las consecuencias pueden ser desastrosas. Una vez que un malhechor pueda tener en sus manos la tabla hash de contraseñas de sus usuarios, sería relativamente fácil para él descifrar las contraseñas dado que el algoritmo es débil o la implementación es incorrecta. Tiene mucho tiempo (tiempo x potencia de la computadora) para descifrar contraseñas. El hash de la contraseña debe ser criptográficamente fuerte para convertir "mucho tiempo" en "una cantidad de tiempo irrazonable ".
Un punto más para agregar
La verificación de hash lleva tiempo (y es buena). Cuando el usuario ingresa un nombre de usuario incorrecto, no se necesita tiempo para verificar que el nombre de usuario sea incorrecto. Cuando el nombre de usuario es correcto, comenzamos la verificación de la contraseña; es un proceso relativamente largo.
Para un hacker, sería muy fácil de entender si el usuario existe o no.
Asegúrese de no devolver una respuesta inmediata cuando el nombre de usuario sea incorrecto.
No hace falta decirlo: nunca dé una respuesta sobre lo que está mal. Solo general "Las credenciales son incorrectas".
using
declaración o invocarloClear()
cuando haya terminado de usar la implementación.