Saltar al contenido

Contraseñas hash y salt en C#

Este dilema se puede solucionar de diferentes formas, pero nosotros te enseñamos la que para nosotros es la resolución más completa.

Solución:

En realidad, esto es un poco extraño, con el string conversiones: lo que hace el proveedor de membresía para colocarlos en archivos de configuración. Los hash y salts son blobs binarios, no necesita convertirlos en cadenas a menos que quiera ponerlos en archivos de texto.

En mi libro, Begining ASP.NET Security, (oh, finalmente, una excusa para mejorar el libro) hago lo siguiente

static byte[] GenerateSaltedHash(byte[] plainText, byte[] salt)

  HashAlgorithm algorithm = new SHA256Managed();

  byte[] plainTextWithSaltBytes = 
    new byte[plainText.Length + salt.Length];

  for (int i = 0; i < plainText.Length; i++)
  
    plainTextWithSaltBytes[i] = plainText[i];
  
  for (int i = 0; i < salt.Length; i++)
  
    plainTextWithSaltBytes[plainText.Length + i] = salt[i];
  

  return algorithm.ComputeHash(plainTextWithSaltBytes);            

La generación de sal es como el ejemplo en la pregunta. Puede convertir texto en matrices de bytes usando Encoding.UTF8.GetBytes(string). Si debe convertir un hash a su string representación que puede utilizar Convert.ToBase64String y Convert.FromBase64String para volver a convertirlo.

Debe tener en cuenta que no puede usar el operador de igualdad en matrices de bytes, verifica las referencias y, por lo tanto, simplemente debe recorrer ambas matrices verificando cada byte así

public static bool CompareByteArrays(byte[] array1, byte[] array2)

  if (array1.Length != array2.Length)
  
    return false;
  

  for (int i = 0; i < array1.Length; i++)
  
    if (array1[i] != array2[i])
    
      return false;
    
  

  return true;

Siempre use una nueva sal por contraseña. Las sales no tienen que mantenerse en secreto y pueden almacenarse junto con el propio hachís.

Lo que dijo blowdart, pero con un poco menos de código. Utilice Linq o CopyTo para concatenar arreglos.

public static byte[] Hash(string value, byte[] salt)

    return Hash(Encoding.UTF8.GetBytes(value), salt);


public static byte[] Hash(byte[] value, byte[] salt)

    byte[] saltedValue = value.Concat(salt).ToArray();
    // Alternatively use CopyTo.
    //var saltedValue = new byte[value.Length + salt.Length];
    //value.CopyTo(saltedValue, 0);
    //salt.CopyTo(saltedValue, value.Length);

    return new SHA256Managed().ComputeHash(saltedValue);

Linq también tiene una manera fácil de comparar sus matrices de bytes.

public bool ConfirmPassword(string password)

    byte[] passwordHash = Hash(password, _passwordSalt);

    return _passwordHash.SequenceEqual(passwordHash);

Sin embargo, antes de implementar algo de esto, echa un vistazo a esta publicación. Para el hashing de contraseñas, es posible que desee un algoritmo hash lento, no uno rápido.

Para ello existe la Rfc2898DeriveBytes class que es lenta (y se puede hacer más lenta), y puede responder a la segunda parte de la pregunta original en el sentido de que puede tomar una contraseña y sal y devolver un hash. Consulte esta pregunta para obtener más información. Tenga en cuenta que Stack Exchange está utilizando Rfc2898DeriveBytes para hash de contraseña (código fuente aquí).

He estado leyendo que las funciones de hashing como SHA256 no estaban realmente diseñadas para almacenar contraseñas: https://patrickmn.com/security/storing-passwords-securely/#notpasswordhashes

en cambio adaptativo key funciones de derivación como PBKDF2, bcrypt o scrypt fueron. Aquí hay uno basado en PBKDF2 que Microsoft escribió para PasswordHasher en su biblioteca Microsoft.AspNet.Identity:

