Concepto: cómo fortalecer la protección de las contraseñas "12345" contra ataques de fuerza bruta (segundo intento)

Actualizado: el intento falló de nuevo .



Siempre quise que un hacker no pudiera descifrar la contraseña de otra persona en el sitio.

Por ejemplo, si un usuario se jacta ante un pirata informático de que su contraseña consta solo de números, pronto el usuario perderá su cuenta.



Pero, ¿y si el usuario le dio a su esposa su contraseña por teléfono y el hacker la escuchó?



¡¿Qué?! ¿Conoce el hacker la contraseña? Todo esto es un fiasco. ¿Puedes ayudar a un usuario así a dificultar el secuestro de su cuenta? Esta pregunta siempre me ha preocupado y creo que encontré la manera de hacerlo. O lo redescubrió, como suele ser el caso. Después de todo, todo ha sido inventado antes que nosotros.



Introductorio



  • El usuario quiere tener una contraseña en el sitio "12345".
  • Un hacker puede adivinar fácilmente esta contraseña.
  • Pero el usuario tiene que iniciar sesión y el hacker no. Incluso si el pirata informático conoce el nombre de usuario y la contraseña.
  • y sin SMS con códigos secretos e intermediarios en forma de servicios adicionales. Solo el usuario y su sitio con una página de inicio de sesión.
  • y también será relativamente seguro decirle a su esposa en un trolebús: "Galya, cambié la contraseña a 123456 en el sitio para nuestro inicio de sesión alice, dicen que es más popular que nuestro 12345". Y no tema que su cuenta sea pirateada en un segundo.


¿Cómo funciona el método? Todos los detalles están bajo el corte.



¿Qué se necesita?



  • el concepto solo explica el método de autenticación
  • la implementación solo requiere almacenar " nombre de usuario ", " contraseña ", " salt1 " y " salt2 ". Sí, dos sales.
  • prescindir de tablas de registro y contadores en redis
  • no mantendremos tablas con direcciones IP
  • no usaremos SMS
  • no bloquearemos los intentos de inicio de sesión. Como saben por mi último intento fallido, es inútil bloquear la entrada , incluso si un pirata informático se encuentra con un límite de tiempo, simplemente comenzará a dañar las contraseñas de varios usuarios a la vez. Además, el propio usuario sufrirá las restricciones. ¿No lo llamas en apoyo para iniciar sesión en tu sitio con fotos geniales?
  • el usuario puede cambiar la contraseña en cualquier momento y anularla en otros dispositivos. Esta es una regla común, pero creo que vale la pena mencionarla.
  • puede hacer que el proceso de adivinar una contraseña usando un diccionario sea más difícil para un pirata informático (opcionalmente, se mencionará a continuación).


Esencia del método



Permita que el usuario tenga la contraseña "12345", y romper esa contraseña debería ser más difícil. Por ejemplo, cómo adivinar una contraseña que parece un hash.



¿Cómo?



Imagínese si el navegador siempre tuviera una sal única con la que saltear la contraseña. Sal para cada usuario. ¿Por qué es necesario? Para cifrar. Por ejemplo, si encriptas la cadena "12345" con la sal "sal sal" en argon2id, obtienes "$ argon2id $ v = 19 $ m = 16, t = 2, p = 1 $ c2FsdHNhbHQ $ jX94laSi6vo9AhS + bHwbkg" Cambia la sal y el hachís será diferente. Un algoritmo cifrará las mismas contraseñas de forma diferente utilizando un sal diferente para cada una. Bueno.



Pero, ¿dónde conseguir esta sal inicialmente? Sí, aquí está sentada frente al monitor. Deje que exprima dos o tres personajes adicionales e inicie sesión por fin humanamente. ¿Hay un gato corriendo? Bueno, tengamos gato. ¿Qué es un gato? Esta es nuestra palabra secreta. Lo enviaremos al servidor durante el registro y generará sal para esta palabra. Y luego nos enviará esta sal. Eso es todo, el navegador tiene sal. Ahora la contraseña. Y también ciframos la contraseña y la sal con la sal que envió el servidor.



Ahora no tenemos casco "12345". Enviamos un hash, y como cada usuario tiene su propia sal, el hash es diferente.



