Forma correcta de cifrar las contraseñas de los usuarios

5

Tengo un escenario en el que el usuario debe guardar las contraseñas para usarlas más adelante. No puedo usar el hash porque necesito obtener la contraseña original para su uso posterior.

Por ejemplo, considere una aplicación de remitente de correo electrónico donde un usuario ingresaría inicialmente una contraseña para su cuenta de correo electrónico y la aplicación la almacenaría en forma cifrada y cuando la aplicación necesita enviar un correo electrónico, descifrará la contraseña y la usará para enviar el correo.

Ahora, esto es lo que estoy haciendo:

  1. La aplicación almacena una clave maestra (generada mediante RNGCryptoServiceProvider) en la base de datos
  2. Tanto la Clave como la IV para AES se derivan de la clave maestra usando Rfc2898DeriveBytes (contraseña = clave maestra y salt = user_id para Rfc2898DeriveBytes)

    // the initial, one time masterkey generation, which will be used for all passwords
    byte[] masterKeyBytes = new byte[32];
    new RNGCryptoServiceProvider().GetBytes(masterKeyBytes);
    // masterkey is saved in database
    string masterKey = Convert.ToBase64String(masterKeyBytes);
    
    // password encryption of a user's password
    var derivative = new Rfc2898DeriveBytes(masterKey, 128_bit_guid_of_user_id, numberOfIterations)
    var aes = new AesCryptoServiceProvider();
    aes.Key = derivative.GetBytes(32);
    aes.IV = derivative.GetBytes(16);
    var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
    // get the encrypted password from encryptor
    .....
    .....
    

¿Estoy haciendo esto de la manera correcta? ¿Hay correcciones por hacer? Además, ¿son los tamaños que se utilizan (para el tamaño de clave, tamaño de clave maestra, etc.) lo suficientemente buenos?

Algunas preguntas más (solo para saber más):

  1. ¿Se considera que las claves derivadas (claves AES separadas para cada usuario) de una clave maestra de alta entropía no son dignas (o peor)? (Es decir, usar solo la clave maestra como clave AES es más que suficiente). Si es así, ¿por qué ?, mi argumento es: si alguien deduce la clave AES para un usuario en particular, él / ella no podrá deducir la clave maestra, por lo que otros usuarios estarán seguros. Encontré: "Diversificación de claves", que se utiliza en algunas aplicaciones de tarjetas inteligentes en las que obtienen una clave AES separada de una clave maestra para cada instancia (¿no se aplica lo mismo aquí?)

  2. ¿Podemos simplemente usar PBKDF para derivar claves en este escenario, o deberíamos usar un KBKDF? Si es así, ¿podría mencionar una biblioteca (preferiblemente de .NET framework) que ofrece KBKDF? Sé que RfcDerivativeBytes usa un PBKDF para derivar una clave, pero no pude averiguar cómo derivar una clave usando KBKDF.

pregunta userx 24.09.2015 - 13:08
fuente

1 respuesta

9

Cuando cifras contraseñas, es porque temes las escuchas ilegales; imaginas un atacante que pueda ver los contenidos de tu base de datos. El cifrado es la herramienta adecuada para eso, pero si coloca la clave de cifrado en la misma base de datos, simplemente hizo lo equivalente a ocultar la llave de su puerta debajo del tapete. Un atacante que puede echar un vistazo a la base de datos generalmente también puede obtener la clave de esa manera. Generalmente, cuando se filtran los contenidos de la base de datos, es a través de un método que impacta la base de datos completa (los ataques de inyección de SQL más exitosos permiten la extracción de datos arbitrarios; lo mismo se aplica, naturalmente, a las copias de seguridad de bases de datos perdidas / robadas). Por lo tanto, tendría más sentido almacenar la clave maestra en un lugar distinto de la base de datos, para limitar el riesgo de exposición tanto de la clave como de la contraseña cifrada.

Ya que aparentemente estás usando C # y .NET, deduzco un sistema de Windows y, por lo tanto, puedes usar DPAPI , a través de Clase ProtectedData .

Derivar la clave de cifrado real y la IV de la misma clave maestra, combinada con un valor específico de la instancia, es una buena cosa, siempre que use un Función de derivación de claves . Sin embargo, el valor de "aleatorización" adicional que utiliza es un identificador específico del usuario; esto significa que si cambia los datos cifrados para un usuario determinado (por ejemplo, el usuario cambia su contraseña), tanto la contraseña antigua como la nueva se cifrarán con la misma clave y el mismo IV. Para la mayoría de los sistemas de encriptación, la reutilización IV es un pecado.

En su lugar, debe usar un valor aleatorio que regenere cada vez que cifre un nuevo dato y almacene junto con el valor cifrado. Puede haber varios métodos para eso; lo más sencillo es utilizar la "clave maestra" como la clave de cifrado real para AES y generar un IV aleatorio cada vez que desee cifrar algo; El resultado cifrado será la concatenación de la IV y los datos cifrados, en ese orden.

Recuerda que la IV no es un secreto; de lo contrario, lo llamaríamos una clave . Por lo tanto, no es un problema si se pone a disposición de los atacantes. Sin embargo, cada sistema de encriptación tiene sus propios requisitos sobre el IV; en particular, un cifrado de bloque (como AES) en CBC mode necesita que el IV sea aleatorio generado con una fuente aleatoria fuerte ( AesCryptoServiceProvider por defecto a CBC). Por lo tanto, genere el IV con una instancia RNGCryptoServiceProvider .

Hablando del KDF, usas Rfc2898DeriveBytes , que es una exageración. Esa clase implementa PBKDF2 , que es un KDF basado en contraseña . Las "contraseñas" son especiales porque son compatibles con los cerebros humanos, y esto implica una debilidad inherente. Los ataques de fuerza bruta funcionan contra contraseñas (entonces los llamamos "ataques de diccionario"). Para mejorar la resistencia, los KDF basados en contraseña se hacen lentos con muchas repeticiones repetidas. Desafortunadamente, esto también hace que el KDF sea costoso de usar.

Debido a que su secreto maestro no es una contraseña, sino una clave aleatoria apropiadamente generada con una fuerte fuente de aleatoriedad, no es vulnerable a las fuerzas brutas. El costo de PBKDF2 es, por lo tanto, un desperdicio de ciclos de CPU. Si aún desea usar PBKDF2 para derivar claves de cifrado y / o IV, debe establecer el número de iteraciones en 1. Alternativamente, como se explicó anteriormente, no use un KDF en absoluto: cifre los datos con la clave maestra "como es ", pero con un IV aleatorio específico de la instancia que almacena junto con el resultado del cifrado.

AesCryptoServiceProvider se basa en la implementación del código nativo subyacente. RijndaelManaged está escrito en ".NET puro". Para cifrar elementos cortos, RijndaelManaged puede ser más eficiente; también reducirá el riesgo de fugas en el manejo si olvida llamar al método Dispose() cuando sea necesario. Por lo general, el rendimiento realmente no importa (el tiempo de cifrado y descifrado será insignificante con respecto al resto de lo que hace su servidor), pero si lo hace en su caso, recuerde que tiene varias opciones para la implementación.

Una clave de 32 bytes (256 bits) para AES es un poco excesiva, pero no dañará mucho. Una clave de 256 bits implica un costo de CPU de + 40% para el cifrado y descifrado, en comparación con una clave de 128 bits, pero para los elementos pequeños cifrados debe ser insignificante.

    
respondido por el Tom Leek 24.09.2015 - 14:53
fuente

Lea otras preguntas en las etiquetas