/* =======================
 * HASHED PASSWORD FORMATS
 * =======================
 * 
 * Version 3:
 * PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations.
 * Format:  0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey 
 * (All UInt32s are stored big-endian.)
 */

public string HashPassword(string password)

    var prf = KeyDerivationPrf.HMACSHA256;
    var rng = RandomNumberGenerator.Create();
    const int iterCount = 10000;
    const int saltSize = 128 / 8;
    const int numBytesRequested = 256 / 8;

    // Produce a version 3 (see comment above) text hash.
    var salt = new byte[saltSize];
    rng.GetBytes(salt);
    var subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested);

    var outputBytes = new byte[13 + salt.Length + subkey.Length];
    outputBytes[0] = 0x01; // format marker
    WriteNetworkByteOrder(outputBytes, 1, (uint)prf);
    WriteNetworkByteOrder(outputBytes, 5, iterCount);
    WriteNetworkByteOrder(outputBytes, 9, saltSize);
    Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length);
    Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length);
    return Convert.ToBase64String(outputBytes);


public bool VerifyHashedPassword(string hashedPassword, string providedPassword)

    var decodedHashedPassword = Convert.FromBase64String(hashedPassword);

    // Wrong version
    if (decodedHashedPassword[0] != 0x01)
        return false;

    // Read header information
    var prf = (KeyDerivationPrf)ReadNetworkByteOrder(decodedHashedPassword, 1);
    var iterCount = (int)ReadNetworkByteOrder(decodedHashedPassword, 5);
    var saltLength = (int)ReadNetworkByteOrder(decodedHashedPassword, 9);

    // Read the salt: must be >= 128 bits
    if (saltLength < 128 / 8)
    
        return false;
    
    var salt = new byte[saltLength];
    Buffer.BlockCopy(decodedHashedPassword, 13, salt, 0, salt.Length);

    // Read the subkey (the rest of the payload): must be >= 128 bits
    var subkeyLength = decodedHashedPassword.Length - 13 - salt.Length;
    if (subkeyLength < 128 / 8)
    
        return false;
    
    var expectedSubkey = new byte[subkeyLength];
    Buffer.BlockCopy(decodedHashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length);

    // Hash the incoming password and verify it
    var actualSubkey = KeyDerivation.Pbkdf2(providedPassword, salt, prf, iterCount, subkeyLength);
    return actualSubkey.SequenceEqual(expectedSubkey);


private static void WriteNetworkByteOrder(byte[] buffer, int offset, uint value)

    buffer[offset + 0] = (byte)(value >> 24);
    buffer[offset + 1] = (byte)(value >> 16);
    buffer[offset + 2] = (byte)(value >> 8);
    buffer[offset + 3] = (byte)(value >> 0);


private static uint ReadNetworkByteOrder(byte[] buffer, int offset)
 ((uint)(buffer[offset + 1]) << 16)
        

Tenga en cuenta que esto requiere la instalación del paquete nuget Microsoft.AspNetCore.Cryptography.KeyDerivation, que requiere .NET Standard 2.0 (.NET 4.6.1 o superior). Para versiones anteriores de .NET, consulte la clase Crypto de la biblioteca System.Web.Helpers de Microsoft.

Actualización de noviembre de 2015

Respuesta actualizada para usar una implementación de una biblioteca de Microsoft diferente que usa hash PBKDF2-HMAC-SHA256 en lugar de PBKDF2-HMAC-SHA1 (tenga en cuenta que PBKDF2-HMAC-SHA1 aún es seguro si iterCount es lo suficientemente alto). Puede consultar la fuente de la que se copió el código simplificado, ya que en realidad maneja la validación y actualización de los hashes implementados desde la respuesta anterior, útil si necesita aumentar iterCount en el futuro.

Tienes la opción de sostener nuestra tarea dejando un comentario o puntuándolo te estamos agradecidos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *