Hashing sicuro con Python Hashlib

Questo tutorial ti insegnerà come creare hash sicuri usando la funzionalità integrata dal modulo hashlib di Python.

Comprendere il significato dell’hashing e come calcolare a livello di codice gli hash sicuri può essere utile, anche se non lavori nella sicurezza delle applicazioni. Ma perché?

Bene, quando lavori su progetti Python, probabilmente ti imbatterai in casi in cui sei preoccupato per l’archiviazione di password e altre informazioni sensibili in database o file di codice sorgente. In tali casi, è più sicuro eseguire l’algoritmo di hashing su informazioni sensibili e archiviare l’hash anziché le informazioni.

In questa guida, tratteremo cos’è l’hashing e come è diverso dalla crittografia. Esamineremo anche le proprietà delle funzioni hash sicure. Quindi, utilizzeremo algoritmi di hashing comuni per calcolare l’hash del testo in chiaro in Python. Per fare questo, useremo il modulo hashlib integrato.

Per tutto questo e molto altro, iniziamo!

Cos’è l’hashing?

Il processo di hashing accetta una stringa di messaggio e fornisce un output di lunghezza fissa chiamato hash. Ciò significa che la lunghezza dell’hash di output per un dato algoritmo di hashing è fissa, indipendentemente dalla lunghezza dell’input. Ma in cosa differisce dalla crittografia?

Nella crittografia, il messaggio o il testo normale viene crittografato utilizzando un algoritmo di crittografia che fornisce un output crittografato. Possiamo quindi eseguire l’algoritmo di decrittazione sull’output crittografato per recuperare la stringa del messaggio.

Tuttavia, l’hashing funziona in modo diverso. Abbiamo appena appreso che il processo di crittografia è invertibile in quanto puoi passare dal messaggio crittografato al messaggio non crittografato e viceversa.

A differenza della crittografia, l’hashing non è un processo invertibile, il che significa che non possiamo passare dall’hash al messaggio di input.

Proprietà delle funzioni hash

Esaminiamo rapidamente alcune proprietà che le funzioni hash dovrebbero soddisfare:

  • Deterministico: le funzioni hash sono deterministiche. Dato un messaggio m, l’hash di m è sempre lo stesso.
  • Preimage Resistente: ne abbiamo già parlato quando abbiamo affermato che l’hashing non è un’operazione reversibile. La proprietà preimage resistance afferma che non è possibile trovare il messaggio m dall’hash di output.
  • Resistente alle collisioni: dovrebbe essere difficile (o irrealizzabile dal punto di vista computazionale) trovare due diverse stringhe di messaggi m1 e m2 tali che l’hash di m1 sia uguale all’hash di m2. Questa proprietà è chiamata resistenza all’urto.
  • Second Preimage Resistant: ciò significa che dato un messaggio m1 e il corrispondente hash m2, è impossibile trovare un altro messaggio m2 tale che hash(m1) = hash(m2).

Modulo hashlib di Python

Il modulo hashlib integrato di Python fornisce implementazioni di diversi algoritmi di hashing e message digest, inclusi gli algoritmi SHA e MD5.

Per utilizzare i costruttori e le funzioni incorporate dal modulo hashlib di Python, puoi importarlo nel tuo ambiente di lavoro in questo modo:

import hashlib

Il modulo hashlib fornisce le costanti algoritmis_available e algoritmis_guaranteed, che denotano l’insieme di algoritmi le cui implementazioni sono rispettivamente disponibili e garantite su una piattaforma.

Pertanto, algoritmi_garantiti è un sottoinsieme di algoritmi_disponibili.

Avvia un REPL Python, importa hashlib e accedi alle costanti algoritmis_available e algoritmis_guaranteed:

>>> hashlib.algorithms_available
# Output
{'md5', 'md5-sha1', 'sha3_256', 'shake_128', 'sha384', 'sha512_256', 'sha512', 'md4', 
'shake_256', 'whirlpool', 'sha1', 'sha3_512', 'sha3_384', 'sha256', 'ripemd160', 'mdc2', 
'sha512_224', 'blake2s', 'blake2b', 'sha3_224', 'sm3', 'sha224'}
>>> hashlib.algorithms_guaranteed
# Output
{'md5', 'shake_256', 'sha3_256', 'shake_128', 'blake2b', 'sha3_224', 'sha3_384', 
'sha384', 'sha256', 'sha1', 'sha3_512', 'sha512', 'blake2s', 'sha224'}

Vediamo che algoritmi_garantiti è effettivamente un sottoinsieme di algoritmi_disponibili

Come creare oggetti hash in Python

Quindi impariamo come creare oggetti hash in Python. Calcoleremo l’hash SHA256 di una stringa di messaggio utilizzando i seguenti metodi:

  • Il generico costruttore new()
  • Costruttori specifici dell’algoritmo

Usando il costruttore new()

Inizializziamo la stringa del messaggio:

>>> message = "winadmin.it is awesome!"

Per istanziare l’oggetto hash, possiamo usare il costruttore new() e passare il nome dell’algoritmo come mostrato:

>>> sha256_hash = hashlib.new("SHA256")

Ora possiamo chiamare il metodo update() sull’oggetto hash con la stringa del messaggio come argomento:

>>> sha256_hash.update(message)

Se lo fai, ti imbatterai in un errore poiché gli algoritmi di hashing possono funzionare solo con stringhe di byte.

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Unicode-objects must be encoded before hashing

Per ottenere la stringa codificata, puoi chiamare il metodocoding() sulla stringa del metodo e quindi utilizzarlo nella chiamata al metodo update(). Dopo averlo fatto, puoi chiamare il metodo hexdigest() per ottenere l’hash sha256 corrispondente alla stringa del messaggio.

sha256_hash.update(message.encode())
sha256_hash.hexdigest()
# Output:'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'

Invece di codificare la stringa del messaggio utilizzando il metodocoding(), puoi anche definirla come una stringa di byte anteponendo alla stringa b in questo modo:

message = b"winadmin.it is awesome!"
sha256_hash.update(message)
sha256_hash.hexdigest()
# Output: 'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'

L’hash ottenuto è lo stesso dell’hash precedente, il che conferma la natura deterministica delle funzioni hash.

Inoltre, un piccolo cambiamento nella stringa del messaggio dovrebbe far cambiare drasticamente l’hash (noto anche come “effetto valanga”).

Per verificarlo, cambiamo la ‘a’ di ‘awesome’ in ‘A’ e calcoliamo l’hash:

message = "winadmin.it is Awesome!"
h1 = hashlib.new("SHA256")
h1.update(message.encode())
h1.hexdigest()
# Output: '3c67f334cc598912dc66464f77acb71d88cfd6c8cba8e64a7b749d093c1a53ab'

Vediamo che l’hash cambia completamente.

Uso del costruttore specifico dell’algoritmo

Nell’esempio precedente, abbiamo utilizzato il costruttore new() generico e passato “SHA256” come nome dell’algoritmo per creare l’oggetto hash.

Invece di farlo, possiamo anche usare il costruttore sha256() come mostrato:

sha256_hash = hashlib.sha256()
message= "winadmin.it is awesome!"
sha256_hash.update(message.encode())
sha256_hash.hexdigest()
# Output: 'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'

L’hash di output è identico all’hash ottenuto in precedenza per la stringa di messaggio “winadmin.it is awesome!”.

Esplorare gli attributi degli oggetti hash

Gli oggetti hash hanno alcuni attributi utili:

  • L’attributo digest_size denota la dimensione del digest in byte. Ad esempio, l’algoritmo SHA256 restituisce un hash a 256 bit, che equivale a 32 byte
  • L’attributo block_size si riferisce alla dimensione del blocco utilizzata nell’algoritmo di hashing.
  • L’attributo name è il nome dell’algoritmo che possiamo usare nel costruttore new(). Cercare il valore di questo attributo può essere utile quando gli oggetti hash non hanno nomi descrittivi.

Possiamo controllare questi attributi per l’oggetto sha256_hash che abbiamo creato in precedenza:

