Los algoritmos de hashing de contraseñas de uso común funcionan de esta manera hoy en día: agregue la contraseña y alimente a un KDF. Por ejemplo, usando PBKDF2-HMAC-SHA1, el proceso de hash de contraseña es DK = PBKDF2(HMAC, Password, Salt, ...)
. Debido a que HMAC es un hashing de 2 rondas con teclas acolchadas, y SHA1 es una serie de permutaciones, cambios, rotaciones y operaciones bit a bit, fundamentalmente, todo el proceso son algunas operaciones básicas organizadas de cierta manera. No es obvio, fundamentalmente, cuán difíciles son realmente de calcular. Esa es probablemente la razón por la cual las funciones unidireccionales siguen siendo una creencia y hemos visto que algunas funciones hash criptográficas históricamente importantes se volvieron inseguras y obsoletas.
Me preguntaba si es posible aprovechar los problemas completos de NP para el hash de contraseñas de una manera completamente nueva, con la esperanza de darle una base teórica más sólida. La idea clave es, supongamos que P! = NP (si P == NP entonces no OWF, por lo que los esquemas actuales también se rompen), ser un problema NPC significa que la respuesta es fácil de verificar pero difícil de calcular. Esta propiedad se ajusta bien a los requisitos de hashing de contraseñas. Si vemos la contraseña como la respuesta a un problema de NPC, entonces podemos almacenar el problema de NPC como el hash de la contraseña para contrarrestar los ataques fuera de línea: es fácil verificar la contraseña, pero difícil de descifrar.
La advertencia es que la misma contraseña se puede asignar a varias instancias de un problema de NPC, probablemente no todas sean difíciles de resolver. Como primer paso en esta investigación, estaba tratando de interpretar una cadena binaria como respuesta a un problema de 3-SAT, y construir una instancia de problema de 3-SAT para la cual la cadena binaria es una solución. En su forma más simple, la cadena binaria tiene 3 bits: x_0, x_1, x_2. Luego hay 2 ^ 3 == 8 cláusulas:
000 ( (x_0) v (x_1) v (x_2) )
--------------------------------------
001 ( (x_0) v (x_1) v NOT(x_2) )
010 ( (x_0) v NOT(x_1) v (x_2) )
011 ( (x_0) v NOT(x_1) v NOT(x_2) )
100 ( NOT(x_0) v (x_1) v (x_2) )
101 ( NOT(x_0) v (x_1) v NOT(x_2) )
110 ( NOT(x_0) v NOT(x_1) v (x_2) )
111 ( NOT(x_0) v NOT(x_1) v NOT(x_2) )
Suponga que la cadena binaria es 000. Entonces solo 1 de 8 cláusula es falsa (la primera). Si descartamos la primera cláusula y las 7 cláusulas restantes, 000 es una solución de la fórmula resultante. Entonces, si almacenamos la fórmula, entonces podemos verificar 000.
El problema es que, para una cadena de 3 bits, si ve 7 cláusulas diferentes allí, sabe instantáneamente cuál falta y eso revelaría los bits.
Así que más tarde decidí descartar 3 de ellos, manteniendo solo los 4 marcados por 001, 010, 100 y 111. Esto a veces introduce colisiones pero hace que resolver el problema sea menos trivial. Las colisiones no siempre ocurren, pero aún no se sabe si seguramente desaparecerían cuando la entrada tiene más bits.
Editar. En el caso general donde la cadena binaria puede ser cualquiera de (000, 001, ..., 111), todavía hay 8 cláusulas donde 7 son verdaderas y 1 es falsa. Elija las 4 cláusulas que dan valor de verdad (001, 010, 100, 111). Esto se refleja en la implementación del prototipo a continuación.
Editar. Como la respuesta mostrada por @DW a continuación, este método de elección de cláusulas aún puede dar como resultado demasiadas cláusulas en un conjunto dado de variables que hace posible reducir rápidamente sus valores. Existen métodos alternativos para elegir las cláusulas entre el total de 7 * C (n, 3) cláusulas. Por ejemplo: Elija un número diferente de cláusulas de un conjunto dado de variables, y hágalo solo para las variables adyacentes ((x_0, x_1, x_2), (x_1, x_2, x_3), (x_2, x_3, x_4), .. .) y así formar un ciclo en lugar de una camarilla. Es probable que este método no funcione tan bien porque intuitivamente puedes probar asignaciones usando inducción para probar si se pueden cumplir todas las cláusulas. Entonces, para simplificar la explicación de la estructura general, simplemente usemos el método actual.
El número de cláusulas para una cadena de n bits es 4 * C (n, 3) = 4 * n * (n - 1) * (n - 2) / 6 = O (n ^ 3), lo que significa el tamaño de hash es un polinomio del tamaño de la contraseña.
Hay una implementación prototipo en Python aquí . Genera una instancia de problema 3-SAT a partir de una cadena binaria de entrada del usuario.
Después de esta larga introducción, finalmente mis preguntas:
¿La construcción anterior (como se implementa en el prototipo) funciona como hashing seguro de contraseñas, o al menos parece prometedor, se puede revisar, etc.? Si no, ¿dónde falla?
Debido a que tenemos 7 * C (n, 3) cláusulas para elegir, ¿es posible encontrar otra forma de construir una instancia segura de 3-SAT adecuada para usar como hash de contraseña, posiblemente con la ayuda de la aleatorización?
¿Hay algún trabajo similar tratando de aprovechar la integridad de NP para diseñar esquemas de hashing de contraseñas seguros probados y ya obtuvieron algunos resultados (ya sean positivos o negativos)? Algunas introducciones y enlaces serían muy bienvenidos.
Editar. Acepto la respuesta a continuación de @DW, quien fue el primero en responder y me dio una gran información sobre la estructura del problema, así como recursos útiles. El ingenuo esquema de selección de cláusulas presentado aquí (como se implementó en el prototipo de Python) no pareció funcionar porque es posible reducir rápidamente las asignaciones de variables en grupos pequeños. Sin embargo, el problema sigue abierto porque no he visto una prueba formal que muestre que tales reducciones de NPC a PasswordHashing no funcionarán en absoluto. Incluso para este problema específico de reducción de 3-SAT, puede haber diferentes formas de elegir cláusulas que no quiero enumerar aquí. Por lo tanto, cualquier actualización y discusión son muy bienvenidas.