HMACSHA512 versus Rfc2898DeriveBytes para el hash de contraseña

11

Actualmente estamos utilizando HMACSHA512 en .net, con una clave de validación 128Char (64bytes) La sal es de 64 caracteres generados aleatoriamente cadena. Asignamos 2048 de longitud en la base de datos para el resultado de la cadena hash base64. Será un sitio web público. ¿Es razonable este enfoque o debería cambiarse a otro enfoque como Rfc2898DeriveBytes?

 public string HashEncode(string password, string salt, string validationKey) {
        byte[] hashKey = BosUtilites.HexStringToByteArray(validationKey);
        var sha512 = new HMACSHA512(hashKey);
        var hashInput = BosUtilites.StringToByteArray(password + salt);
        byte[] hash = sha512.ComputeHash(hashInput);
        return Convert.ToBase64String(hash);
    }

 public string GenerateSimpleSalt(int Size = 64) {
        var alphaSet = new char[64]; // use 62 for strict alpha... that random generator for alphas only
        //nicer results with set length * int i = 256. But still produces excellent random results.
        //alphaset plus 2.  Reduce to 62 if alpha requried
        alphaSet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890#=".ToCharArray();
        var tempSB = GenerateRandomString(Size, alphaSet);
        return tempSB.ToString();
    }

    public StringBuilder GenerateRandomString(int Size, char[] alphaSet) {
        using (var crypto = new RNGCryptoServiceProvider()) {
            var bytes = new byte[Size];
            crypto.GetBytes(bytes); //get a bucket of very random bytes
            var tempSB = new StringBuilder(Size);
            foreach (var b in bytes) { // use b , a random from 0-255 as the index to our source array. Just mod on length set
                tempSB.Append(alphaSet[b%(alphaSet.Length)]);
            }

            return tempSB;
        }

EDIT2: en caso de que alguien encuentre esto a través de google, he incluido las lecciones aprendidas El promedio de muestra en las pruebas en estaciones de trabajo fue de alrededor de 300 ms. Esto no debe ser demasiado notable durante el inicio de sesión. Y ya no hay necesidad de una clave de validación. Que es un alivio :-)

 SCrypt package installed via nuget. and rfc2898 PBKDF2 changed to be large number or iterations but only 20bytes output.  SAme CPU time.

Las contraseñas nuevas están codificadas en SCRYPT de forma predeterminada,

  <package id="CryptSharpOfficial" version="2.0.0.0" targetFramework="net451" />
  // save salt, hash algorithm used and resulting encoding on user record
  public string PasswordEncode(string password, byte[] salt, HashAlgorithm hashAlgorithm ) {
        switch (hashAlgorithm) {
            case HashAlgorithm.PBKDF2:
                    var deriver2898 = new Rfc2898DeriveBytes(password, salt,<Use a number around 50K>); // approx 300msecs on workstation
                    byte[] hash = deriver2898.GetBytes(20); // 
                    return Convert.ToBase64String(hash);
            case HashAlgorithm.Scrypt:
                var key = Encoding.UTF8.GetBytes(password);
                byte[] hashScrypt =  SCrypt.ComputeDerivedKey(key: key, salt: salt, 
                                    cost: 65536, // must be a power of 2 !, on PC, singlethread this is approx 1 sec
                                    blockSize: 8, 
                                    parallel: 1,
                                    maxThreads: 1, 
                                    derivedKeyLength: 128);

                    return Convert.ToBase64String(hashScrypt);
            default:
                throw new ArgumentOutOfRangeException("hashAlgorithm");
        }
    }
    
pregunta soadyp 02.05.2013 - 18:53
fuente

4 respuestas

14

Rfc2898DeriveBytes implementa PBKDF2 : una función que convierte una contraseña (con un salt) en una secuencia de longitud arbitraria de bytes. PBKDF2 se usa a menudo para el hashing de contraseñas (es decir, para calcular y almacenar un valor que es suficiente para verificar una contraseña) porque tiene las características necesarias para las funciones de hashing de contraseñas: a salt y lentitud configurable .

Estas características son necesarias porque las contraseñas son débiles : encajan en el cerebro humano. Como tales, son vulnerables a una búsqueda exhaustiva: es posible, en general, enumerar la mayoría de las contraseñas que los usuarios humanos encontrarán y recordarán. El ataque asume que el atacante obtuvo una copia de la sal y la contraseña hash, y luego "intentará las contraseñas" en su propia máquina. Eso se llama un ataque de diccionario sin conexión .

