algoritmo - Contraseñas hash con MD5 o sha-256 C#
sha256 c# (9)
Aquí hay una implementación completa de una clase SecuredPassword inconsciente de persistencia
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
public class SecuredPassword
{
private const int saltSize = 256;
private readonly byte[] hash;
private readonly byte[] salt;
public byte[] Hash
{
get { return hash; }
}
public byte[] Salt
{
get { return salt; }
}
public SecuredPassword(string plainPassword)
{
if (string.IsNullOrWhiteSpace(plainPassword))
return;
using (var deriveBytes = new Rfc2898DeriveBytes(plainPassword, saltSize))
{
salt = deriveBytes.Salt;
hash = deriveBytes.GetBytes(saltSize);
}
}
public SecuredPassword(byte[] hash, byte[] salt)
{
this.hash = hash;
this.salt = salt;
}
public bool Verify(string password)
{
if (string.IsNullOrWhiteSpace(password))
return false;
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt))
{
byte[] newKey = deriveBytes.GetBytes(saltSize);
return newKey.SequenceEqual(hash);
}
}
}
Y pruebas:
public class SecuredPasswordTests
{
[Test]
public void IsHashed_AsExpected()
{
var securedPassword = new SecuredPassword("password");
Assert.That(securedPassword.Hash, Is.Not.EqualTo("password"));
Assert.That(securedPassword.Hash.Length, Is.EqualTo(256));
}
[Test]
public void Generates_Unique_Salt()
{
var securedPassword = new SecuredPassword("password");
var securedPassword2 = new SecuredPassword("password");
Assert.That(securedPassword.Salt, Is.Not.Null);
Assert.That(securedPassword2.Salt, Is.Not.Null);
Assert.That(securedPassword.Salt, Is.Not.EqualTo(securedPassword2.Salt));
}
[Test]
public void Generates_Unique_Hash()
{
var securedPassword = new SecuredPassword("password");
var securedPassword2 = new SecuredPassword("password");
Assert.That(securedPassword.Hash, Is.Not.Null);
Assert.That(securedPassword2.Hash, Is.Not.Null);
Assert.That(securedPassword.Hash, Is.Not.EqualTo(securedPassword2.Hash));
}
[Test]
public void Verify_WhenMatching_ReturnsTrue()
{
var securedPassword = new SecuredPassword("password");
var result = securedPassword.Verify("password");
Assert.That(result, Is.True);
}
[Test]
public void Verify_WhenDifferent_ReturnsFalse()
{
var securedPassword = new SecuredPassword("password");
var result = securedPassword.Verify("Password");
Assert.That(result, Is.False);
}
[Test]
public void Verify_WhenRehydrated_AndMatching_ReturnsTrue()
{
var securedPassword = new SecuredPassword("password123");
var rehydrated = new SecuredPassword(securedPassword.Hash, securedPassword.Salt);
var result = rehydrated.Verify("password123");
Assert.That(result, Is.True);
}
[Test]
public void Constructor_Handles_Null_Password()
{
Assert.DoesNotThrow(() => new SecuredPassword(null));
}
[Test]
public void Constructor_Handles_Empty_Password()
{
Assert.DoesNotThrow(() => new SecuredPassword(string.Empty));
}
[Test]
public void Verify_Handles_Null_Password()
{
Assert.DoesNotThrow(() => new SecuredPassword("password").Verify(null));
}
[Test]
public void Verify_Handles_Empty_Password()
{
Assert.DoesNotThrow(() => new SecuredPassword("password").Verify(string.Empty));
}
[Test]
public void Verify_When_Null_Password_ReturnsFalse()
{
Assert.That(new SecuredPassword("password").Verify(null), Is.False);
}
}
Estoy escribiendo un formulario de registro para una aplicación pero todavía tengo problemas con ser nuevo en c #.
Estoy buscando encriptar / hash las contraseñas a md5 o sha-256, preferiblemente sha-256.
¿Algún buen ejemplo? Quiero que pueda tomar la información de "contraseña de cadena"; y luego hash y almacenar en la variable "string hPassword;". ¿Algunas ideas?
La clase System.Security.Cryptography.SHA256 debería hacer el truco:
http://msdn.microsoft.com/en-us/library/system.security.cryptography.sha256.aspx
No use un hash simple, o incluso un hash salado. Use algún tipo de técnica de fortalecimiento de claves como bcrypt (con una implementación .NET aquí ) o PBKDF2 (con una implementación incorporada ).
Aquí hay un ejemplo usando PBKDF2.
Para generar una clave a partir de su contraseña ...
string password = GetPasswordFromUserInput();
// specify that we want to randomly generate a 20-byte salt
using (var deriveBytes = new Rfc2898DeriveBytes(password, 20))
{
byte[] salt = deriveBytes.Salt;
byte[] key = deriveBytes.GetBytes(20); // derive a 20-byte key
// save salt and key to database
}
Y luego para probar si una contraseña es válida ...
string password = GetPasswordFromUserInput();
byte[] salt, key;
// load salt and key from database
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt))
{
byte[] newKey = deriveBytes.GetBytes(20); // derive a 20-byte key
if (!newKey.SequenceEqual(key))
throw new InvalidOperationException("Password is invalid!");
}
PBKDF2 está utilizando HMACSHA1 ....... si desea una implementación más moderna de HMACSHA256 o HMACSHA512 y aún desea un estiramiento de teclas para hacer que el algoritmo sea más lento, sugiero esta API: https://sourceforge.net/projects/pwdtknet/
Por favor use esto ya que tengo los mismos problemas antes, pero podría resolverlo con el pequeño fragmento de código
public static string ComputeHash(string input, HashAlgorithm algorithm, Byte[] salt)
{
Byte[] inputBytes = Encoding.UTF8.GetBytes(input);
// Combine salt and input bytes
Byte[] saltedInput = new Byte[salt.Length + inputBytes.Length];
salt.CopyTo(saltedInput, 0);
inputBytes.CopyTo(saltedInput, salt.Length);
Byte[] hashedBytes = algorithm.ComputeHash(saltedInput);
StringBuilder hex = new StringBuilder(hashedBytes.Length * 2);
foreach (byte b in hashedBytes)
hex.AppendFormat("{0:X2}", b);
return hex.ToString();
}
Si va a almacenar las contraseñas hash, use bcrypt en lugar de SHA-256. El problema es que SHA-256 está optimizado para la velocidad, lo que hace que sea más fácil para un ataque de fuerza bruta contra contraseñas si alguien tiene acceso a su base de datos.
Lea este artículo: Suficiente con The Rainbow Tables: Lo que necesita saber sobre los Esquemas de contraseña segura y esta answer a una pregunta anterior de SO.
Algunas citas del artículo:
El problema es que MD5 es rápido. También lo son sus competidores modernos, como SHA1 y SHA256. La velocidad es un objetivo de diseño de un hash seguro moderno, porque los hashes son un bloque de construcción de casi todos los criptosistemas y, por lo general, se ejecutan a demanda por paquete o por mensaje.
La velocidad es exactamente lo que no quieres en una función de hash de contraseña.
Finalmente, aprendimos que si queremos almacenar contraseñas de forma segura tenemos tres opciones razonables: el esquema MD5 de PHK, el esquema Bcrypt de Provos-Maziere y el SRP. Aprendimos que la elección correcta es Bcrypt.
TL; DR utiliza Microsoft.AspNetCore.Cryptography.KeyDerivation , implementando PBKDF2 con SHA-512.
La buena idea para comenzar con el hash de contraseñas es observar lo que dicen las directrices de OWASP . La lista de algoritmos recomendados incluye Argon2, PBKDF2, scrypt y bcrypt. Todos estos algoritmos se pueden sintonizar para ajustar el tiempo que lleva codificar una contraseña y, en consecuencia, el tiempo para descifrarla mediante la fuerza bruta. Todos estos algoritmos utilizan sal para protegerse de los ataques de tablas del arco iris.
Ninguno de estos algoritmos es terriblemente débil, pero hay algunas diferencias:
- bcrypt ha existido por casi 20 años, ha sido ampliamente utilizado y ha resistido la prueba del tiempo. Es bastante resistente a los ataques de GPU, pero no a FPGA
- Argon2 es la última incorporación, siendo un ganador de la competencia de hashing de contraseñas de 2015. Tiene una mejor protección contra los ataques de GPU y FPGA, pero es demasiado reciente para mi gusto
- No sé mucho sobre scrypt. Ha sido diseñado para frustrar los ataques acelerados de GPU y FPGA, pero he escuchado que resultó no ser tan fuerte como se pretendía originalmente.
- PBKDF2 es una familia de algoritmos parametrizados por las diferentes funciones hash. No ofrece una protección específica contra ataques GPU o ASIC, especialmente si se utiliza una función hash más débil como SHA-1, pero está certificada por FIPS si es importante para usted, y aún aceptable si el número de iteraciones es lo suficientemente grande.
Basado en algoritmos solo, probablemente iría con bcrypt, PBKDF2 sería el menos favorable.
Sin embargo, no es la historia completa, porque incluso el mejor algoritmo puede volverse inseguro por una mala implementación. Veamos qué hay disponible para la plataforma .NET:
- Bcrypt está disponible a través de bcrypt.net . Dicen que la implementación se basa en Java jBCrypt. Actualmente hay 6 contribuyentes y 8 números (todos cerrados) en github. En general, se ve bien, sin embargo, no sé si alguien ha realizado una auditoría del código, y es difícil saber si una versión actualizada estará disponible lo suficientemente pronto si se encuentra una vulnerabilidad. Escuché que dejó de usar bcrypt por esos motivos
- Probablemente, la mejor forma de utilizar Argon2 sea a través de enlaces a la conocida biblioteca libsodium, por ejemplo, https://github.com/adamcaudill/libsodium-net . La idea es que la mayor parte de la criptografía se implemente a través de libsodium, que tiene un soporte considerable, y las partes "no probadas" son bastante limitadas. Sin embargo, los detalles de la criptografía significan mucho, así que combinado con Argon2 siendo relativamente reciente, lo trataría como una opción experimental
- Durante mucho tiempo, .NET tenía incorporada una implementación de Rfc2898DeriveBytes través de la clase Rfc2898DeriveBytes . Sin embargo, la implementación solo puede usar la función hash SHA-1, que se considera demasiado rápida para ser segura hoy en día.
- Finalmente, la solución más reciente es Microsoft.AspNetCore.Cryptography.KeyDerivation paquete Microsoft.AspNetCore.Cryptography.KeyDerivation disponible a través de NuGet. Proporciona el algoritmo PBKDF2 con funciones hash SHA-1, SHA-256 o SHA-512, que es considerablemente mejor que
Rfc2898DeriveBytes
. La mayor ventaja aquí es que la implementación es proporcionada por Microsoft, y si bien no puedo evaluar adecuadamente la diligencia criptográfica de los desarrolladores de Microsoft frente a los desarrolladores de BCrypt.net o libsodium, simplemente tiene sentido confiar en ello, porque si está ejecutando una aplicación .NET, están confiando mucho en Microsoft ya. También podríamos esperar que Microsoft publique actualizaciones si se encuentran problemas de seguridad. Ojalá.
Para resumir la investigación hasta este punto, mientras que PBKDF2 podría ser el algoritmo menos preferido de los cuatro, la disponibilidad de la implementación suministrada por Microsoft supera a la anterior, por lo que la decisión razonable sería usar Microsoft.AspNetCore.Cryptography.KeyDerivation
.
El paquete reciente en este momento tiene como objetivo .NET Standard 2.0, por lo que está disponible en .NET Core 2.0 o .NET Framework 4.6.1 o posterior. Si utiliza una versión anterior de Framework, es posible utilizar la versión anterior del paquete, 1.1.3 , que se dirige a .NET Framework 4.5.1 o .NET Core 1.0. Desafortunadamente, no es posible usarlo incluso en versiones anteriores de .NET.
La documentación y el ejemplo de trabajo están disponibles en Microsoft.AspNetCore.Cryptography.KeyDerivation . Sin embargo, no lo copie y pegue como está, todavía hay decisiones que un desarrollador debe tomar.
La primera decisión es qué función hash usar. Las opciones disponibles incluyen SHA-1, SHA-256 y SHA-512. De ellos, SHA-1 es definitivamente demasiado rápido para ser seguro, SHA-256 es decente, pero recomendaría SHA-512, porque supuestamente, su uso de operaciones de 64 bits hace que sea más difícil beneficiarse de los ataques basados en GPU.
Luego, debe elegir la longitud de salida del hash de la contraseña y la longitud del salt. No tiene sentido tener una salida más larga que la salida de la función hash (por ejemplo, 512 bits para SHA-512), y probablemente sería la más segura tenerlo exactamente así. Para la duración de la sal, las opiniones difieren. 128 bits deberían ser suficientes, pero en cualquier caso, la longitud más larga que la longitud de la salida de hash seguramente no proporciona ningún beneficio.
Luego, hay un recuento de iteraciones. Cuanto más grande es, los hash de contraseñas más difíciles son para descifrar, pero cuanto más tiempo lleva registrar a los usuarios. Sugiero elegirlo, por lo que el hash toma entre 0,25 y 1 segundo en el sistema de producción típico, y en cualquier caso, no debe ser menor que 10000
Normalmente, obtendrá una matriz de bytes como valores de sal y hash. Use Base64 para convertirlos en cadenas. Puede optar por usar dos columnas diferentes en la base de datos, o combinar sal y contraseña en una columna usando un separador que no se encuentra en Base64.
No se olvide de diseñar un almacenamiento de hash de contraseñas de manera que permita avanzar sin problemas a un mejor algoritmo de hash en el futuro.
System.Security.Cryptography
utilizar el espacio de nombres System.Security.Cryptography
; específicamente, la clase MD5
o la clase SHA256
.
Sacando un poco del código en esta página , y sabiendo que ambas clases tienen la misma clase base ( HashAlgorithm
), puedes usar una función como esta:
public string ComputeHash(string input, HashAlgorithm algorithm)
{
Byte[] inputBytes = Encoding.UTF8.GetBytes(input);
Byte[] hashedBytes = algorithm.ComputeHash(inputBytes);
return BitConverter.ToString(hashedBytes);
}
Entonces podrías llamarlo así (para MD5):
string hPassword = ComputeHash(password, new MD5CryptoServiceProvider());
O para SHA256:
string hPassword = ComputeHash(password, new SHA256CryptoServiceProvider());
Editar: agregando soporte de sal
Como dtb señaló en los comentarios, este código sería más fuerte si incluyera la capacidad de agregar salt . Si no está familiarizado con él, sal es un conjunto de bits aleatorios que se incluyen como una entrada a la función de hashing, que evita en gran medida los ataques de diccionario contra una contraseña hash (por ejemplo, utilizando una tabla de arco iris ). Aquí hay una versión modificada de la función ComputeHash
que admite sal:
public static string ComputeHash(string input, HashAlgorithm algorithm, Byte[] salt)
{
Byte[] inputBytes = Encoding.UTF8.GetBytes(input);
// Combine salt and input bytes
Byte[] saltedInput = new Byte[salt.Length + inputBytes.Length];
salt.CopyTo(saltedInput, 0);
inputBytes.CopyTo(saltedInput, salt.Length);
Byte[] hashedBytes = algorithm.ComputeHash(saltedInput);
return BitConverter.ToString(hashedBytes);
}
¡Espero que esto haya sido de ayuda!
Siempre debe saltear la contraseña antes de hash al guardarlos en la base de datos.
Columnas de base de datos recomendadas:
- PasswordSalt: int
- PasswordHash: binario (20)
La mayoría de las publicaciones que encuentres en línea hablarán sobre la codificación ASCII de la sal y el hash, pero eso no es necesario y solo agrega cómputos innecesarios. Además, si usa SHA-1 , la salida solo tendrá 20 bytes, por lo que su campo de hash en la base de datos solo necesita 20 bytes de longitud. Entiendo que pregunte por SHA-256, pero a menos que tenga un motivo convincente, usar SHA-1 con un valor de sal será suficiente en la mayoría de las prácticas comerciales. Si insiste en SHA-256, el campo hash de la base de datos debe tener 32 bytes de longitud.
A continuación hay algunas funciones que generarán la sal, calcularán el hash y verificarán el hash contra una contraseña.
La función de sal a continuación genera una sal criptográficamente fuerte como un Entero a partir de 4 bytes aleatorios creados criptográficamente.
private int GenerateSaltForPassword()
{
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] saltBytes = new byte[4];
rng.GetNonZeroBytes(saltBytes);
return (((int)saltBytes[0]) << 24) + (((int)saltBytes[1]) << 16) + (((int)saltBytes[2]) << 8) + ((int)saltBytes[3]);
}
La contraseña puede ser hash utilizando la sal con la función a continuación. La sal se concatena a la contraseña y luego se calcula el hash.
private byte[] ComputePasswordHash(string password, int salt)
{
byte[] saltBytes = new byte[4];
saltBytes[0] = (byte)(salt >> 24);
saltBytes[1] = (byte)(salt >> 16);
saltBytes[2] = (byte)(salt >> 8);
saltBytes[3] = (byte)(salt);
byte[] passwordBytes = UTF8Encoding.UTF8.GetBytes(password);
byte[] preHashed = new byte[saltBytes.Length + passwordBytes.Length];
System.Buffer.BlockCopy(passwordBytes, 0, preHashed, 0, passwordBytes.Length);
System.Buffer.BlockCopy(saltBytes, 0, preHashed, passwordBytes.Length, saltBytes.Length);
SHA1 sha1 = SHA1.Create();
return sha1.ComputeHash(preHashed);
}
Verificar la contraseña se puede hacer simplemente calculando el hash y luego comparándolo con el hash esperado.
private bool IsPasswordValid(string passwordToValidate, int salt, byte[] correctPasswordHash)
{
byte[] hashedPassword = ComputePasswordHash(passwordToValidate, salt);
return hashedPassword.SequenceEqual(correctPasswordHash);
}