2010-12-01 237 views
40

Estoy escribiendo un formulario de registro para una aplicación pero todavía tengo problemas con ser nuevo en C#.Contraseñas hash con MD5 o sha-256 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?

+4

¿Qué vas a hacer con la contraseña hash? Almacenarlo en una base de datos? Entonces simplemente hashing no es suficiente ([Rainbow table] (http://en.wikipedia.org/wiki/Rainbow_table)). Use sales. – dtb

+2

Voy a almacenarlo en una base de datos. Que recomiendas. – Sean

+1

Personas, tenga en cuenta que las respuestas a continuación están desactualizadas. Y tenga en cuenta que nunca más debe usar MD5 para la contraseña hash. Es viejo, roto y obsoleto. La solución más fácil, actualizada y segura para la mayoría de las personas sería [bcrypt] (https://bcrypt.codeplex.com/). Al menos cuando se escribió este comentario :) Argon2 probablemente será la recomendación estándar pronto. – Sammi

Respuesta

47

Querrá utilizar el espacio de nombre System.Security.Cryptography; específicamente, el MD5 class o el SHA256 class.

Dibujo un poco del código en this page, y con el conocimiento de que ambas clases tienen la misma clase base (HashAlgorithm), se puede utilizar 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 se podría llamar así esto (por MD5):

string hPassword = ComputeHash(password, new MD5CryptoServiceProvider()); 

O para SHA256:

string hPassword = ComputeHash(password, new SHA256CryptoServiceProvider()); 

Editar: Adición de compatibilidad Sal
Como DTB se señala en los comentarios, este código sería más fuerte si se incluye la posibilidad de añadir 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 del diccionario contra una contraseña hash (por ejemplo, usando un rainbow table). Aquí está una versión modificada de la función ComputeHash que soporta la 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); 
} 

Hope esto ha sido útil!

+0

OP tiene una cadena y quiere el hash de esa cadena como una cadena. Las clases de criptografía tratan solo con matrices de bytes. – dtb

+0

'ASCIIEncoding.Default' devuelve confusamente la codificación UTF-16. Sugiero 'Encoding.UTF8' (ASCII sería demasiado limitado). – dtb

+1

Si ahora también agrega un parámetro 'byte [] salt' a su método' ComputeHash' y antepone esos bytes a 'inputBytes' entonces amo oficialmente su respuesta :-) – dtb

2

Si se va a almacenar las contraseñas hash, utilice 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: Enough With The Rainbow Tables: What You Need To Know About Secure Password Schemes y este answer a una pregunta anterior de SO.

algunas citas del artículo:

El problema es que MD5 es rápida. 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 desea en una función de hash de contraseña.


Por último, hemos aprendido que si queremos almacenar contraseñas de forma segura, tenemos tres opciones razonables: Esquema de PHK MD5, Bcrypt esquema de Provos-Maziere, y SRP. Aprendimos que la elección correcta es Bcrypt.

+0

Bueno, las contraseñas se almacenarán en una base de datos después del hash. No almacenado localmente en absoluto. – Sean

+2

Ok, entonces cada contraseña debe ser hash con una sal única y debe usar bcrypt, o algo similar. Hashing con una sal detiene los ataques utilizando una tabla de arcoiris, y el uso de un algoritmo más lento aumenta el esfuerzo para un ataque de fuerza bruta contra las contraseñas hash. –

+0

No recomendaría el uso de bcrypt en .NET, StackOverflow abandonó bcrypt para usar PBKDF2. Vea los comentarios de Kevin Montrose en el blog de StackOverflow http://blog.stackoverflow.com/2011/05/stack-exchange-is-an-openid-provider/ –

75

No utilice un hash simple, o incluso un hash salado. Use algún tipo de técnica de fortalecimiento de claves como bcrypt (con .NET implementation here) o PBKDF2 (con built-in implementation).

Aquí hay un ejemplo de uso de PBKDF2.

Para generar una clave de la 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 a continuación, 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!"); 
} 
+3