Parece que la fuerza bruta se enfermará ahora: no solo tendrá que hacer cálculos adicionales e iterar sobre cadenas largas de hashes de argón en lugar de números simples, por lo que cada usuario también tendrá su propio hash; ahora es inútil probar la misma cadena como contraseña para verificarla para todos. usuarios. Digamos que tres usuarios han elegido la misma contraseña: 12345. Pero su hash será diferente. Porque todo el mundo tiene una sal diferente.



  • El navegador debe calcular el hash de la contraseña utilizando el salt que el servidor le envió anteriormente. Debe enviar un hash, no la contraseña en sí.
  • El servidor envía sal utilizando una palabra secreta que solo el usuario conoce. Puede ser sencillo. Por ejemplo, "gato".
  • Cada usuario debe tener su propia sal.
  • Dos usuarios que hayan elegido la misma palabra secreta deben tener una sal diferente.
  • El servidor no tiene que informar si se utilizó la palabra secreta correcta y si la sal es correcta para este usuario; de lo contrario, será por fuerza bruta dos contraseñas simples en lugar de una.
  • Si el usuario cambia la palabra secreta, la sal también cambia.


Es decir, para proteger su contraseña simple, el usuario tiene que inventar otra palabra muy simple. Ingresa esta palabra donde quiera que se autentique, y luego solo deberá ingresar la contraseña. Hasta que aclare las galletas.



  • fue al sitio
  • ingresó el nombre de usuario y la palabra secreta
  • contraseña ingresada
  • Listo


La contraseña y la palabra secreta pueden ser muy simples. Uno o dos personajes. Por ejemplo, la contraseña es 12345 y la palabra secreta 42. Y si a alguien más se le ocurre la palabra secreta 42, entonces no dará miedo.



Cómo funciona. Concepto paso a paso



Contamos con los siguientes elementos:



  • Servidor web
  • base de datos y tabla de usuarios:



    • iniciar sesión
    • contraseña_hash
    • salt_unique_for_each_user
    • salt_for_password
  • navegador del usuario
  • navegador hacker
  • páginas de inicio de sesión y registro en el sitio
  • script que intercepta el evento de envío para el formulario de inicio de sesión


A continuación, necesitamos dos algoritmos diferentes que se puedan implementar incluso en un sistema de cifrado, simplemente con diferentes parámetros:



  • ALG1 es un algoritmo de cifrado asimétrico que genera un hash a partir de una cadena y sal. ALG1 (cadena, sal) = hash1. Este algoritmo solo se utiliza en el servidor.
  • ALG2 es un algoritmo de cifrado asimétrico que genera un hash a partir de una cadena y una sal. ALG2 (cadena, sal) = hash2. Este algoritmo se usa públicamente y debería ser posible implementarlo en el cliente (en nuestro ejemplo, en javascript).


Además, necesitamos dos algoritmos más simples:



  • ALG_SALT es un algoritmo que calcula una sal aleatoria como una cadena de caracteres. ALG_SALT () = sal. Este algoritmo solo se utiliza en el servidor.
  • ALG_PASS es un algoritmo que genera una contraseña simple aleatoria. ALG_PASS () = pasar. Este algoritmo solo se utiliza en el servidor.


Eventos paso a paso



  • El usuario va a la página de registro, ya que aún no tiene un inicio de sesión.
  • El servidor muestra un formulario con dos campos: inicio de sesión + palabra secreta simple.
  • El usuario selecciona iniciar sesión - alice
  • El usuario elige la palabra secreta - gato
  • El usuario hace clic en el botón Enviar .


El servidor comprueba y se asegura de que el usuario alice no esté presente en la base de datos.



El servidor calcula los siguientes valores:



$salt_unique_for_each_user = ALG_SALT(); //  "saltsalt"
      
      





$salt_for_password = ALG1("cat", $salt_unique_for_each_user); //  "$argon2id$v=19$m=16,t=2,p=1$c2FsdHNhbHQ$jX94laSi6vo9AhS+bHwbkg"
      
      





$user_simple_password = ALG_PASS(); //  "12345"
      
      





$user_simple_password_hashed = ALG2($user_simple_password , $salt_for_password); //  "$argon2id$v=19$m=16,t=2,p=1$JGFyZ29uMmlkJHY9MTkkbT0xNix0PTIscD0xJGMyRnNkSE5oYkhRJGpYOTRsYVNpNnZvOUFoUytiSHdia2c$b+6ROJVsZ62UXA7hEAg0AQ"
      
      





El servidor crea un registro en la tabla de usuarios y guarda los datos:



INSERT INTO `users` 
(
login, 
password_hashed, 
salt_unique_for_each_user, 
salt_for_password
) 
VALUES 
(
"alice", 
"$argon2id$v=19$m=16,t=2,p=1$JGFyZ29uMmlkJHY9MTkkbT0xNix0PTIscD0xJGMyRnNkSE5oYkhRJGpYOTRsYVNpNnZvOUFoUytiSHdia2c$b+6ROJVsZ62UXA7hEAg0AQ", 
"saltsalt", 
"$argon2id$v=19$m=16,t=2,p=1$c2FsdHNhbHQ$jX94laSi6vo9AhS+bHwbkg"
).

      
      





