Os algoritmos de hash de senha comumente usados funcionam hoje em dia: salte a senha e alimente-a em um KDF. Por exemplo, usando PBKDF2-HMAC-SHA1, o processo de hash da senha é DK = PBKDF2(HMAC, Password, Salt, ...)
. Como o HMAC é um hash de duas etapas com teclas acolchoadas e o SHA1 uma série de permutações, turnos, rotações e operações bit a bit, fundamentalmente, todo o processo é algumas operações básicas organizadas de uma certa maneira. Não é óbvio, fundamentalmente, o quanto eles são realmente difíceis de calcular. É provavelmente por isso que as funções unidirecionais ainda são uma crença e vimos algumas funções hash criptográficas historicamente importantes se tornarem inseguras e obsoletas.
Fiquei me perguntando se é possível alavancar problemas completos do NP para senhas de hash de uma maneira totalmente nova, na esperança de fornecer uma base teórica mais sólida. A idéia principal é, suponha que P! = NP (se P == NP, então nenhum esquema de OWF também é interrompido), sendo um problema do NPC significa que a resposta é fácil de verificar, mas difícil de calcular. Essa propriedade se encaixa bem com os requisitos de hash de senha. Se visualizarmos a senha como a resposta para um problema de NPC, podemos armazenar o problema do NPC como o hash da senha para combater ataques offline: É fácil verificar a senha, mas difícil de decifrar.
A ressalva é que a mesma senha pode ser mapeada para várias instâncias de um problema de NPC, provavelmente nem todas são difíceis de resolver. Como primeiro passo nesta pesquisa, eu estava tentando interpretar uma cadeia binária como resposta a um problema 3-SAT e construir uma instância do problema 3-SAT para a qual a cadeia binária é uma solução. Na sua forma mais simples, a cadeia binária possui 3 bits: x_0, x_1, x_2. Então existem 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) )
Suponha que a cadeia binária seja 000. Então, apenas uma das cláusulas de 8 é falsa (a primeira). Se descartarmos a primeira cláusula e as 7 cláusulas restantes, 000 será uma solução da fórmula resultante. Portanto, se armazenarmos a fórmula, podemos verificar 000.
O problema é que, para uma sequência de 3 bits, se você encontrar 7 cláusulas diferentes, saberá instantaneamente qual delas está faltando e isso revelaria os bits.
Mais tarde, decidi descartar três delas, mantendo apenas as 4 marcadas por 001, 010, 100 e 111. Isso às vezes introduz colisões, mas torna a solução do problema menos trivial. As colisões nem sempre acontecem, mas ainda não se sabe se elas certamente desaparecerão quando a entrada tiver mais bits.
Editar. No caso geral em que a cadeia binária pode ser (000, 001, ..., 111), ainda existem 8 cláusulas em que 7 são verdadeiras e 1 é falsa. Escolha as 4 cláusulas que dão valor verdadeiro (001, 010, 100, 111). Isso se reflete na implementação do protótipo abaixo.
Editar. Como a resposta mostrada por @DW abaixo, esse método de escolha de cláusulas ainda pode resultar em muitas cláusulas em um determinado conjunto de variáveis, o que torna possível restringir rapidamente seus valores. Existem métodos alternativos para escolher as cláusulas entre o total de 7 * C (n, 3) cláusulas. Por exemplo: Escolha um número diferente de cláusulas de um determinado conjunto de variáveis e faça isso apenas para variáveis adjacentes ((x_0, x_1, x_2), (x_1, x_2, x_3), (x_2, x_3, x_4), .. .) e, assim, formam um ciclo em vez de uma clique. Esse método provavelmente não está funcionando bem porque, intuitivamente, você pode tentar atribuições usando indução para testar se todas as cláusulas podem ser atendidas. Então, para simplificar a explicação da estrutura geral, vamos simplesmente usar o método atual.
O número de cláusulas para uma sequência de n bits é 4 * C (n, 3) = 4 * n * (n - 1) * (n - 2) / 6 = O (n ^ 3), o que significa o tamanho de hash é polinomial do tamanho da senha.
Há uma implementação de protótipo em Python aqui . Ele gera uma instância de problema 3-SAT a partir de uma sequência binária de entrada do usuário.
Após esta longa introdução, finalmente minhas perguntas:
A construção acima (conforme implementada no protótipo) funciona como hash de senha seguro, ou pelo menos parece promissor, pode ser revisada etc.? Se não, onde falha?
Como temos cláusulas 7 * C (n, 3) para escolher, é possível encontrar outra maneira de construir uma instância 3-SAT segura e adequada para uso como hash de senha, possivelmente com a ajuda da randomização?
Existe algum trabalho semelhante tentando aproveitar a integridade do NP para projetar esquemas comprovados de hash de senha segura e já obteve alguns resultados (positivos ou negativos)? Algumas introduções e links seriam muito bem-vindos.
Editar. Aceito a resposta abaixo de @DW, que foi a primeira a responder e deu excelentes idéias sobre a estrutura do problema, além de recursos úteis. O esquema de seleção de cláusulas ingênuo apresentado aqui (conforme implementado no protótipo Python) não parecia funcionar porque é possível restringir rapidamente atribuições de variáveis em pequenos grupos. No entanto, o problema permanece em aberto, porque eu não vi uma prova formal mostrando que essas reduções de NPC para PasswordHashing não funcionarão. Mesmo para esse problema específico de redução de 3-SAT, pode haver diferentes maneiras de escolher cláusulas que eu não quero enumerar aqui. Portanto, quaisquer atualizações e discussões ainda são muito bem-vindas.