En su caso, tiene un tercer elemento: una clave de validación . Es una clave , es decir, supuestamente secreta. Si el atacante podría agarrar las sales y las contraseñas con hash, pero no la clave de validación, entonces no puede realizar el ataque de diccionario en sus propias máquinas; en estas condiciones (la clave de validación permanece secreta y el algoritmo de validación es robusto, HMAC / SHA-512 está bien para eso), la lentitud configurable de PBKDF2 no es necesaria. Este tipo de validación con una clave secreta a veces se llama "salpicadura".

Tenga en cuenta, sin embargo, que cuando asumimos que el atacante podría agarrar una copia de las contraseñas con hash, entonces se convierte en una cuestión de delicadeza suponer que la clave permaneció inmaculada por sus miradas viles. Esto depende del contexto. La mayoría de los ataques de inyección SQL podrán leer parte de toda la base de datos, pero no el resto de los archivos en la máquina. Sin embargo, su servidor debe poder arrancar y arrancar de alguna manera sin intervención humana, por lo que la clave de validación está en algún lugar del disco. Un atacante que robe todo el disco (o una cinta de respaldo ...) también obtendrá la clave de validación, momento en el que volverá a la necesidad de una lentitud configurable.

En general, recomendaría PBKDF2 (también conocido como Rfc2898DeriveBytes en .NET) sobre una construcción personalizada, aunque debo decir que parece que usa HMAC correctamente (las construcciones caseras rara vez alcanzan ese nivel de corrección). Si insiste en tener una "clave de validación" (y está listo para asumir la sobrecarga de procedimiento de la administración de claves, por ejemplo, copias de seguridad especiales para esa clave), sugiero usar PBKDF2 y luego aplicar HMAC en el Salida PBKDF2.

Consulte esta respuesta para una discusión detallada sobre la contraseña hashing.

    
respondido por el Tom Leek 02.05.2013 - 19:26
fuente
9

Estoy respondiendo de manera específica al EDIT de las lecciones aprendidas en la pregunta original.

  

Se llama a Rfc2898DeriveBytes con 1000 iteraciones. utilizando 1024 bytes de salida. El tamaño de la contraseña en Db fue diseñado 2k afortunadamente. La muestra promedio en las pruebas en las estaciones de trabajo fue de alrededor de 300 msegs

Resumen rápido: si le gusta su carga actual de CPU y Rfc2898DeriveBytes, cambie de 1000 iteraciones y una salida de 1024 bytes a 52000 iteraciones y una salida de 20 bytes (20 bytes es la salida nativa de SHA-1, que es qué. NET 4.5 Rfc2898DeriveBytes se basa en).

La explicación de por qué, incluidas las referencias:

Para evitar darle a los atacantes una ventaja sobre usted, NO use un PBKDF2 / RFC2898 / PKCS # 5 (o un HMAC, que se usa internamente en PBKDF2 y otros) con un tamaño de salida mayor que la salida nativa del hash Función utilizada. Desde la implementación de .NET (hasta 4.5) de los códigos de disco duro SHA-1, debe usar un máximo de 160 bits de salida, no 8192 bits de salida.

El motivo de esto es que, como nos referimos a la especificación RFC2898 , si el tamaño de salida (dkLen, es decir, longitud de clave derivada) es mayor que el tamaño de salida de hash nativo (hLen, es decir, longitud de hash). En la página 9, vemos

Paso 2: "Sea l el número de bloques hLen-octet en la clave derivada,          redondeo "

Paso 3: " T_1 = F (P, S, c, 1), T_2 = F (P, S, c, 2), ... T_l = F (P, S, c, l), "

Y en la página 10: Paso 4: "DK = T_1 || T_2 || ... || T_l < 0..r-1 >" donde DK es la clave derivada (salida PBKDF2) y || es el operador de concatenación.

Por lo tanto, podemos ver que para su clave de 8192 bits y HMAC-SHA-1 de .NET, tenemos 8192/160 = 51.2 bloques y CEIL (51.2) = 52 bloques necesarios para PBKDF2, es decir, T_1 a T_52 (l = 52). Las referencias específicas a lo que sucede con el .2 de manera diferente a un bloque completo están fuera del alcance de esta discusión (sugerencia: truncamiento después de que se calcule el resultado completo).

Por lo tanto, estás ejecutando un conjunto de 1000 iteraciones en tu contraseña un total de 52 veces, y concatenando la salida. Por lo tanto, para una contraseña, ¡en realidad estás ejecutando 52000 iteraciones!

Un atacante inteligente ejecutará solo 1000 iteraciones y comparará su resultado de 160 bits con los primeros 160 bits de sus 8192 bits de salida. Si falla, es una suposición errónea, siga adelante. Si tiene éxito, es casi seguro que es una suposición exitosa (ataque).