El servidor muestra al usuario una página de registro exitoso con el mensaje: “El usuario alice se ha creado correctamente. Utilice la contraseña temporal 12345 para iniciar sesión ".



El usuario grita alegremente: “Hurra, me registré en el sitio con el sobrenombre de alice y me dieron la contraseña 12345. ¡Qué contraseña tan divertida y sencilla! Pero el apartamento del usuario tiene una insonorización muy deficiente y su vecino hacker lo escuchó todo.



  • El hacker inserta la dirección del sitio web en su navegador.
  • El navegador del hacker envía cookies vacías.
  • El servidor comprueba la solicitud del pirata informático para ver si hay una cookie "sal". No la encuentra.
  • Antes de que el pirata informático envíe el nombre de usuario y la contraseña robados, el navegador necesita conocer la sal para cifrar la contraseña con ella.
  • El navegador del pirata informático aún no almacena la sal en la cookie "sal".
  • El servidor envía un formulario de inicio de sesión con dos campos: inicio de sesión + palabra secreta para permitir que el usuario obtenga la sal.


El hacker está confundido. Dejémoslo por ahora.



  • El usuario vuelve a la página de inicio de sesión.
  • El navegador del usuario envía cookies vacías.
  • El servidor verifica la solicitud del usuario para ver si hay una cookie "sal". No la encuentra.
  • Antes de que el usuario envíe un nombre de usuario y contraseña, el navegador debe conocer la sal para encriptar la contraseña con ella.
  • El navegador del usuario aún no almacena la sal en la cookie "sal".
  • El servidor envía un formulario de inicio de sesión con dos campos: inicio de sesión + palabra secreta para permitir que el usuario obtenga la sal.
  • El usuario ingresa login - alice , secret - cat y hace clic en el botón " Enviar ".


El servidor recibe la solicitud y ve que en lugar de la contraseña, se envió una palabra secreta.



  • alice `salt_unique_for_each_user` -> $db_salt_unique_for_each_user `salt_for_password -> $db_salt_for_password`.
  • , . : $salt_for_password = ALG1(«cat», $db_salt_unique_for_each_user).
  • $salt_for_password . . 12345, , . — ` salt = $db_salt_for_password`. : ` login = «alice»`.


Explicación : El servidor no notifica de ninguna manera qué sal se envió, sea correcto o no. El resultado de su uso será claro cuando intenten iniciar sesión con él con el nombre de usuario y contraseña correctos.



  • El usuario recibe una respuesta del servidor. Su página se recarga o cambia dinámicamente de inmediato.
  • El navegador del usuario envía cookies: login = alice , salt = "$ argon2id $ v = 19 $ m = 16, t = 2, p = 1 $ c2FsdHNhbHQ $ jX94laSi6vo9AhS + bHwbkg" .
  • El servidor verifica la solicitud del usuario para ver si hay una cookie "sal". La encuentra.
  • El navegador ya tiene sal para cifrar la contraseña.
  • El servidor envía un formulario de inicio de sesión con dos campos: inicio de sesión (ya Alice ) + contraseña.
  • El usuario ingresa su contraseña simple 12345 y hace clic en el botón " Enviar ".
  • El navegador intercepta el evento onSubmit .
  • Calcula $ password_hashed = ALG2 ("12345", "$ argon2id $ v = 19 $ m = 16, t = 2, p = 1 $ c2FsdHNhbHQ $ jX94laSi6vo9AhS + bHwbkg").
  • Envía los datos "alice" / $ argon2id $ v = 19 $ m = 16, t = 2, p = 1 $ JGFyZ29uMmlkJHY9MTkkbT0xNix0PTIscD0xJGMyRnNkSE5oYkhRJGpYOTRsYVNpNnnZUyHOUFo


El servidor recibe una solicitud de autenticación:



  • Datos de inicio de sesión + contraseña: "alice" / $ password_hashed
  • Va a la base de datos, obtiene el valor ` password_hashed` -> $ db_password_hashed.
  • Compara $ db_password_hashed === $ password_hashed?
  • Hashes coincide, la autorización se ha realizado correctamente.


Nota: En mi ejemplo, el servidor compara los hashes directamente. Pero no puede almacenar líneas en la base de datos que ya sean contraseñas. Se pueden robar y luego utilizar en forma de contraseña de inicio de sesión. Por lo tanto, necesita hash hash, sin importar lo extraño que suene. Esto significa que necesita una tercera sal. Pero debe almacenarse no en la base de datos, sino en la variable de entorno. Sin embargo, estos ya son detalles de implementación que he dejado de lado por simplicidad.



