Hasher de contraseña predeterminado de ASP.NET Identity: ¿cómo funciona y es seguro?

Resuelto André Snede asked hace 11 años • 6 respuestas

Me pregunto si el Password Hasher que está implementado de forma predeterminada en el UserManager que viene con MVC 5 y ASP.NET Identity Framework es lo suficientemente seguro. Y si es así, ¿podrías explicarme cómo funciona?

La interfaz IPasswordHasher se ve así:

public interface IPasswordHasher
{
    string HashPassword(string password);
    PasswordVerificationResult VerifyHashedPassword(string hashedPassword, 
                                                       string providedPassword);
}

Como puede ver, no requiere sal, pero se menciona en este hilo: " Hash de contraseña de identidad de Asp.net " que, de hecho, sí lo agrega detrás de escena. Entonces me pregunto ¿cómo se hace esto? ¿Y de dónde viene esta sal?

Mi preocupación es que la sal es estática, lo que la hace bastante insegura.

André Snede avatar Dec 17 '13 05:12 André Snede
Aceptado

Así es como funciona la implementación predeterminada ( ASP.NET Framework o ASP.NET Core ). Utiliza una función de derivación de claves con sal aleatoria para producir el hash. La sal se incluye como parte de la producción del KDF. Por lo tanto, cada vez que "hash" la misma contraseña obtendrás diferentes hashes. Para verificar el hash, la salida se divide en salt y el resto, y el KDF se ejecuta nuevamente con la contraseña con el salt especificado. Si el resultado coincide con el resto de la salida inicial, se verifica el hash.

Hash:

public static string HashPassword(string password)
{
    byte[] salt;
    byte[] buffer2;
    if (password == null)
    {
        throw new ArgumentNullException("password");
    }
    using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, 0x10, 0x3e8))
    {
        salt = bytes.Salt;
        buffer2 = bytes.GetBytes(0x20);
    }
    byte[] dst = new byte[0x31];
    Buffer.BlockCopy(salt, 0, dst, 1, 0x10);
    Buffer.BlockCopy(buffer2, 0, dst, 0x11, 0x20);
    return Convert.ToBase64String(dst);
}

Verificando:

public static bool VerifyHashedPassword(string hashedPassword, string password)
{
    byte[] buffer4;
    if (hashedPassword == null)
    {
        return false;
    }
    if (password == null)
    {
        throw new ArgumentNullException("password");
    }
    byte[] src = Convert.FromBase64String(hashedPassword);
    if ((src.Length != 0x31) || (src[0] != 0))
    {
        return false;
    }
    byte[] dst = new byte[0x10];
    Buffer.BlockCopy(src, 1, dst, 0, 0x10);
    byte[] buffer3 = new byte[0x20];
    Buffer.BlockCopy(src, 0x11, buffer3, 0, 0x20);
    using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, dst, 0x3e8))
    {
        buffer4 = bytes.GetBytes(0x20);
    }
    return ByteArraysEqual(buffer3, buffer4);
}
Andrew Savinykh avatar Dec 16 '2013 22:12 Andrew Savinykh

Debido a que hoy en día ASP.NET es de código abierto, puede encontrarlo en GitHub: AspNet.Identity 3.0 y AspNet.Identity 2.0 .

De los comentarios:

/* =======================
 * HASHED PASSWORD FORMATS
 * =======================
 * 
 * Version 2:
 * PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations.
 * (See also: SDL crypto guidelines v5.1, Part III)
 * Format: { 0x00, salt, subkey }
 *
 * 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.)
 */
Knelis avatar Dec 16 '2015 15:12 Knelis

Entiendo la respuesta aceptada y la voté a favor, pero pensé en dejar mi respuesta laica aquí...

Creando un hash

  1. La sal se genera aleatoriamente utilizando la función Rfc2898DeriveBytes que genera un hash y una sal. Las entradas a Rfc2898DeriveBytes son la contraseña, el tamaño del salt a generar y el número de iteraciones de hash a realizar. https://msdn.microsoft.com/en-us/library/h83s4e12(v=vs.110).aspx
  2. Luego, la sal y el hash se trituran (primero la sal seguida del hash) y se codifican como una cadena (de modo que la sal se codifica en el hash). Este hash codificado (que contiene la sal y el hash) luego se almacena (normalmente) en la base de datos contra el usuario.

Comprobar una contraseña con un hash

Para verificar una contraseña que ingresa un usuario.

  1. La sal se extrae de la contraseña hash almacenada.
  2. La sal se utiliza para codificar la contraseña ingresada por los usuarios mediante una sobrecarga de Rfc2898DeriveBytes que toma una sal en lugar de generar una. https://msdn.microsoft.com/en-us/library/yx129kfs(v=vs.110).aspx
  3. Luego se comparan el hash almacenado y el hash de prueba.

El hachís

Debajo de las sábanas, el hash se genera utilizando la función hash SHA1 ( https://en.wikipedia.org/wiki/SHA-1 ). Esta función se llama iterativamente 1000 veces (en la implementación de identidad predeterminada)

¿Por qué es esto seguro?

  • Las sales aleatorias significan que un atacante no puede usar una tabla de hash pregenerada para intentar descifrar contraseñas. Necesitarían generar una tabla hash para cada sal. (Suponiendo aquí que el hacker también haya comprometido su sal)
  • Si 2 contraseñas son idénticas, tendrán hashes diferentes. (lo que significa que los atacantes no pueden inferir contraseñas "comunes")
  • Llamar iterativamente a SHA1 1000 veces significa que el atacante también debe hacerlo. La idea es que, a menos que tengan tiempo en una supercomputadora, no tendrán recursos suficientes para forzar la contraseña desde el hash. Reduciría enormemente el tiempo para generar una tabla hash para una sal determinada.
Nattrass avatar Sep 06 '2017 08:09 Nattrass