Actualización de hashing de contraseñas sin forzar una nueva contraseña para usuarios existentes

32

Mantiene una aplicación existente con una base de usuarios establecida. Con el tiempo se decide que la técnica actual de hashing de contraseñas está desactualizada y debe actualizarse. Además, por razones de UX, no desea que los usuarios existentes se vean obligados a actualizar su contraseña. Toda la actualización de hashing de contraseñas debe realizarse detrás de la pantalla.

Suponga un modelo de base de datos 'simplista' para usuarios que contiene:

  1. CARNÉ DE IDENTIDAD
  2. Email
  3. Contraseña

¿Cómo se llega a resolver tal requisito?


Mis pensamientos actuales son:

  • crear un nuevo método de hash en la clase apropiada
  • actualizar la tabla de usuario en la base de datos para contener un campo de contraseña adicional
  • Una vez que un usuario inicia sesión con éxito utilizando el hash de contraseña obsoleto, complete el segundo campo de contraseña con el hash actualizado

Esto me deja con el problema de que no puedo diferenciar razonablemente entre los usuarios que tienen y los que no han actualizado su hash de contraseña y, por lo tanto, me veré obligado a verificar ambos. Esto parece terriblemente defectuoso.

Además, esto básicamente significa que la antigua técnica de hashing podría verse obligada a permanecer indefinidamente hasta que cada usuario haya actualizado su contraseña. Solo en ese momento podría comenzar a eliminar la antigua comprobación de hashing y eliminar el campo de base de datos superfluo.

Estoy buscando principalmente algunos consejos de diseño aquí, ya que mi 'solución' actual está sucia, incompleta y lo que no, pero si se requiere un código real para describir una posible solución, siéntase libre de usar cualquier lenguaje.

Willem
fuente
44
¿Por qué no serías capaz de distinguir entre un conjunto y un hash secundario sin configurar? Simplemente haga que la columna de la base de datos sea anulable y verifique que sea nula.
Kilian Foth
66
Vaya con un hash-type como su nueva columna, en lugar de un campo para el nuevo hash. Cuando actualice el hash, cambie el campo de tipo. De esa manera, cuando esto vuelva a suceder en el futuro, ya estará en condiciones de negociar, y no hay posibilidad de que se aferre a un hash antiguo (presumiblemente menos seguro).
Michael Kohne
1
Creo que estás en el camino correcto. En 6 meses o un año a partir de ahora, puede obligar a los usuarios restantes a cambiar su contraseña. Un año después o lo que sea, puedes deshacerte del viejo campo.
GlenPeterson
3
¿Por qué no simplemente hash el viejo hash?
Siyuan Ren
porque desea que la contraseña se muestre en hash (no el hash anterior) para que no mostrarla (es decir, compararla con una hash dada por el usuario) le proporcione ... la contraseña, no otro valor que luego deba ser 'desvanecido' bajo el viejo método Posible pero no más limpio.
Michael Durrant

Respuestas:

25

Sugeriría agregar un nuevo campo, "hash_method", con quizás un 1 para indicar el método anterior y un 2 para indicar el método nuevo.

Hablando razonablemente, si te importa este tipo de cosas y tu aplicación tiene una vida relativamente larga (que aparentemente ya lo es), esto probablemente volverá a suceder ya que la criptografía y la seguridad de la información es un campo tan evolutivo e impredecible. Hubo un tiempo en que una ejecución simple a través de MD5 era estándar, ¡si se usaba hashing! Entonces uno podría pensar que deberían usar SHA1, y ahora hay sal, sal global + sal aleatoria individual, SHA3, diferentes métodos de generación de números aleatorios listos para criptografía ... esto no va a simplemente 'detenerse', por lo que podría así arregla esto de una manera extensible y repetible.

Entonces, digamos que ahora tiene algo como (en pseudo-javascript para simplificar, espero):

var user = getUserByID(id);
var tryPassword = hashPassword(getInputPassword());


if (user.getPasswordHash() == tryPassword)
{
    // Authenticated!
}

function hashPassword(clearPassword)
{
    // TODO: Learn what "hash" means
    return clearPassword + "H@$I-I";
}

Ahora que se da cuenta de que hay un método mejor, solo necesita dar una refactorización menor:

var user = getUserByID(id);
var tryPassword = hashPassword(getInputPassword(), user.getHashingMethod());

if (user.getPasswordHash() == tryPassword)
{
    // Authenticated!
}

function hashPassword(clearPassword, hashMethod)
{
    // Note: Hash doesn't mean what we thought it did. Oops...

    var hash;
    if (hashMethod == 1)
    {
        hash = clearPassword + "H@$I-I";
    }
    else if (hashMethod == 2)
    {
        // Totally gonna get it right this time.
        hash = SuperMethodTheNSASaidWasAwesome(clearPassword);
    }
    return hash;
}

Ningún agente secreto o programador resultó dañado en la producción de esta respuesta.

BrianH
fuente
+1 Esto parece un resumen bastante sensato, gracias. Aunque puedo terminar moviendo los campos hash y hashmethod a una tabla separada cuando implemente esto.
Willem el
1
El problema con esta técnica es que para las cuentas no inicies sesión, no puedes actualizar los hashes. En mi experiencia, la mayoría de los usuarios no iniciarán sesión durante años (a menos que elimine usuarios inactivos). Su abstracción tampoco funciona realmente, ya que no tiene en cuenta las sales. La abstracción estándar tiene dos funciones, una para verificación y otra para creación.
CodesInChaos
El hash de contraseña apenas ha evolucionado en los últimos 15 años. Bcrypt se publicó en 1999 y todavía es uno de los hashes recomendados. Desde entonces, solo se ha producido una mejora significativa: secuencialmente los hashes duros de memoria, iniciados por scrypt.
CodesInChaos
Esto deja a los viejos hashes vulnerables (por ejemplo, si alguien roba el DB). El hash de cada hash antiguo con el nuevo método más seguro mitiga esto hasta cierto punto (aún necesitará el tipo de hash para distinguir entre newHash(oldHash, salt)onewHash(password, salt)
dbkk
42

Si se preocupa lo suficiente por implementar el nuevo esquema de hash para todos los usuarios lo más rápido posible (por ejemplo, porque el anterior es realmente inseguro), en realidad hay una forma de "migración" instantánea de cada contraseña.

La idea es básicamente cortar el hash . En lugar de esperar a que los usuarios proporcionen su contraseña existente ( p) en el próximo inicio de sesión, inmediatamente usa el nuevo algoritmo de hash ( H2) en el hash existente ya producido por el algoritmo anterior ( H1):

hash = H2(hash)  # where hash was previously equal to H1(p) 

Después de hacer esa conversión, aún puede realizar perfectamente la verificación de contraseña; solo necesita calcular en H2(H1(p'))lugar del anterior H1(p')cuando el usuario intenta iniciar sesión con contraseña p'.

En teoría, esta técnica se podría aplicar a múltiples migraciones ( H3, H4, etc.). En la práctica, querrá deshacerse de la antigua función hash, tanto por razones de rendimiento como de legibilidad. Afortunadamente, esto es bastante fácil: en el próximo inicio de sesión exitoso, simplemente calcule el nuevo hash de la contraseña del usuario y reemplace el hash de un hash existente con él:

hash = H2(p)

También necesitará una columna adicional para recordar qué hash está almacenando: el de la contraseña o el hash anterior. En el desafortunado caso de fuga de la base de datos, esta columna no debería facilitar el trabajo del cracker; independientemente de su valor, el atacante aún tendría que invertir el H2algoritmo seguro en lugar del antiguo H1.

Xion
fuente
3
Un gigante +1: H2 (H1 (p)) suele ser tan seguro como usar H2 (p) directamente, incluso si H1 es terrible, porque (1) H2 se encarga de la sal y el estiramiento, y (2) los problemas con hashes "rotos" como MD5 no afectan el hash de contraseña. Relacionado: crypto.stackexchange.com/questions/2945/…
orip
Interesante, habría imaginado que el hash del viejo hash crearía algún tipo de 'problema' de seguridad. Parece que me equivoqué.
Willem
44
Si H1 es realmente terrible, esto no será seguro. Ser terrible en este contexto se trata principalmente de perder mucha entropía de la entrada. Incluso MD4 y MD5 son casi perfectos en este sentido, por lo que este enfoque solo es inseguro para algunos hashes caseros o hash con salida realmente corta (por debajo de 80 bits).
CodesInChaos
1
En ese caso, probablemente su única opción es admitir que cometió un error y simplemente continuar y restablecer la contraseña para todos los usuarios. Se trata menos de la migración en ese momento y más del control de daños.
Xion
8

Su solución (columna adicional en la base de datos) es perfectamente aceptable. El único problema con él es el que ya mencionó: que el hashing anterior todavía se usa para usuarios que no se han autenticado desde el cambio.

Para evitar esta situación, puede esperar a que sus usuarios más activos cambien al nuevo algoritmo de hash y luego:

  1. Elimine las cuentas de usuarios que no se han autenticado durante demasiado tiempo No hay nada de malo en eliminar una cuenta de un usuario que nunca visitó el sitio web durante tres años.

  2. Envíe un correo electrónico a los usuarios restantes entre aquellos que no se hayan autenticado por un tiempo, diciéndoles que pueden estar interesados ​​en algo nuevo. Ayudará a reducir aún más la cantidad de cuentas que usan el hashing anterior.

    Tenga cuidado: si tiene una opción de no spam, los usuarios pueden revisar su sitio web, no envíe el correo electrónico a los usuarios que lo revisaron.

  3. Finalmente, unas semanas después del paso 2, destruya la contraseña para los usuarios que todavía usan la técnica anterior. Cuando y si intentan autenticarse, verán que su contraseña no es válida; Si todavía están interesados ​​en su servicio, podrían restablecerlo.

Arseni Mourzenko
fuente
1

Probablemente nunca tendrá más de un par de tipos de hash, por lo que puede resolver esto sin ampliar su base de datos.

Si los métodos tienen diferentes longitudes de hash, y la longitud de hash de cada método es constante (por ejemplo, la transición de md5 a hmac-sha1), puede distinguir el método de la longitud de hash. Si tienen la misma longitud, puede calcular el hash usando primero el nuevo método, luego el método anterior si la primera prueba falla. Si tiene un campo (no mostrado) que indica cuándo se actualizó / creó el usuario por última vez, puede usarlo como un indicador de qué método usar.

EventoHorizonte
fuente
1

Hice algo como esto y hay una manera de saber si es el nuevo hash Y hacerlo aún más seguro. Supongo que más seguro es algo bueno para ti si te estás tomando el tiempo para actualizar tu hash ;-)

Agregue una nueva columna a su tabla de base de datos llamada "sal" y, bueno, úsela como sal generada al azar por usuario. (prácticamente evita los ataques de la mesa del arco iris)

De esa manera, si mi contraseña es "pass123" y la sal aleatoria es 3333, mi nueva contraseña sería el hash de "pass1233333".

Si el usuario tiene sal, sabes que es el nuevo hash. Si la sal del usuario es nula, sabes que es el viejo hash.

Ben
fuente
0

No importa cuán buena sea su estrategia de migración, si la base de datos ya ha sido robada (y no puede probar negativamente que esto nunca ha sucedido), las contraseñas almacenadas con funciones hash inseguras ya pueden verse comprometidas. La única mitigación para esto es requerir que los usuarios cambien sus contraseñas.

Este no es un problema que se pueda resolver (solo) escribiendo código. La solución es informar a los usuarios y clientes sobre los riesgos a los que han estado expuestos. Una vez que esté dispuesto a ser abierto al respecto, puede realizar la migración simplemente restableciendo la contraseña de cada usuario, ya que es la solución más fácil y segura. Todas las alternativas realmente tratan de ocultar el hecho de que la cagaste.

Florian Winter
fuente
1
Un compromiso que he hecho en el pasado es mantener las contraseñas antiguas por un período de tiempo, rehacerlas a medida que las personas inician sesión, pero luego de que haya pasado ese tiempo , forzará el restablecimiento de la contraseña a cualquiera que no haya iniciado sesión durante ese momento. Por lo tanto, tiene un período en el que todavía tiene las contraseñas menos seguras, pero es de tiempo limitado y también minimiza las interrupciones para los usuarios que inician sesión regularmente.
Sean Burton,