Por lo tanto, está ejecutando 52,000 iteraciones en una CPU, y un atacante está ejecutando 1,000 iteraciones en lo que sea que tenga (probablemente algunas GPU, que están superando ampliamente su CPU para SHA-1 en primer lugar); le ha dado a los atacantes una ventaja de 52: 1 por encima de las ventajas de hardware.

Afortunadamente, una buena función PBKDF2 es fácil de ajustar; simplemente cambie su longitud de salida a 160 bits y su número de iteraciones a 52,000, y usará la misma cantidad de tiempo de CPU, almacenará claves más pequeñas y ¡resultará 52 veces más caro para cualquier atacante sin costo de ejecución para usted!

Si desea seguir atacando nerf con GPU, puede cambiar a PBKDF2-HMAC-SHA-512 (o scrypt o bcrypt) y un tamaño de salida de 64 bytes o menos (el tamaño de salida nativo de SHA- 512), que reduce significativamente la cantidad de ventaja que tienen las GPU actuales (a principios de 2014) sobre las CPU debido a que las instrucciones de 64 bits están en las CPU pero no en las GPU. Sin embargo, esto no está disponible de forma nativa en .NET 4.5.

  • Sin embargo, @Jither creó un buen ejemplo de agregar las capacidades PBKDF2-HMAC-SHA256, PBKDF2-HMAC-SHA384, PBKDF2-HMAC-SHA512, etc. a .NET, y he incluido una variante con un conjunto razonable de vectores de prueba en mi repositorio Github para referencia.

Para otra referencia relacionada con una falla de diseño real en 1Password, consulte este hilo del foro Hashcat - "Para cada La iteración de PBKDF2-HMAC-SHA1 se llama 4 veces la transformación SHA1. Pero esto es solo para producir una clave de 160 bits. Para producir la clave de 320 bits requerida, la llamas 8 veces ".

P.S. si así lo desea, para un pequeño cambio de diseño, puede ahorrar un poco más de espacio en la base de datos si almacena la salida en una columna VARBINARY o BINARY en lugar de hacer la codificación Base64.

P.P.S. es decir, cambie el código de prueba en su edición de la siguiente manera (2000 * 52 = 104000); tenga en cuenta que su texto dijo 1000 y su prueba enumeró 2000, por lo tanto, texto a texto y código a código, así que muévase.

//var hash = libCrypto.HashEncode2(password, salt, 2000);
var hash = libCrypto.HashEncode2(password, salt, 104000);
// skip down into HashEncode2
//byte[] hash = deriver.GetBytes(1024);
byte[] hash = deriver.GetBytes(20);
    
respondido por el Anti-weakpasswords 13.02.2014 - 05:46
fuente
4

La familia SHA2 no es una buena opción para el almacenamiento de contraseñas. Es significativamente mejor que md5, pero realmente deberías usar bcrypt (o scrypt!).

RNGCryptoServiceProvider es una buena fuente de entropía. Idealmente, una sal no es base 64, sino base 256, como en un byte completo. Para entender esto mejor, necesitas saber cómo se generan las tablas de arco iris. La entrada a la generación de tablas del arco iris requiere un espacio de teclas. Por ejemplo, se podría generar un arco iris para buscar: símbolo alfanumérico de 7 a 12 caracteres de longitud. Ahora para romper este esquema de sal propuesto, el atacante tendría que generar un símbolo alfanumérico con 71-76 caracteres para compensar la sal de 64 caracteres (que es grande). Al convertir la sal en un byte completo, aumentaría considerablemente el espacio de teclas que la tabla del arco iris tendría que agotar.

    
respondido por el rook 02.05.2013 - 19:26
fuente
0

Hay, creo, un error en la función RandomString de la pregunta (además de un par de problemas de sintaxis menores). Tiene un sesgo siempre que el parámetro Longitud no sea un factor de 256. Si la Longitud fuera 62, como sugiere el código como opción, 'a' y 'b' aparecerían con más frecuencia que cualquier otro carácter (en lugar de un bonito punto 1). La distribución / 62 para todos los caracteres, ayb se produciría dos veces más que los otros caracteres, 2/64. Los 60 caracteres restantes aparecerán 1/64).

Debido a que existe un sesgo, existe una mayor probabilidad de generar sales idénticas. No es muy probable, pero está ahí. Sin embargo, tal como está, si usas los 64 caracteres, está bien.

    
respondido por el stevieg 22.01.2015 - 02:10
fuente

Lea otras preguntas en las etiquetas