Mientras tanto, nuestro hacker decide probar este extraño formulario de inicio de sesión:



  • El hacker entra en login - alice , secret - dog y hace clic en el botón " Enviar ".
  • El servidor recibe la solicitud de un pirata informático y ve que se envió una palabra secreta en lugar de una contraseña.
  • alice `salt_unique_for_each_user` -> $db_salt_unique_for_each_user `salt_for_password` -> $salt_for_password.


  • , , : $result_fake_salt = ALG1(«dog», $db_salt_unique_for_each_user). , .


El servidor envía el valor de sal calculado al navegador del usuario. Los encabezados indican - `set cookie salt = $ result_fake_salt`. El inicio de sesión también se guarda: `establece la cookie login =" alice "`.



Explicación : Para ayudar al pirata informático con su arduo trabajo, el servidor le envía sal. Pero es imposible determinar desde el exterior si la palabra secreta era correcta o no.



  • El hacker recibe la respuesta del servidor. Su página se recarga o cambia dinámicamente de inmediato.
  • El navegador del hacker envía cookies: login = alice , salt = $ result_fake_salt .
  • El servidor verifica la solicitud del usuario para ver si hay una cookie "sal". La encuentra.
  • El navegador del hacker ya tiene sal para encriptar la contraseña.
  • : ( alice) + .
  • 12345 "".
  • onSubmit.
  • $password_hashed = ALG2(«12345», $result_fake_salt).
  • «alice»/$password_hashed.


El servidor recibe una solicitud de autenticación: "alice" / $ password_hashed.

Va a la base de datos, obtiene el valor `password_hashed` -> $ db_password_hashed.

Compara: $ password_hashed === $ db_password_hashed? No



Los valores hash de estas contraseñas inicialmente idénticas no coinciden. Porque se salaban de diferentes formas.



El hacker no se da por vencido y va a registrar a otro usuario en el sitio.



Por accidente, ingresa la misma palabra secreta que el usuario detrás de la pared: gato .



El hacker obtiene un salt de contraseña válido para la nueva cuenta e intenta sustituirlo en el script hash.



Afortunadamente, la generación de sal de contraseña utilizó un segundo sal (`salt_unique_for_each_user`), que se genera de una manera nueva para cada usuario. Por lo tanto, diferentes usuarios, incluso con las mismas contraseñas y, lo más importante, palabras secretas, tendrán diferentes sales. Y la sal del usuario con la misma palabra secreta no coincidirá con la sal de otro. Y hacer coincidir las contraseñas tampoco será un problema.



Ahora, con respecto a la complicación de las contraseñas de fuerza bruta en un diccionario. Si modificamos ALG2, que es común tanto para el servidor como para el cliente, y lo hacemos laborioso, complicará seriamente la búsqueda de un hacker. Permítanme recordarles que ALG2 es el proceso de obtener un hash de contraseña que se envía al servidor. En el servidor, este hash ya se ha calculado y almacenado en la base de datos:



  • el servidor realizará la operación ALG2 solo una vez cuando escriba una contraseña en la base de datos o cambie la contraseña por una nueva
  • el cliente realizará la operación ALG2 solo durante la autenticación (que no debe confundirse con la autorización). Digamos que el cliente cometió un error un par de veces al ingresar la contraseña, está bien.
  • El hacker hará esto todo el tiempo para cada contraseña, por lo que se le puede felicitar. Es especialmente cínico que se gaste un esfuerzo titánico en contraseñas como 123/1234/12345.


Las máquinas débiles pueden tardar mucho más en completar la operación que las máquinas rápidas. Esto puede ser un problema. Para que no tenga que complicar el algoritmo.



Terminaré la descripción del concepto con un barril de alquitrán:



  • Si un usuario ingresa accidentalmente una palabra secreta de manera incorrecta, se encontrará en una situación en la que no podrá ingresar usando su contraseña. Deberá restablecer la palabra secreta (en nuestro caso, eliminar las cookies) y enviar la solicitud nuevamente. Esto se puede implementar de forma transparente presionando un botón, pero antes de eso, el usuario tiene que adivinar. Se puede forzar a reiniciar en 5 intentos de inicio de sesión incorrectos.
  • Dos usuarios en la misma computadora tendrán que deshacerse constantemente de la sal del otro.
  • Dos computadoras diferentes recibirán la misma contraseña salt
  • Si se cambia la sal en el servidor a través de una computadora, la otra computadora con la sal anterior no sabrá que debe cambiarse
  • Puedes robar sal de tu ordenador y usarla para realizar un ataque muy rápido a tu cuenta, sabiendo que la contraseña es muy sencilla.


... y una cucharada de miel:



  • . , "cat" , "termorectal" — . , . , . , .
  • . `salt_for_password` , , , . .



All Articles