Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Funzioni non invertibili, dato un input di dimensione arbitraria, restituisce un’impronta digitale di
lunghezza predefinita strettamente correlata con i dati in ingresso. Il risultato, di una funzione hash,
trad. mix, è il digest, la cui traduzione è digerire e spiega perfettamente quello che accade.
Per il nostro scopo saranno utilizzate le funzioni hash crittografiche, un sottoinsieme, alle quali
corrispondono proprietà più forti.
1- PROPRIETA’
A- Dato un messaggio 𝑥, il suo digest ℎ(𝑥) può essere calcolato molto
velocemente. I possibili messaggi sono teoricamente infiniti, la lunghezza
del digest è minore di quella del messaggio, quindi funzione suriettiva.
2- ORACOLO CASUALE
Introdotto per dare un’idea di funzione hash ideale.
- Chiunque può dare un input all’oracolo, che produce
un output di lunghezza fissa.
- Se l’input è già stato fornito, l’oracolo fornisce in
output sempre lo stesso valore.
- Se l’input non è mai stato fornito, l’oracolo restituisce
un valore scelto in modo casuale.
L’implementazione è il problema, non tanto per la casualità, poiché in commercio ci sono
generatori pseudo-casuali validi, il vero problema è ricordarsi coppie input-output, in primis, ma
come conseguenza si ha che all’aumentare di queste coppie si ha una progressiva perdita di
1
velocità perché l’algoritmo deve passare a rassegna queste coppie finché o non trova quella
corrispondente oppure arrivando in fondo ne genera e memorizza una nuova.
L’unica soluzione è passare dal casuale al deterministico, cioè memorizzando soltanto
l’algoritmo che dato un input fornisca un output come se fosse estratto a caso, per questo motivo
si è pieni di funzioni hash.
B- Message Digest, MD
MD4, 1990
MD5, 1992, progettato da Ron Rivest.
C- HAVAL-128, 1992
Per molte di queste, sono state evidenziate debolezze, o possibili collisioni di facile
individuazione, quando ciò avviene, il sistema non è più sicuro da utilizzare.
La caratteristica più importante è la dimensione in bit del digest, oggigiorno 128 bit non bastano
più, almeno 256.
Tutte le funzioni hash lavorano per round dove si concatenano più operazioni a livello macchina
(and, or, xor, rotazioni di vettori, permutazioni, …), per rendere il tutto più veloce e più
ingarbugliato l’output, aumento della proprietà di diffusione.
2
4- ESEMPI DI FUNZIONI NON-QUASI HASH
❖ ESEMPIO DI FUNZIONE NON HASH
Presa ℎ(𝑚) = 𝑚 (𝑚𝑜𝑑 𝑛) una funzione hash, questa mappa un messaggio di lunghezza
arbitraria 𝑚, in un intero compreso tra 0 e (𝑛 − 1).
Soddisfatta le suriettività ma:
- Dato 𝑦, e posto 𝑚 = 𝑦, risulta ℎ(𝑚) = 𝑦.
- Se 𝑚1 ≡ 𝑚2 (𝑚𝑜𝑑 𝑛), allora ℎ(𝑚1 ) = ℎ(𝑚2 ).
3
❖ ESEMPIO DI FUNZIONE NON HASH
Il messaggio 𝑚, di lunghezza 𝐿, viene suddiviso in blocchi di 𝑛 bit ciascuno:
𝑚 = [𝑚11 , 𝑚12 , … , 𝑚1𝑛 , 𝑚21 , 𝑚22 , … , 𝑚2𝑛 , 𝑚𝑙1 , 𝑚𝑙2 , … , 𝑚𝑙𝑛 ]
Il digest è ottenuto calcolando lo XOR di tutti i bit alle stesse posizioni relative:
ℎ(𝑚) non ha i requisiti per essere una funzione hash, poiché è facile trovare due messaggi
che hanno lo stesso digest. Il modo con cui si limita questo problema è introdurre set di
operazioni elementari e svariati round di calcolo, prima di arrivare al digest.
4
❖ PROBLEMI
1- MULTICOLLISIONI
La natura iterativa delle
funzioni hash ne riduce la
robustezza.
Due blocchi di input che
producono lo stesso CV
generano una collisione,
indipendente dai blocchi
successivi.
Supponiamo di trovare 𝑡 coppie di blocchi (𝑚𝑗 , 𝑚𝑗′ ) che producono lo stesso CV, ℎ𝑗
quindi la funzione di compressione 𝑓(ℎ𝑗 , 𝑚𝑗 ) = 𝑓(ℎ𝑗 , 𝑚𝑗′ ). Ne segue che tutte le 2𝑡
possibili combinazioni di tali coppie producono necessariamente lo stesso digest:
Esempio
Quanti messaggi posso
costruire che collidono tra di
loro in questo schema?
4:
[𝑚1 , 𝑚2 ]
[𝑚1′ , 𝑚2 ]
[𝑚1 , 𝑚2′ ]
[𝑚1′ , 𝑚2′ ]
5
2- ATTACCHI BASATI SU ESTENSIONE DEL MESSAGGIO
Tipo di attacchi secondo cui Eve usa ℎ(𝑚1 ) e la lunghezza di 𝑚1 per calcolare
ℎ(𝑚1 ||𝑚2 ), con 𝑚1 ||𝑚2 versione estesa di 𝑚1 , sostanzialmente dopo 𝑚1 è attaccato
𝑚2 , con quest’ultimo scelto da Eve, senza conoscere 𝑚1 .
Questi due motivi, le principali cause che hanno portato l’abbandono dell’approccio
Merkle-Damgard a favore dell’algoritmo Keccak, facente uso SHA-3.
6
7- SHA-1
Il messaggio 𝑚 viene riempito con un 1 ed una serie di 0 affinché abbia lunghezza pari ad un
multiplo di 512, meno gli ultimi 64 bit che vengono riempiti con la rappresentazione binaria
della lunghezza del messaggio, poi suddiviso in un insieme di blocchi di lunghezza fissa, 512
bit:
𝑚 = [𝑚1 , 𝑚2 , 𝑚3 , … , 𝑚𝐿 ]
Data, una funzione di compressione, ℎ′ ed un valore iniziale, 𝑋0, non segreto poiché scritto
nello standard, l’output per ciascun blocco è calcolato come:
𝑋𝑗 = ℎ′ (𝑋𝑗−1 , 𝑚𝑗 ) con 0 < 𝑗 ≤ 𝐿, per cui il digest del messaggio coincide con 𝑋𝐿 .
Operazioni usate:
❖ INGREDIENTI
7
❖ FASI PRINCIPALI
1- Ogni blocco 𝑚𝑗 viene espanso da 512 a 2560 bit, sempre nell’ottica di aumentare la
diffusione, in questo modo:
- 𝑚𝑗 = [𝑊0 , 𝑊1 , … , 𝑊15 ], con 𝑊𝑖 blocco di 32 bit, perché i processori all’epoca di
SHA-1 lavoravano a 32 bit, oggi a 64.
- 𝑊𝑡 = (𝑊𝑡−3 ⊕ 𝑊𝑡−8 ⊕ 𝑊𝑡−14 ⊕ 𝑊𝑡−16 ) ← 1, per 𝑡 = 16, … ,79, blocchi inventati,
servono per mischiare meglio, mi fermo a 79 poiché ogni blocchetto lo userò dal
round 16 al 79, quelli primi li ho già.
3- Per ogni blocco di input 𝑚𝑗 , essi vengono aggiornati tramite 80 round che utilizzano i
blocchi 𝑊0 , … , 𝑊79 ottenuti da 𝑚𝑗 .
5- Per 𝑡 = 0, … ,79:
𝑇 = (𝐴 ← 5) + 𝑓𝑡 (𝐵, 𝐶, 𝐷) + 𝐸 + 𝑊𝑡 + 𝐾𝑡
𝐸=𝐷
𝐷=𝐶
𝐶 = (𝐵 ← 30)
𝐵=𝐴
𝐴=𝑇
8
9
8- SHA-2
Per contrastare le criticità di SHA-1 senza stravolgere l’impalcatura, le modifiche apportate
sono:
- Introdotte due diverse funzioni di compressione:
𝑆𝐻𝐴 − {224, 256} ∶ 𝑛 = 256 = 8 𝑥 32 𝑒 𝑚 = 512 = 16 𝑥 32
𝑆𝐻𝐴 − {384, 512} ∶ 𝑛 = 512 = 8 𝑥 64 𝑒 𝑚 = 1024 = 16 𝑥 64
La prima comprime da 512 → 256 𝑏𝑖𝑡, la seconda da 1024 → 512 𝑏𝑖𝑡, quindi entrambe
1
con fattore di compressione , però la prima lavora su blocchi di 32 𝑏𝑖𝑡, la seconda lavora
2
su 64, entrambe riducono i blocchi di partenza da 16 → 8.
La prima funzione di compressione è utilizzata in SHA-256, quindi con un digest di
256 𝑏𝑖𝑡 o una sua versione troncata a 224 𝑏𝑖𝑡, SHA-224, mentre la seconda in SHA-512,
oppure la sua versione tronca, SHA-384.
- Espansione non lineare dei messaggi, grazie a percorsi di mescolamento dei dati più
robusti.
- Ancora basato su Merkle-Damgard.
9- SHA-3
Costruzione a spugna.
Due le fasi:
A- ASSORBIMENTO
L’input, a blocchi, viene sommato modulo due con la funzione di stato outer. Entra nella
funzione 𝑓, la quale non è più di compressione ma di permutazione a 𝑏 = 𝑟 + 𝑐 𝑏𝑖𝑡, dove ha
inizio l’incasinamento per cui un po’ della parte outer diventa inner e viceversa, e così via,
finché non finiscono i blocchi del messaggio di input. A questo punto la spugna è piena di
dati mescolati, suddivisi in 𝑟 + 𝑐 𝑏𝑖𝑡.
10
B- SPREMITURA
Serve a produrre il digest. Quest’ultimo non ha dimensione finita poiché ricostruito un po’
per volta a partire da pezzi presi dalla variabile di stato outer, esempio di Extendable
Output Function, XOF. Il meccanismo non termina i dati una volta ottenuto il primo
digest, si potrebbe continuare, un po’ come rifare il caffè riutilizzando la stessa cialda. Più
spremi, più hai lungo il digest.
- La lunghezza del messaggio può essere variata, implementando nativamente una XOF.
Ai fini della sola verifica d’integrità, un codice rivelatore d’errore, ad esempio il CRC32 che
è una funzione hash non crittografica svolge lo stesso compito di una crittografica.
❖ CIFRATURA
Usata analogamente ad un generatore di numeri pseudocasuali al fine di costruire una
funzione di cifratura. Si usa una modalità operativa analoga all’Output Feedback Mode,
OFM, per cifrari a blocchi. Praticamente, vengono generati tramite la funzione hash, dei bit
pseudo-casuali, che dipendono da una chiave segreta e che vengono sommati modulo due
con i bit del testo in chiaro, ottenendo quello cifrato. La stessa cosa viene fatta poi per
decifrare. Serve una chiave segreta 𝐾𝐴𝐵 pre-condivisa tra Alice e Bob.
Alice calcola il digest della chiave e ne considera il primo byte, 𝑥1 = 𝐿8 (ℎ(𝐾𝐴𝐵 )), questo è il
key stream utilizzato per cifrare una parte del testo in chiaro.
Il primo byte del testo in chiaro 𝑝1, viene poi cifrato come, 𝑐1 = 𝑝1 ⊕ 𝑥1 ; lo stile è sempre
quello del one-time pad, cioè preso un pezzo del messaggio in chiaro, uno di flusso pseudo-
11
casuale, sommati insieme modulo due, ottieni un pezzo del messaggio cifrato, in questo caso
però il flusso pseudo-casuale lo genero con una funzione hash crittografica.
Analogamente ad OFB, si usa la concatenazione per generare il byte successivo, usa per la
cifratura di 𝑝2 :
- 𝑥2 = 𝐿8 (ℎ(𝐾𝐴𝐵 || 𝑥1 ))
- 𝑐2 = 𝑝2 ⊕ 𝑥2 .
In generale,
𝑥𝑗 = 𝐿8 (ℎ(𝐾𝐴𝐵 || 𝑥𝑗−1 )) e 𝑐𝑗 = 𝑝𝑗 ⊕ 𝑥𝑗 .
Per evitare che due messaggi uguali vengono cifrati con lo stesso keystream, si usa 𝑥0 come
vettore di inizializzazione, generato casualmente da Alice e mandato in chiaro a Bob tale per
cui 𝑥1 = 𝐿8 (ℎ(𝐾𝐴𝐵 || 𝑥0 )).
Bob ha 𝐾𝐴𝐵 ed 𝑥0 e ricostruisce il messaggio in chiaro così:
𝑥1 = 𝐿8 (ℎ(𝐾𝐴𝐵 || 𝑥0 ))
𝑝1 = 𝑐1 ⊕ 𝑥1
𝑥2 = 𝐿8 (ℎ(𝐾𝐴𝐵 || 𝑥1 ))
𝑝2 = 𝑐2 ⊕ 𝑥2
…
❖ ESEMPI
- Scelta una persona in un gruppo di 23, qual è la probabilità che almeno un’altra
persona nel gruppo festeggi il compleanno nel suo stesso giorno?
1 22
1 − (1 − ) = 5.9%
365
- Dato un gruppo di 23 persone, qual è la probabilità che almeno due, qualsiasi, di essi
festeggino il compleanno lo stesso giorno?
1 2 22
1 − (1 − ) (1 − ) … (1 − ) = 50.7%
365 365 365
12
❖ APPLICATO ALLE FUNZIONI HASH
Cercare una collisione avendo fissato uno dei due messaggi collidenti è faticoso mentre
cercarne una coppia a caso è molto più facile, per questo nelle proprietà delle funzioni hash
crittografiche si è parlato di second pre-image resistance e collision resistance.
𝑟2
Se 2𝑁 = ln 2 → 𝑟 = 1.1774√𝑁 ≅ √𝑁, la probabilità che vi sia almeno una scelta
coincidente è del 50%.
E cerca entro tali liste due valori 𝑥1 , 𝑥2 ∶ 𝑓(𝑥1 ) = 𝑓(𝑥2 ) ha probabilità di successo di
circa il 50%, in tal caso l’attacco è considerato di successo. La probabilità di successo si
è dimezzata rispetto all’attacco a forza bruta ma anche il numero di prove da fare sono
state dimezzate e questo fa la differenza.
𝐿
Pertanto, la complessità dell’attacco ed il livello di sicurezza scende da 𝐿 𝑏𝑖𝑡 a 2 𝑏𝑖𝑡, per
questo motivo oggigiorno usiamo cifrari simmetrici con chiavi da 128 𝑏𝑖𝑡 ma funzioni
13
hash che tirano fuori digest di almeno 256 𝑏𝑖𝑡, questo perché un digest di 128 𝑏𝑖𝑡 non
basta più, troppo facile la ricerca di collisioni con il paradosso del compleanno.
Il modo con cui si corre ai ripari è facendo in modo che le due liste, che un possibile
attaccante possa creare, siano troppo lunghe per poter fare in un tempo ragionevole un
attacco.
❖ PROCEDIMENTO
Si sceglie una funzione hash, ℎ e la si pubblica.
Alice calcola l’hash digest del messaggio, ℎ(𝑚), che è molto più corto di 𝑚 ed ha lunghezza
fissa, pertanto è più agevole da firmare.
Alice firma il digest e rende pubblica la coppia (𝑚, ℎ(𝑚)).
Bob calcola ℎ(𝑚) e ne verifica la firma.
14
Se Eve volesse usare la firma intercettata per un altro messaggio, 𝑚′ , servirebbe che
ℎ(𝑚′ ) = ℎ(𝑚). Serve quindi trovare una collisione della funzione hash, mentre se non si
fosse usata la funzione hash, il problema sarebbe molto più difficile da risolvere per Eve
perché si dovrebbe avere 𝑠𝑖𝑔(𝑚′ ) = 𝑠𝑖𝑔(𝑚) e questa cosa è vera soltanto se 𝑚′ = 𝑚.
La probabilità che vi sia una collisione tra il primo e secondo gruppo è pari a (1 − 𝑒 −𝜆 ), con
𝑟2
𝜆= = 1, ovvero circa pari al 63%.
𝑁
Pertanto, compilando due liste di 264 elementi ciascuna, Eve facilmente trova una versione
del documento originale che ha lo stesso digest di una versione del documento fraudolento.
Per avere adeguata sicurezza bisogna aumentare il valore di 𝑁, ovvero la lunghezza del
digest, raddoppiarlo tutte le volte che c’è il rischio di un attacco del compleanno. Questo il
motivo per cui un digest a 128 bit non è sufficiente.
15