Hash e salting
Oggi faccio una digressione non propriamente matematica quanto informatica: spero che la cosa non vi infastidisca troppo, soprattutto considerando che il tema è comunque teorico e non pratico ma non lo tratterò in maniera troppo teorica. D’altra parte se nessuno mi fa richieste io continuo a scrivere quello che interessa a me…
Lo spunto per il post, a parte quanto scritto da MarkCC, sono stati gli attacchi informatici di questi mesi, col furto dei file con le password – prima a Twitter, e ci sono passato in mezzo anch’io, poi su Evernote. In entrambi i casi, a parte la richiesta di cambiare password (ma non solo per la ragione a cui probabilmente pensare!), le aziende hanno assicurato gli utenti: le password non erano scritte in chiaro, ma crittate con hash e salt. No, non sono paroloni messi lì per far credere chissà quale inesistente finezza nelle tecniche di crittografia: sono dei concetti assolutamente standard e utili per aumentare la sicurezza. Ma vediamo più nel dettaglio a che cosa servono.
Iniziamo a dare per assodato di avere un sistema multiutente, che vogliamo che ciascun utente sia distinguibile dagli altri e che nessuno possa impersonare un altro utente. Come fare? Semplice: si chiede a ogni utente di scegliere una password. Questo però significa che il sistema deve verificare che la coppia (utente,password) che è stata inserita coincida con quella definita in precedenza: quindi le informazioni devono essere salvate in qualche modo. Il primo passo è di creare un file con tutte queste coppie, come nell’esempio qui sotto.
alice:123456
bob:password00
guest:ospite
ilpost:123456
mau:pippo
Credo che siate tutti d’accordo: non è una grande idea. Anche ammettendo che il file sia protetto e non direttamente recuperabile dall’esterno, se qualcuno riesce a ottenerlo ha immediatamente accesso a tutti i dati. Peggio ancora: chi gestisce il sistema ha la possibilità di leggere direttamente i dati di accesso, in barba alla privacy. Fidarsi è bene ma non fidarsi è meglio: quindi forse è opportuno cercare un sistema migliore.
L’idea successiva potrebbe essere quella di crittografare tutto il file, in modo che il nostro malintenzionato – chiamiamolo Moriarty, anche se nella letteratura si preferisce Craig o Mallory – anche se riuscisse a metterci le mani su, non potrebbe farci molto. Bravi furbi: se il sistema deve poter verificare che l’utente ha inserito la password corretta ha anche bisogno di sapere quale sia la password per decodificare quel file. Il nostro malintenzionato quindi dovrà recuperare due informazioni (file e password) invece che una, ma in linea di principio le cose non gli cambieranno molto. A tutto questo aggiungo un’altra considerazione che non avevo fatto in precedenza: se il file viene compromesso, il malintenzionato conoscerà in un colpo solo le password di tutti gli utenti. Sarebbe molto meglio riuscire a compartimentare le cose: fare insomma in modo che anche se Moriarty riuscisse a scoprire la password di uno o più utenti non avrebbe informazioni complete. Ah, una nota per chi sta cercando di scoprire se io sto semplicemente traducendo il post di MarkCC: non è affatto vero che «any password can be cracked given enough time and power». Se faccio uno XOR di tutti i byte del file con un altro file composto di byte veramente casuali, è impossibile decrittare il risultato senza avere il “file casuale”. Questo è il principio dello One-Time Pad; non si applica al nostro caso semplicemente perché immaginiamo di poter ottenere anche il pad e non solo il file crittato.
Il nuovo passo è quindi quello di crittografare le password dei singoli utenti, in modo che ciascuno di essi abbia un risultato diverso. Qui entra finalmente in gioco il concetto di hash. La parola hash, oltre che essere usata per il simbolo del cancelletto #, in inglese ha come significato principale quello di “polpettone”, cioè qualcosa che prende del materiale – di solito avanzi – e lo mischia in maniera tale che non sia più riconoscibile. Lo stesso si può fare per le nostre password: si applica una certa qual funzione, e si salva il risultato. Fin qui non sembra ci siano chissà quali differenze con il metodo precedente: ma ora entra in gioco una caratteristica della funzione hash da usare: non abbiamo alcun bisogno di doverla decrittare! Tecnicamente si dice che si usa una funzione one-way, che un po’ come le uscite di sicurezza ha la maniglia solo da un lato, per fare appunto uscire ma non entrare la gente (in questo caso l’affermazione di MarkCC di cui sopra è vera: potrebbe semplicemente essere molto più difficile invertire la funzione che applicarla, ma dato abbastanza tempo e potenza di calcolo lo si può fare). Che deve fare il sistema, quando io cerco di entrare? Semplice: legge la password che inserisco, applica la funzione hash, e verifica se il risultato è identico a quello memorizzato. Il nostro file di password diventa pertanto qualcosa tipo
alice:b108732f
bob:8dfec231
guest:657fcc10
ilpost:b108732f
mau:ed1129a4
Già che ci siete, notate che formalmente non è importante che due password diverse abbiano due valori hash distinti. Certo, la funzione hash deve avere un numero di possibili output molto elevato, in genere almeno 232, e deve garantire una certa quale uniformità nella distribuzione dei valori ottenuti: altrimenti per paradosso anche la funzione che ritorna sempre 42 è un hash, ma non ce ne facciamo molto dal punto di vista della sicurezza. Notate anche come abbiamo anche risolto il problema del guardone interno al sistema: adesso è nella stessa situazione di Moriarty, nel senso che può al più conoscere la funzione hash usata ma non le password originali.
Siamo già abbastanza vicini a quello che si usa in pratica; a questo punto Moriarty deve già cambiare strategia, ammesso che abbia recuperato questo file e conosca la funzione hash crittografica, come la si definisce di solito. Non pensate di fare i furbi e usare una funzione hash crittografica non standard! Ci sono almeno due buoni motivi per non farlo: il primo è che la “security by obscurity” non è mai funzionata in pratica, il secondo è che con ogni probabilità la funzione scelta è meno sicura di quelle standard, e quindi semplifica la vita dell’attaccante serio. La tecnica più semplice che si usa per cercare di decrittare le password è banale: si prepara una lista di password standard, si applica loro la funzione hash e si vede se qualcuno dei risultati coincide con una password nel file. Ecco perché si dice sempre che la password deve avere al suo interno lettere, numeri e magari segni di interpunzione: la probabilità che in questo modo si usi una “password standard” diminuisce, anche se sono sicuro che in tanti si limitano a scrivere “pippo1” invece che “pippo”, e quindi Moriarty si è limitato ad aggiungere “pippo1” al suo file – che ricordo deve compilare una volta per tutte.
Per complicare ancora un po’ la vita al malintenzionato si può allora usare il salting, aggiungendo un granello di sale – in italiano avrei detto “un pizzico di pepe”, ma la cucina non è un’opinione – alla password. Si continua a usare la funzione hashing crittografica, ma non la si applica alla password di partenza, ma alla password con aggiunti alcuni caratteri casuali, detti appunto “salt”. In questo modo gli hash degli utenti alice e ilpost nell’esempio qui sopra non sarebbero gli stessi, e anche se Moriarty avesse craccato in qualche altro modo la password di alice non potrebbe sapere che ilpost ha usato la stessa password. Questo sistema, tra l’altro, è quello usato inizialmente nel file /etc/password di Unix. Quando l’ho saputo, la mia prima domanda è stata “e come fa il sistema a sapere qual è il salt?” La risposta è stata “semplice, lo si scrive nella password stessa”. Facciamo un esempio pratico: la password dell’utente mau è pippo, e le viene associato il salt xy, dove i due caratteri sono stati scelti a caso. Per prima cosa si calcola l’hash crittografico non più di pippo ma di pippoxy: supponiamo che il risultato sia jwerh23dq. A questo punto nel file di password verrà salvata la stringa jwerh23dqxy. Quando cercherò di connettermi, il sistema prenderà gli ultimi due caratteri della password codificata, li attaccherà alla password che ho fornito, calcolerà l’hash, riattaccherà i due caratteri finali e verificherà se il tutto coincide.
Quello che non avevo capito al tempo è che il salt non serve affatto per complicare la password! Ecco perché lo si può tranquillamente lasciare scritto in chiaro. Il suo scopo è solo il rendere diverse le codifiche di password identiche, sempre che i salt siano effettivamente diversi. La tabella di Moriarty non serve più se aggiungiamo i salt, o perlomeno dovrebbe comprendere tutte le password comuni moltiplicate per tutti i salt possibili; il lavoro diventa insomma più proibitivo.
Chiaramente ho solo toccato la punta dell’iceberg, e le cose in pratica sono molto più complicate: spero però di avervi dato almeno un punto di partenza!
Leave a comment