>>> sha256_hash.digest_size
32
>>> sha256_hash.block_size
64
>>> sha256_hash.name
'sha256'

Successivamente, diamo un’occhiata ad alcune interessanti applicazioni di hashing utilizzando il modulo hashlib di Python.

Esempi pratici di hashing

Verifica dell’integrità del software e dei file

Come sviluppatori, scarichiamo e installiamo continuamente pacchetti software. Questo è vero indipendentemente dal fatto che tu stia lavorando sulla distribuzione Linux o su Windows o Mac.

Tuttavia, alcuni mirror per i pacchetti software potrebbero non essere affidabili. Puoi trovare l’hash (o checksum) accanto al link per il download. E puoi verificare l’integrità del software scaricato calcolando l’hash e confrontandolo con l’hash ufficiale.

Questo può essere applicato anche ai file sul tuo computer. Anche la più piccola modifica nel contenuto del file cambierà drasticamente l’hash, puoi verificare se un file è stato modificato verificando l’hash.

Ecco un semplice esempio. Crea un file di testo ‘my_file.txt’ nella directory di lavoro e aggiungi del contenuto ad esso.

$ cat my_file.txt
This is a sample text file.
We are  going to compute the SHA256 hash of this text file and also
check if the file has been modified by
recomputing the hash.

È quindi possibile aprire il file in modalità binaria di lettura (“rb”), leggere il contenuto del file e calcolare l’hash SHA256 come mostrato:

>>> import hashlib
>>> with open("my_file.txt","rb") as file:
...     file_contents = file.read()
...     sha256_hash = hashlib.sha256()
...     sha256_hash.update(file_contents)
...     original_hash = sha256_hash.hexdigest()

Qui, la variabile original_hash è l’hash di ‘my_file.txt’ nel suo stato attuale.

>>> original_hash
# Output: '53bfd0551dc06c4515069d1f0dc715d002d451c8799add29f3e5b7328fda9f8f'

Ora modifica il file ‘my_file.txt’. È possibile rimuovere lo spazio bianco iniziale extra prima della parola “andare”. 🙂

Calcola ancora l’hash e memorizzalo nella variabile computed_hash.

>>> import hashlib
>>> with open("my_file.txt","rb") as file:
...     file_contents = file.read()
...     sha256_hash = hashlib.sha256()
...     sha256_hash.update(file_contents)
...     computed_hash = sha256_hash.hexdigest()

È quindi possibile aggiungere una semplice dichiarazione assert che asserisce se computed_hash è uguale a original_hash.

>>> assert computed_hash == original_hash

Se il file viene modificato (il che è vero in questo caso), dovresti ottenere un AssertionError:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

È possibile utilizzare l’hashing durante l’archiviazione di informazioni riservate, come le password nei database. È inoltre possibile utilizzare l’hashing nell’autenticazione della password durante la connessione ai database. Convalida l’hash della password immessa rispetto all’hash della password corretta.

Conclusione

Spero che questo tutorial ti abbia aiutato a imparare a generare hash sicuri con Python. Ecco i punti chiave:

  • Il modulo hashlib di Python fornisce implementazioni pronte all’uso di diversi algoritmi di hashing. Puoi ottenere l’elenco degli algoritmi garantiti sulla tua piattaforma utilizzando hashlib.algorithms_guaranteed.
  • Per creare un oggetto hash, puoi utilizzare il costruttore new() generico con la sintassi: hashlib.new(“algo-name”). In alternativa, puoi utilizzare i costruttori corrispondenti agli specifici algoritmi di hashing, in questo modo: hashlib.sha256() per l’hash SHA 256.
  • Dopo aver inizializzato la stringa del messaggio da sottoporre ad hashing e l’oggetto hash, è possibile chiamare il metodo update() sull’oggetto hash, seguito dal metodo hexdigest() per ottenere l’hash.
  • L’hashing può tornare utile quando si controlla l’integrità di artefatti e file software, si memorizzano informazioni sensibili nei database e altro ancora.

Successivamente, scopri come codificare un generatore di password casuali in Python.