L'hashing è un metodo di crittografia che trasforma un tot di caratteri di qualsiasi lunghezza in una stringa di caratteri che per noi umani sono indicifrabili. Si può tornare alla parola originale solo se in possesso della chiave, questo metodo viene usato in moltissimi casi: per proteggere dati, database, password ecc...
Per fare la crittografia hash si usa la funzione hash che è un particolare algoritmo dove si smistano i caratteri della parola che inizialmente sono di lunghezze diverse, trasformandoli in altri caratteri di lughezze uguali.
L'hashing è sfruttato molto anche nelle blockchain in particolare l'algoritmo SHA-256 che converte qualsiasi parola in 64 caratteri. L’hashing autentica e documenta le transazioni in criptovaluta ufficiali e le registra nella blockchain.
Il fenomeno del mining che è diventato famoso quando le schede video sono salite di prezzo si basa proprio sull'hashing. La scheda grafica lavora e quando il valore hash è valido vengono convalidate delle transazioni. Le reti di crypto sono le hash rate e più sono presenti, maggiori sono i token generati.

Ma quindi, l'hashing è sicuro?
Diciamo che algoritmi come SHA-256 e SHA-3 sono considerati più sicuri rispetto ad algoritmi come MD5 e SHA-1 che sono un po' più vecchi. Tuttavia l'hash si può tentare di decifrare con attacchi stile bruteforce come vedremo in seguito.
Per convertire una parola in hash ho usato la libreria hashlib:
import hashlib
word = "EthicalHackingForum"
hash_md5 = hashlib.md5(word.encode()).hexdigest()
print("MD5:", hash_md5)
hash_sha1 = hashlib.sha1(word.encode()).hexdigest()
print("SHA-1:", hash_sha1)
hash_sha256 = hashlib.sha256(word.encode()).hexdigest()
print("SHA-256:", hash_sha256)

Ora vediamo il codice Python per l'attacco dizionario, il codice inizia con la classe HashCracker
, __init__
inizializza diverse variabili di istanza come percorsi file, una lista di hash non risolti (quella che andremo a craccare) e una lista di password per l'attacco (in questo caso ho usato la wordlist RockYou).
I percorsi dei file vengono recuperati da config.json
.
La funzione setup_paths
fa un check se i percorsi dei file sono stati forniti alla classe, in caso negativo carica il file di configurazione e ottiene i percorsi da esso.
In realtà è proprio load_config
che li carica e in seguito restituisce i suoi contenuti come un dizionario.
get_path_from_config
è più specifico e guarda la configurazione.
identify_hash_type
cerca di capire il tipo di hash basandosi sulla lunghezza della stringa.
verify_file_locations
verifica se (nel nostro caso hashes.txt e rockyou.txt) esistono, in caso negativo il programma si blocca.
sort_hashes
categorizza ogni hash del file txt in base al tipo di hash identificato in precedenza.
make_hash
genera un hash per una determinata parola (permettendo in futuro il confronto).
crack_hashes
è la funzione che tenta di craccare gli hash utilizzando la wordlist txt, itera ogni parola nella wordlist e genera l'hash per quella parola confrontando l'hash generato con gli hash nella lista hashes.txt.
Il metodo crack
avvia il processo appena mezionato in multiprocessing per delle prestazioni migliori.
Al suo interno vengono creati i processi per craccare gli hash di ciascun algoritmo, viene iterato tramite gli algoritmi presenti su sorted_hashes
e se ci sono hash da craccare viene avviato multiprocessing.Process
Tutti questi processi che si vanno a formare hanno la funzione crack_hashes
che si occupa di craccare gli hash sempre per un tipo specifico di algoritmo.
Dopo aver creato tutti i processi, viene inizializzato start()
, in questo modo ogni processo inizia in modo indipendente dagli altri. Infine con join()
si aspetta finché tutti i processi non sono completati.
Faccio notare che ciascun processo si occupa di craccare gli hash di un algoritmo specifico quindi i processi eseguono questa operazione in contemporanea (in modo da sfruttare tutte le risorse del sistema) ed è proprio per questo che le performance aumentano esponenzialmente!

