Hash contraseñas antes de insertar en la base de datos

4

Durante las últimas 2 semanas he estado leyendo muchos blogs sobre seguridad de sitios web y contraseñas de hash.

Muchos sitios han mencionado las ventajas y desventajas de las diferentes maneras de hacer esto, lo que me ha dejado un poco confundido acerca de qué tan seguro es mi código.

Si es posible, ¿puede alguien ver el código a continuación y hacerme saber lo que piensan? He publicado esta pregunta en enlace pero todavía nadie ha respondido.

Los pasos que he tomado son los siguientes:

  1. Crear sal aleatoria
  2. Agregue sal aleatoriamente y envíe un correo electrónico para crear sal para la contraseña (el correo electrónico se cifrará en la base de datos)
  3. Hash contraseña y sal y almacenar en la base de datos

    public const int HashBytes = 128;
    public const int DefaultIterations = 10000;
    
        //Create random bytes for salt
        public static string SaltSHA256()
        {
            const int minSaltSize = 8;
            const int maxSaltSize = 16;
            var random = new Random();
            int saltSize = random.Next(minSaltSize, maxSaltSize);
            byte[] saltBytes = new byte[saltSize];
            var rng = new RNGCryptoServiceProvider();
            rng.GetNonZeroBytes(saltBytes);
            HashAlgorithm hash = new SHA256Managed();
            byte[] bytes = hash.ComputeHash(saltBytes);
            return Convert.ToBase64String(bytes);
        }
    
        //Create salt using email and public static string SaltSHA256()
        //Store email and public static string SaltSHA256() in database
        public static string SaltRfc2898(string email,string hashedSalt)
        {
            var salt = new Rfc2898DeriveBytes(email, Encoding.UTF8.GetBytes(hashedSalt), DefaultIterations);
            return Convert.ToBase64String(salt.GetBytes(HashBytes));
        }
    
        //Hash password and salt
        public static string PasswordHashRfc2898(string password,string salt)
        {
            var hashedPassword = new Rfc2898DeriveBytes(password, Encoding.UTF8.GetBytes(salt), DefaultIterations);
            return Convert.ToBase64String(hashedPassword.GetBytes(HashBytes));
        }
        //Get salt and password from database based on username
        //Salt in from data created by public static string SaltSHA256()
    
        public static bool DoPasswordsMatch(string password,string salt)
        {
            //Password would be pulled from db based on username
            byte[] testPassword = Convert.FromBase64String("basestring");
    
            //Salt would be pulled from database based on username
            var saltFromDatabase = salt;
    
            //Hash password and salt
            var hashUserInputAndSalt = PasswordHashRfc2898(password, saltFromDatabase);
    
            //Convert to byte[] ready for comparison
            byte[] convertUserInputFromBase64 = Convert.FromBase64String(hashUserInputAndSalt);
    
            //Compare and return true or false
            return convertUserInputFromBase64.SequenceEqual(testPassword);
    
        }
    
pregunta George Phillipson 05.09.2013 - 15:01
fuente

1 respuesta

9

Estás utilizando Rfc2898DeriveBytes , que implementa PBKDF2 : eso es bueno.

Usted está produciendo una salida de 128 bytes , que es una exageración total para la verificación de la contraseña. 128 bits , es decir, 16 bytes, son suficientes. 128 bytes no dañan, excepto que hacen que las cadenas más voluminosas se almacenen en su base de datos.

Estás usando 10000 iteraciones, lo que podría ser un poco bajo. Más alto es mejor. 10000 no es malo , pero es probable que su servidor pueda manejar diez veces más, y esto haría que los ataques sean diez veces más difíciles. Le sugiero que pruebe varios valores y haga que el recuento sea tan alto como sea tolerable en su sistema, en relación con la carga de trabajo esperada (es decir, la cantidad de usuarios conectados por segundo).

Los problemas de codificación se mantienen mejor separados, en el nivel de almacenamiento. La sal, la salida PBKDF2 y la contraseña son todas secuencias de bytes. Rfc2898DeriveBytes puede codificar una contraseña String para usted, pero espera que la sal sea "algunos bytes" y produce una salida que es "algunos bytes". Para el almacenamiento en la base de datos, utilice las capacidades de esa base de datos para procesar datos binarios o aplique alguna codificación, como Base64 . Por lo tanto, use Convert.FromBase64String() y Convert.ToBase64String() antes de escribir y después de leer, respectivamente. Su mezcla de Base64 y UTF-8 es confusa y hace que leer (auditar) su código sea una tarea más difícil.

Tu generación de sal es rara:

  • No tiene sentido hacer que la longitud de la sal sea variable. Solo ve por 16 bytes.
  • No tiene sentido evitar bytes de valor 0. Una sal es una secuencia de bytes , no de caracteres , y 0 no es especial.
  • No hay ningún punto en hash la sal.

Una sal adecuada para PBKDF2 es una secuencia de al menos 8 bytes, y única (tanto como sea posible). 16 bytes llenos con la salida de un PRNG fuerte son una buena manera de hacerlo. Un GUID también funcionaría (las sales no necesitan ser aleatorias, solo necesitan ser reutilizadas tan raramente como sea posible por los sistemas normales, y GUID hacen eso). En .NET, si desea generar un buen salt, simplemente haga eso:

byte[] salt = Guid.NewGuid().ToByteArray();

Alternativamente, Rfc2898DeriveBytes puede generar la sal para usted. Tras el registro del usuario o el cambio de contraseña, haga lo siguiente:

Rfc2898DeriveBytes h = new Rfc2898DeriveBytes(password, saltSize, iterations);
byte[] hashedPassword = h.GetBytes(hashSize);
byte[] salt = h.Salt;
// then store salt and hashedPassword in the database

y, tras la verificación:

byte[] salt = ... // read from database
Rfc2898DeriveBytes h = new Rfc2898DeriveBytes(password, salt, iterations);
byte[] hashedPassword = h.GetBytes(hashSize);
// compare hashedPassword with the stored value

Usted puede invocar un PRNG separado como RNGCryptoServiceProvider() y hacer las cosas usted mismo; eso es lo que haces, y aunque es raro , no es débil . Sin embargo, es más sencillo utilizar lo que el sistema ya proporciona. Un código más simple generalmente significa menos errores.

    
respondido por el Tom Leek 05.09.2013 - 15:33
fuente

Lea otras preguntas en las etiquetas