Supongo que en su publicación en la que dice ** de ** PBKDF2, quiso decir ** O **. No recomendaría el uso de bcrypt en .NET, StackOverflow abandonó bcrypt para usar PBKDF2. Vea los comentarios de Kevin Montrose en el blog de StackOverflow http://blog.stackoverflow.com/2011/05/stack-exchange-is-an-openid-provider/ –

+1

@Chris: Sí, quise decir "o PBKDF2"; actualizado. Yo personalmente iría por PBKDF2 también, aunque no tengo conocimiento de ninguna razón fuerte para no usar bcrypt. (Solo he oído decir cosas buenas al respecto.) – LukeH

+2

El problema con bcrypt es que no hay una implementación de .NET que haya sido verificada, esta es la razón por la cual StackOverflow decide dejar de usar el bcrypt. No desearon gastar los recursos para verificar la implementación, mientras que la implementación nativa PBKDF2 incluida en .NET ya se ha verificado para Microsoft. –

5

siempre se debe salar la contraseña antes de hash cuando almacenándolos en la base de datos.

columnas de base de datos recomendados:

  • PasswordSalt: int
  • PasswordHash: binario (20)

La mayoría de los mensajes que usted encuentra en línea hablará de ASCII que codifica la sal y el hachís, pero que las no es necesario y solo agrega cálculos 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 se detallan algunas funciones que generarán la sal, calcularán el hash y verificarán el hash con 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 se puede aplicar con la función siguiente. 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); 
} 

La comprobación de 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); 
} 

+4

-1 para SHA-1 liso. Use un hash lento, como PBKDF2, bcrypt o scrypt. También se recomienda usar sal de 64 bits, pero ese es un problema menor. – CodesInChaos

2

Aquí es una implementación completa de una persistencia conscientes clase SecuredPassword

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 exámenes:

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); 
    } 
} 
1

utilice este, ya que tienen los mismos problemas que antes, pero podría resolver será fragmento de código en el código litle

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(); 

    } 
+0

Debe agregar una explicación y ver [¿Cómo hago una buena pregunta?] (Https://stackoverflow.com/help/how-to-ask) – CodingNinja

0

TL; DR use Microsoft.AspNetCore.Cryptography.KeyDerivation, implementando PBKDF2 con SHA-512.

La buena idea para empezar a usar hash de contraseñas es observar qué dice OWASP guidelines. 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 desde hace 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 hash 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 ataques acelerados de GPU y FPGA, pero he escuchado que resultó ser menos fuerte que originalmente
  • PBKDF2 es una familia de algoritmos parametrizados por las diferentes funciones de 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 solo en algoritmos, probablemente iría con bcrypt, siendo PBKDF2 el menos favorable.

Sin embargo, no es la historia completa, porque incluso el mejor algoritmo puede quedar inseguro por una mala implementación. Veamos lo que está 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 Stack Overflow dejó de usar bcrypt por estos motivos
  • Probablemente la mejor forma de utilizar Argon2 sea a través de enlaces a la conocida biblioteca libsodium , p. Ej. https://github.com/adamcaudill/libsodium-net. La idea es que la mayor parte de la criptografía se implementa a través de libsodium, que tiene un considerable soporte de , y las partes "no probadas" son bastante limitadas.Sin embargo, en detalles de criptografía significan mucho, por lo que combinado con ARGON2 siendo relativamente reciente, me tratan como una opción experimental
  • Durante mucho tiempo, .NET tenía un sistema incorporado en una implementación de PBKDF2 través Rfc2898DeriveBytes clase. 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 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 aplicación suministrado por Microsoft triunfa sobre la que, por lo que la decisión razonable sería utilizar Microsoft.AspNetCore.Cryptography.KeyDerivation.

El paquete reciente en este momento está destinado a .NET Standard 2.0, por lo que está disponible en .NET Core 2.0 o .NET Framework 4.6.1 o posterior. Si utiliza la versión anterior del marco, es posible usar la versión anterior del paquete, 1.1.3, que apunta 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 docs.microsoft.com. 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, tiene que elegir la longitud de salida del hash de la contraseña y la longitud de la sal. 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.

A continuación, 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 inferior a 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 olvide idear un almacenamiento de hash de contraseña de forma que permita avanzar sin problemas a un mejor algoritmo de hash en el futuro.

Cuestiones relacionadas