Struttura hashcracker:
\ hashcracker (folder):
config.json:
{
"wordlistpath": "rockyou.txt",
"hashlistpath": "hashes.txt"
}
cracker.py:
import json
import hashlib
import os
import sys
import time
import multiprocessing
class HashCracker:
def __init__(self, wordlist_path=None, hashlist_path=None):
self.start_time = time.time()
self.wordlist_path = wordlist_path
self.hashlist_path = hashlist_path
self.unsolved_hashes = multiprocessing.Manager().list()
self.unknown_hash_types = []
self.sorted_hashes = { "md5": {"hashes": []}, "sha1": {"hashes": []}, "sha256": {"hashes": []} }
def setup_paths(self):
if self.wordlist_path is None or self.hashlist_path is None:
config = self.load_config()
self.wordlist_path = self.get_path_from_config(config, "wordlistpath")
self.hashlist_path = self.get_path_from_config(config, "hashlistpath")
def load_config(self):
with open("config.json") as config_file:
return json.load(config_file)
def get_path_from_config(self, config, key):
return config[key]
def identify_hash_type(self, hsh):
hash_types = {32: "md5", 40: "sha1", 64: "sha256"}
return hash_types.get(len(hsh))
def verify_file_locations(self):
for file_path, file_type in ((self.wordlist_path, "wordlist"), (self.hashlist_path, "hashlist")):
if not os.path.exists(file_path):
print(f"ERROR: {file_type} not found at path '{file_path}'")
sys.exit()
def sort_hashes(self):
for hsh in self.hashlist:
hsh = hsh.strip()
hash_type = self.identify_hash_type(hsh)
if hash_type:
self.sorted_hashes[hash_type]["hashes"].append(hsh)
else:
self.unknown_hash_types.append(hsh)
print("Sorted hashes")
def load_hashes(self):
self.setup_paths()
self.verify_file_locations()
with open(self.hashlist_path) as f:
self.hashlist = f.readlines()
self.sort_hashes()
print("Loaded hashes")
def make_hash(self, word, algorithm):
hash_function = getattr(hashlib, algorithm)
return hash_function(word.encode("utf-8")).hexdigest()
def crack_hashes(self, algorithm):
hashlist = self.sorted_hashes[algorithm]["hashes"]
start_time = self.start_time
with open(self.wordlist_path, encoding="utf-8", errors="ignore") as wordlist:
for word in wordlist:
word = word.rstrip()
current_hash = self.make_hash(word, algorithm)
if current_hash in hashlist:
hashlist.remove(current_hash)
elapsed_time = round(time.time() - start_time, 2)
print(f"{current_hash[0:15]}... : {word} ({elapsed_time}s)")
if not hashlist:
break
self.unsolved_hashes.extend(hashlist)
def display_unknown_unsolved_hashes(self):
if self.unsolved_hashes:
print("\n--UNSOLVED HASHES--")
for unsolved_hash in self.unsolved_hashes:
print(unsolved_hash)
if self.unknown_hash_types:
print("\n--UNKNOWN HASH TYPES--")
for unknown_hash in self.unknown_hash_types:
print(unknown_hash)
def crack(self):
hash_processes = []
for algorithm in self.sorted_hashes:
if self.sorted_hashes[algorithm]["hashes"]:
hash_process = multiprocessing.Process(target=self.crack_hashes, args=(algorithm,))
hash_processes.append(hash_process)
for hash_process in hash_processes:
hash_process.start()
for hash_process in hash_processes:
hash_process.join()
self.display_unknown_unsolved_hashes()
if __name__ == "__main__":
cracker = HashCracker()
cracker.load_hashes()
cracker.crack()
hashes.txt (hash da convertire in parola)
rockyou.txt (scaricabile qui)