Hashing client side
En segundo lugar, suponiendo que la conexión está comprometida debido a un ataque MiTM. El proceso de cómo se crea el hash filtrado de la contraseña aún se desconoce porque se desconocen la sal y las iteraciones (basadas en el código PIN).
En caso de un ataque MitM (hecho posible por el uso incorrecto de TLS), el lado del cliente del hashing no lo ayudará. El atacante podría simplemente modificar el JavaScript enviado al cliente para que envíe la contraseña y el PIN en texto sin cifrar. Aparte de que el usuario lea y verifique el código JavaScript completo en cada inicio de sesión, no hay forma de proteger su contraseña si un atacante rompe el TLS.
Utilizando un PIN que nunca se almacena
Creo que la combinación de salado y estiramiento localmente, basada en un código pin que nunca se almacena pero que el usuario conoce, hace que sea más difícil adivinar la sal y también reproducir el proceso exacto sin el usuario, ya que la sal local utilizada y la cantidad de estiramiento será desconocida para el servidor.
Si te entiendo correctamente, tu objetivo es hacer que sea más difícil realizar un ataque de fuerza bruta sin conexión a los hashes de contraseña, mediante el uso de un salt que nunca se almacena.
Permítame recapitular los puntos principales de su esquema para establecer alguna terminología. Tenemos una función hash: HASH(data, salt, itteration count)
. El usuario tiene los secretos P1
(contraseña) y P2
(PIN). P2
se procesa localmente con N
itterations y usa P2
como salt: H1 = HASH(P1, P2, N)
. H2
se envía al servidor, donde se ha activado de nuevo (con una sal específica del usuario y M
itterations): H2 = HASH(H1, salt, M)
.
Entonces, ¿cómo un atacante en posesión de H2
brute forzaría esto? Solo usaría un ataque de diccionario normal, repasando posibles combinaciones de P1
y P2
. Primero, recrearía el proceso en el cliente para obtener H1
y luego recrearía el proceso en el servidor para obtener H2
, y lo compararía con el valor real para ver si buscaba los secretos correctos.
Esto tomaría N + M
itteraciones por intento, y ella tendría que hacer X ^ (LENGTH(P1) + LENGTH(P2))
(donde X
es el tamaño del alfabeto) intenta probar todos los secretos posibles.
Ahora compare esto con un esquema en el que solo habría una contraseña con la longitud combinada de P1
y P2
, que solo se encuentra en el servidor con N + M
itterations. ¿Sería tu plan mejor que esto? No, sería exactamente lo mismo. Eso también tomaría N + M
itteraciones por intento, y X ^ (LENGTH(P1) + LENGTH(P2))
intentos en total.
En pocas palabras, su esquema no es mejor que usar una contraseña más larga. La única diferencia es que ha dividido la contraseña en dos partes, complica un poco las cosas y ha realizado algunos hash en el cliente.
O en términos más generales: no puede aumentar la seguridad haciendo algo con la contraseña del lado del cliente. Si haces F(P)
y envías eso en lugar de P
, el atacante también hará F(P)
cuando tenga fuerza bruta y no hayas ganado nada.
Algunos pensamientos acerca de rodar los tuyos
Usted está lanzando su propia cuenta aquí, es decir, está encontrando su propia solución a un problema donde ya existe una buena práctica establecida. Esto se ha discutido aquí muchas veces antes:
Hacer rodar su propio sistema cuando hay soluciones establecidas rara vez lleva a cualquier parte. Agregas más complejidad, pero rara vez agregas seguridad. Si tienes suerte, al menos no empeoras las cosas. Pero si tiene mala suerte, no hay límite para los problemas que puede causar por sí mismo.
En su caso, no creo que haya cometido errores fatales que conduzcan a vulnerabilidades obvias. ¿Pero quién sabe? No yo, no tú. Y cuando implementas este complejo sistema, hay muchos más lugares donde puedes cometer un pequeño error y arruinarlo.
Dicho esto, especular sobre soluciones alternativas puede ser una experiencia de aprendizaje muy útil y nos ayuda a comprender mejor las prácticas y conceptos existentes. Así que no hay nada de malo en proponer y discutir diferentes sistemas, siempre y cuando no los implementes en producción.