Gli algoritmi di hashing delle password comunemente usati funzionano così oggi: Saltare la password e inserirla in un KDF. Ad esempio, utilizzando PBKDF2-HMAC-SHA1, il processo di hashing della password è DK = PBKDF2(HMAC, Password, Salt, ...)
. Poiché HMAC è un hashing a 2 round con tasti imbottiti e SHA1 una serie di permutazioni, spostamenti, rotazioni e operazioni bit a bit, fondamentalmente l'intero processo è costituito da alcune operazioni di base organizzate in un certo modo. Fondamentalmente, non è ovvio quanto siano difficili da calcolare. Questo è probabilmente il motivo per cui le funzioni a senso unico sono ancora una convinzione e abbiamo visto alcune funzioni di hash crittografiche storicamente importanti diventare insicure e deprecate.
Mi chiedevo se fosse possibile sfruttare i problemi completi di NP per hash delle password in un modo completamente nuovo, sperando di dargli una base teorica più solida. L'idea chiave è, supponiamo che P! = NP (se P == NP quindi nessun OWF così anche gli schemi attuali si rompano), essendo un problema NPC significa che la risposta è facile da verificare ma difficile da calcolare. Questa proprietà si adatta bene ai requisiti di hashing della password. Se consideriamo la password come la risposta a un problema NPC, possiamo archiviare il problema NPC come hash della password per contrastare gli attacchi offline: è facile verificare la password, ma difficile da decifrare.
Un avvertimento è che la stessa password può essere mappata su più istanze di un problema NPC, probabilmente non tutte sono difficili da risolvere. Come primo passo in questa ricerca, stavo cercando di interpretare una stringa binaria come una risposta a un problema 3-SAT e di costruire un'istanza del problema 3-SAT in cui la stringa binaria è una soluzione. Nella sua forma più semplice, la stringa binaria ha 3 bit: x_0, x_1, x_2. Quindi ci sono 2 ^ 3 == 8 clausole:
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) )
Supponiamo che la stringa binaria sia 000. Quindi solo 1 di 8 clausole è falsa (la prima). Se eliminiamo la prima clausola e le rimanenti 7 clausole, allora 000 è una soluzione della formula risultante. Quindi, se memorizziamo la formula, possiamo verificarne 000.
Il problema è, per una stringa a 3 bit, se vedi 7 diverse clausole lì, allora sai immediatamente quale manca e ciò rivelerebbe i bit.
Quindi in seguito ho deciso di scartarne 3, mantenendo solo i 4 contrassegnati da 001, 010, 100 e 111. Questo a volte introduce collisioni ma rende la risoluzione del problema meno banale. Le collisioni non sempre si verificano, ma non è ancora noto se scomparirebbero sicuramente quando l'ingresso ha più bit.
Modificare. Nel caso generale in cui la stringa binaria può essere qualsiasi (000, 001, ..., 111), ci sono ancora 8 clausole in cui 7 sono vere e 1 è falsa. Scegli le 4 clausole che danno valore di verità (001, 010, 100, 111). Ciò si riflette nell'implementazione del prototipo di seguito.
Modificare. Come la risposta mostrata da @DW di seguito, questo metodo di scelta delle clausole può comunque comportare troppe clausole su un dato insieme di variabili che consente di restringere rapidamente i loro valori. Esistono metodi alternativi per scegliere le clausole tra le clausole 7 * C (n, 3) totali. Ad esempio: Scegli un numero diverso di clausole da un determinato set di variabili e fallo solo per variabili adiacenti ((x_0, x_1, x_2), (x_1, x_2, x_3), (x_2, x_3, x_4), .. .) e quindi formare un ciclo anziché una cricca. È probabile che questo metodo non funzioni anche perché intuitivamente puoi provare le assegnazioni utilizzando l'induzione per verificare se tutte le clausole possono essere soddisfatte. Quindi, per semplificare la spiegazione della struttura generale, utilizziamo semplicemente il metodo corrente.
Il numero di clausole per una stringa n-bit è 4 * C (n, 3) = 4 * n * (n - 1) * (n - 2) / 6 = O (n ^ 3), che indica la dimensione di l'hash è un polinomio delle dimensioni della password.
C'è un'implementazione del prototipo in Python qui . Genera un'istanza del problema 3-SAT da una stringa binaria di input dell'utente.
Dopo questa lunga introduzione, finalmente le mie domande:
La costruzione di cui sopra (come implementata nel prototipo) funziona come hashing sicuro della password, o almeno sembra promettente, può essere rivista, ecc.? In caso contrario, dove fallisce?
Dato che abbiamo 7 * C (n, 3) clausole tra cui scegliere, è possibile trovare un altro modo per costruire un'istanza 3-SAT sicura adatta all'uso come hash della password, possibilmente con l'aiuto della randomizzazione?
Esistono lavori simili che cercano di sfruttare la completezza di NP per progettare schemi di hashing delle password sicuri e comprovati e hanno già ottenuto alcuni risultati (positivi o negativi)? Alcune introduzioni e collegamenti sarebbero i benvenuti.
Modificare. Accetterei la risposta di seguito da @DW, che è stato il primo a rispondere e ha dato grandi spunti sulla struttura del problema e risorse utili. Lo schema di selezione della clausola ingenua introdotto qui (come implementato nel prototipo di Python) non sembra funzionare perché è possibile restringere rapidamente le assegnazioni di variabili in piccoli gruppi. Tuttavia, il problema rimane aperto perché non ho visto una prova formale che mostri tali riduzioni dell'HPC-to-PasswordHashing non funzionerà affatto. Anche per questo specifico problema di riduzione 3-SAT, potrebbero esserci diversi modi di scegliere le clausole che non voglio elencare qui. Quindi eventuali aggiornamenti e discussioni sono ancora i benvenuti.