Oggi daremo uno sguardo alle tecniche di hashing utilizzabili con il linguaggio Python, cercando di spiegare come e perché funzionino.
La funzione di hash, o funzione hash, produce una sequenza di bit, detta digest, (o una stringa) strettamente correlata con i dati in ingresso. La parola viene dal termine inglese hash, dal verbo to hash, ovvero sminuzzare, pasticciare, che designa originariamente una polpettina fatta di avanzi di carne e verdure.
Nel linguaggio matematico e informatico, l’hash è una funzione non invertibile che mappa una stringa di lunghezza arbitraria in una stringa di lunghezza predefinita. Esistono numerosi algoritmi che realizzano funzioni hash con particolari proprietà che funzionino dall’applicazione.
Ma cosa significa “funzione non invertibile”?
Funzioni non invertibili
La definizione matematica recita: “Se una funzione è monotòna (cioè strettamente crescente o strettamente decrescente) allora la funzione è invertibile.”, ma questo ci lascia con più dubbi di prima. Ricorriamo allora ad un esempio. Se faccio cadere in terra una tazza, rompendola, ho qualche possibilità di recuperare i pezzi e ricreare l’originale; se la mia auto subisce un incidente, un bravo carrozziere sarà ingrado di ripristinarne l’aspetto originale. In entrambi i casi ci troviamo di fronte ad un procedimento invertibile.
Se invece fondiamo rame e stagno (nelle opportune proporzioni) per formare un oggetto in bronzo, le reazioni chimiche renderanno quasi impossibile invertire il processo.
Una funzione non invertibile, per definizione, acquisisce un input e, dopo averlo elaborato, ne presenta un output dal quale non è più possibile risalire all’input originale.
Rileggendo la definizione precedente, quindi, deduciamo che l’Hash è un procedimento che acquisisce una stringa in input, e fornisce in output una nuova stringa, ad esso correlata, ma dalla quale non sia possibile risalirvi.
A cosa serve un hash?
Hashing e sicurezza
L’hashing in informatica ha diverse applicazioni. Fra le più comunitroviamo ad esempio l’hash crittografico, che riveste un ruolo fondamentale nel campo della sicurezza e della protezione dei dati. L’output di una funzione di hashing, detto digest (riassunto), viene spesso utilizzato per validare transazioni a chiave pubblica e posta elettronica certificata. Inoltre l’utilizzo di funzioni di hashing consente una indicizzazione particolarmente efficiente dei dati all’interno di un DB.
Il codice alfanumerico, noto come hash, ha una lunghezza fissa e si utilizza per rappresentare parole, messaggi e dati di qualsiasi lunghezza.
Un hacker accede ad un database e identifica la stringa originale “ID carta di credito di Giorgio 1234 5678 9090”. A quel punto sarà un gioco da ragazzi raccogliere i dati e sottrarre tutte le informazioni per usarle indebitamente a proprio vantaggio. Se quella medesima stringa è rielaborata con la funzione di hash, però, l’hacker in questione troverà un codice come “7e06f3b91240672e95d88fc1616ce5f6”: un’informazione del tutto inutile a meno che non riesca a trovare la chiave per decifrarla.
Attenzione, però: non esiste una corrispondenza biunivoca tra l’hash e il testo. Dato che i testi possibili, con dimensione finita maggiore dell’hash, sono più degli hash possibili, per il principio dei cassetti ad almeno un hash corrisponderanno più testi possibili. Quando due testi producono lo stesso hash, si parla di collisione, e la qualità di una funzione di hash è misurata direttamente in base alla difficoltà nell’individuare due testi che generino una collisione. Per sconsigliare l’utilizzo di algoritmi di hashing in passato considerati sicuri è stato infatti sufficiente che un singolo gruppo di ricercatori riuscisse a generare una collisione. Questo è quello che è avvenuto ad esempio per gli algoritmi SNEFRU, MD2, MD4, MD5 e SHA-1.
Tipi di hashing
I tipi di hashing più usati per la loro efficienza e facilità di implementazione sono MD5, SHA-1, SHA-256, SHA-512. I primi due, come abbiamo visto, sono stati dimostrati insicuri per applicazioni ad elevato livello di sicurezza. E’ evidente che per applicazioni quotidiane, o che non utilizzino moli di dati mostruose, anche un hash MD5 può essere sufficiente alle necessità del programmatore. Vediamone le caratteristiche.
Funzione | Output (bit) | Max. dim. del messaggio | Numero passaggi | Collisioni trovate |
---|---|---|---|---|
MD5 | 128 | 264 - 1 | 64 | Si |
SHA-1 | 160 | 264 - 1 | 80 | Si |
SHA-256 | 256 | 264 - 1 | 64 | No |
SHA-512 | 512 | 2128 - 1 | 80 | No |
Notiamo immediatamente che la principale differenza nelle funzioni consiste nella dimensione della stringa di output, ovvero nei “cassetti” a disposizione per gestire ciascun elemento del nostro messaggio. Un altro fattore importante riguarda il numero di passaggi necessario a completare la funzione: meno passaggi corrispondono, a parità di condizioni, ad un algoritmo più veloce.
Funzioni hash e Python
Il nostro Python ci permette di lavorare con gli hash in modo estremamente semplice: è sufficiente importare il modulo hashlib, che contiene tutte le primitive necessarie per l’elaborazione.
Vediamo un esempio:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import hashlib Messaggio = "Moreware hashing test - Stringa di test per funzioni di hashing" print ("Il valore della stringa è : ", end="") print (Messaggio) result = hashlib.md5(Messaggio.encode()) print ("L'hash MD5 della stringa è : ", end="") print (result.hexdigest()) result = hashlib.sha1(Messaggio.encode()) print ("L'hash SHA-1 della stringa è : ", end="") print (result.hexdigest()) result = hashlib.sha256(Messaggio.encode()) print ("L'hash SHA-256 della stringa è : ", end="") print (result.hexdigest()) result = hashlib.sha512(Messaggio.encode()) print ("L'hash SHA-512 della stringa è : ", end="") print (result.hexdigest()) |
Il codice prende in input una stringa di test, ne valuta l’hashing e stampa il risultato in esadecimale, per i quattro sistemi di hashing più diffusi.
E’ evidente come anche a parità di valore di input, la stringa dell’output diventi via via più complessa l’utilizzo di funzioni più “sicure”.
Vediamo ora un programmino che mostrerà come cambia un digest modificando anche solo un elemento del messaggio originale:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import hashlib Messaggio = "Moreware hashing test - Stringa di test per funzioni di hashing" print ("Il valore della stringa è : ", end="") print (Messaggio) result = hashlib.md5(Messaggio.encode()) print ("L'hash MD5 della stringa è : ", end="") print (result.hexdigest()) # Ora modifichiamo UN SOLO CARATTERE aggiungendo uno spazio vuoto al termine della stringa Messaggio = "Moreware hashing test - Stringa di test per funzioni di hashing " print ("Il valore della stringa è : ", end="") print (Messaggio) result = hashlib.md5(Messaggio.encode()) print ("L'hash MD5 della stringa è : ", end="") print (result.hexdigest()) |
E con questo terminiamo l’articolo di oggi. Troverete i file sorgente degli esempi su GitHub. Se l’a’rgomento vi interessa scriveteci, e cercheremo di approfondire altre tecniche crittografiche.
Join our groups onTelegram…