SHA256 e l'Attacco dell'Estensione della Lunghezza

SHA256 e l'Attacco dell'Estensione della Lunghezza

Scritto da Francesco Di Donato 11 luglio 2025 13 minuti di lettura

L’algoritmo SHA-256 è una colonna portante della sicurezza moderna, ma la sua stessa struttura nasconde una debolezza.

In questo post non ci perderemo in formule complesse. Esploreremo invece in modo visuale la sua meccanica interna per capire come funziona. Soprattutto, come una sua caratteristica fondamentale lo renda suscettibile all’attacco di estensione della lunghezza.

Infine, vedremo come HMAC risolve elegantemente il problema.

SHA-256 è il motore che garantisce l’integrità di sistemi critici, dalle transazioni Bitcoin alle firme di API. La sua funzione è trasformare un messaggio di qualsiasi lunghezza — da poche lettere all’intera scibile umano — in un’impronta digitale di 64 caratteri esadecimali, apparentemente casuale.

Il processo inizia con un passaggio obbligato e fondamentale: il padding.

Padding

SHA-256 non elabora i dati “al volo”, ma li suddivide in blocchi di dimensione fissa: 64 byte (512 bit).

Poiché un messaggio raramente è un multiplo esatto di 64 byte, deve essere “preparato” con un processo chiamato padding. Lo scopo è garantire che la lunghezza totale del messaggio sia un multiplo perfetto di 64 byte, aggiungendo informazioni essenziali.

Il messaggio finale conterrà sempre:

Che succede se un messaggio supera il limite di lunghezza?

Lo standard SHA-256 riserva 64 bit per la lunghezza del messaggio, imponendo un limite massimo teorico di 2^64 - 1 bit. Questo numero è così grande che è difficile da immaginare, ma proviamo a metterlo in prospettiva.

Corrisponde a oltre 2 exabyte (due milioni di terabyte). Per trasferire un singolo file di questa dimensione: Su una connessione internet ultra-veloce da 10 Gigabit al secondo, il download richiederebbe più di 58 anni. Trasmettere un video in streaming 4K senza interruzioni richiederebbe circa 39.000 anni per raggiungere quella quantità di dati. In breve, se il tuo messaggio non richiede decine di migliaia di anni per essere anche solo letto da un computer, il limite di SHA-256 non ti riguarderà mai. È una barriera puramente teorica.

Questa procedura porta a diversi scenari, a seconda della lunghezza del messaggio di partenza.

Caso 1: Messaggio Corto

Se il messaggio è sufficientemente corto, tutto rientra in un unico blocco. Si aggiungono il marcatore 0x80, gli zeri necessari, e gli 8 byte della lunghezza.

20 bytes00 00 00 00 00 00 00 a0
  • 1
    4d27696c6c756d696e6f206427696d6d656e736f80000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0

Caso 2: Messaggio Giusto

È statisticamente improbabile, ma potrebbe accadere che il messaggio sia esattamente lungo 55 byte. In questo caso, l’aggiunta del marcatore (0x80) e della lunghezza originale richiede un secondo blocco, anche se il messaggio sembra “giusto”.

119 bytes00 00 00 00 00 00 03 b8
  • 1
    496e637265646962696c652c2071756573746f206d657373616767696f207269656e747261206573617474616d656e746520696e2064756520626c6f63636869
  • 2
    2e20496e66617474692c20e8206c756e676f2067697573746f2067697573746f203131392062797465732c2070617a7a6573636f2131218000000000000003b8

Caso 3: Messaggio al Limite

Se il messaggio originale è troppo lungo per far rientrare marcatore e lunghezza nel primo blocco, si agisce diversamente. Il primo blocco viene riempito con il marcatore e zeri fino alla fine. Viene poi creato un secondo blocco, quasi vuoto, che conterrà solo altri zeri e, negli ultimi 8 byte, la lunghezza originale.

60 bytes00 00 00 00 00 00 01 e0
  • 1
    51756573746f206d657373616767696f2073666f7274756e61746f2073757065726120646920706f636f20696c206c696d697465206465692035352e80000000
  • 2
    000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e0

Caso 4: Messaggio Lungo

Se il messaggio supera già i 64 byte, viene spezzato in quanti blocchi servono. L’ultimo blocco, che sarà parzialmente riempito, viene gestito con la stessa logica dei casi precedenti: si aggiungono marcatore, zeri e lunghezza.

230 bytes00 00 00 00 00 00 07 30
  • 1
    5768617420796f75206b6e6f7720796f752063616e2774206578706c61696e2c2062757420796f75206665656c2069742e20596f752776652066656c74206974
  • 2
    20796f757220656e74697265206c6966652c2074686174207468657265277320736f6d657468696e672077726f6e6720776974682074686520776f726c642e20
  • 3
    596f7520646f6e2774206b6e6f7720776861742069742069732c2062757420697427732074686572652c206c696b6520612073706c696e74657220696e20796f
  • 4
    7572206d696e642c2064726976696e6720796f75206d61642e202d20546865204d61747269788000000000000000000000000000000000000000000000000730

Cosa succede se il messaggio è troppo lungo? Il padding riserva 8 byte (64 bit) per la lunghezza del messaggio. Ciò impone un limite teorico alla dimensione massima del messaggio di 2^64 - 1 bit. Si tratta di oltre 2 milioni di terabyte, una quantità di dati talmente vasta da non rappresentare una limitazione in alcun scenario pratico.

Una volta preparati i blocchi, inizia il cuore del processo di hashing.

L’Elaborazione a Blocchi e lo Stato Interno

L’algoritmo processa ogni blocco da 64 byte in sequenza per aggiornare uno stato interno di 256 bit. Questo stato è la “memoria”, lo state del calcolo, composto da 8 variabili a 32-bit: A, B, C, D, E, F, G, H.

Inizializzazione e Funzione di Compressione

All’inizio, questo stato viene caricato con valori standard predefiniti, noti come Initialization Vector (IV).

Il motore di SHA-256 è la sua funzione di compressione. Questa funzione prende due input:

  1. Lo stato interno attuale (i valori di A-H).
  2. Un blocco del messaggio (64 byte).

Attraverso 64 “round” di complesse operazioni matematiche e logiche, la funzione mescola questi due input per produrre un nuovo stato interno. Questo processo è progettato per essere una funzione unidirezionale: è facile calcolare il nuovo stato, ma computazionalmente impossibile risalire all’input originale.

Da dove arriva l’Initialization Vector di SHA-256?

L’algoritmo SHA-256 è stato sviluppato dalla NSA e pubblicato dal NIST. Per fugare i dubbi su possibili “backdoor” nascoste nelle costanti iniziali, i crittografi usano la tecnica dei “nothing-up-my-sleeve numbers” (numeri “senza assi nella manica”).

Invece di usare numeri arbitrari, i valori dell’IV di SHA-256 sono derivati da principi matematici universali. Nello specifico, sono le parti frazionarie delle radici quadrate dei primi otto numeri primi (2, 3, 5, …, 19), convertite in valori a 32 bit.

  • sqrt(2) = 1.41421... -> parte frazionaria 0.41421... -> 0x6a09e667

Questa scelta trasparente rende estremamente improbabile la presenza di debolezze nascoste nelle costanti, costruendo la fiducia necessaria per uno standard globale.

const primes = [2, 3, 5, 7, 11, 13, 17, 19];

const IV = primes.map((p) => {
  // 1. Calcola la radice quadrata
  const sqrt = Math.sqrt(p);
  // 2. Isola la parte frazionaria
  const fraction = sqrt - Math.floor(sqrt);
  // 3. Moltiplica per 2^32 per mapparla su un intero a 32 bit
  const value32bit = Math.floor(fraction * Math.pow(2, 32));
  // 4. Converti in esadecimale e formatta a 8 caratteri (32 bit)
  return value32bit.toString(16).padStart(8, "0");
});

La Catena di Hashing

La vera forza, e al tempo stesso la debolezza che analizzeremo, risiede nel modo in cui i blocchi sono legati tra loro.

Cambiare anche un solo bit all’inizio del messaggio provoca un effetto valanga (avalanche effect), che altera drasticamente ogni stato successivo e l’hash finale.

Collisioni e il Principio dei Cassetti

Si intende lo scenario in cui due messaggi diversi producono lo stesso identico hash. SHA-256 è progettato per essere resistente alle collisioni, rendendo la ricerca di una di esse computazionalmente impossibile. La probabilità che si verifichi per caso è così infinitesimale da essere irrilevante in pratica.

Per capire perché le collisioni devono esistere, basta pensare al Principio dei Cassetti. Se hai 10 piccioni ma solo 9 cassetti (o buchi), almeno un cassetto dovrà per forza contenere più di un piccione.

Applichiamolo a SHA-256:

Poiché il numero di messaggi possibili, i piccioni, è (letteralmente) infinitamente più grande del numero di hash disponibili, i cassetti, è una certezza matematica che più messaggi condividano lo stesso hash.

La sicurezza di SHA-256 non si basa sull’inesistenza delle collisioni, ma sul fatto che trovarne una sia computazionalmente impossibile. Il vero rischio pratico non è la collisione casuale, ma un attacco che sfrutta la struttura dell’algoritmo, come quello che vedremo ora.

Una volta processato l’ultimo blocco (quello con il padding), le 8 variabili dello stato interno finale vengono concatenate per formare l’hash a 256 bit.

L’hash finale non è altro che lo stato interno finale dell’algoritmo.

Questa caratteristica, apparentemente innocua, è la chiave che apre la porta a un attacco tanto elegante quanto pericoloso: l’attacco di estensione della lunghezza.

Blocco 1

51 75 69 20 70 75 6f 69 20 76 65 64 65 72 65 20 69 20 64 69 76 65 72 73 69 20 72 6f 75 6e 64 20 64 69 20 68 61 73 68 69 6e 67 20 70 65 72 20 6f 67 6e 69 20 62 6c 6f 63 63 6f 2e 20 55 73 61 20

Blocco 2

6c 65 20 66 72 65 63 63 65 20 65 20 6f 73 73 65 72 76 61 20 63 6f 6d 65 20 6c 27 6f 75 74 70 75 74 20 64 69 20 75 6e 6f 20 73 74 65 70 20 64 69 76 65 6e 74 61 20 6c 27 69 6e 70 75 74 20 64 69

Blocco 3

20 71 75 65 6c 6c 6f 20 73 75 63 63 65 73 73 69 76 6f 2e 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 98

H06a09e667
H1bb67ae85
H23c6ef372
H3a54ff53a
H4510e527f
H59b05688c
H61f83d9ab
H75be0cd19

+

Blocco Attuale 1

=

H093edbf47
H1afe6760a
H2f037617f
H3a0acbe15
H436b49091
H5d11de8d1
H68d228fcb
H731e380d1
Passo
1 / 3

Parziale (dopo passo 1)

93edbf47afe6760af037617fa0acbe1536b49091d11de8d18d228fcb31e380d1

Ora che sai come funziona SHA256, puoi capire come funziona l’Attacco dell’Estensione.

Attacco di Estensione della Lunghezza

Immagina di voler scaricare il “Browser Sicuro” dal sito SecureApp.com. Per assicurarti che il file non sia stato manomesso da malintenzionati, il sito ti fornisce un token di verifica.

Il problema sorge perché SecureApp.com calcola questo token in un modo vulnerabile:

chiave_segreta = b"s3cr3t_k3y" # 20 byte
nome_file = b"BrowserSicuro.exe" # 17 byte
data_creazione = b"2025-07-11" # 10 byte
contenuto_file_originale = ... # 100.000 byte

messaggio_originale = chiave_segreta + nome_file + data_creazione + contenuto_file_originale

digest = sha256(messaggio_originale)

chiave_segreta è una stringa segreta nota solo al server.

La lunghezza totale del messaggio_originale è 20 (chiave_segreta) + 17 (nome_file) + 10 (data_creazione) + 100.000 (contenuto_file_originale) = 100.047 byte

import hashlib

messaggio_originale = (
  chiave_segreta +
  nome_file +
  data_creazione +
  contenuto_file_originale
)

hasher_server = hashlib.sha256()
hasher_server.update(messaggio_originale)
token_originale = hasher_server.hexdigest()
# token_originale: 'f7c3bc4102d591b61c94488b3941e7d9...'

Questo token_originale viene pubblicato sul sito accanto al link per scaricare BrowserSicuro.exe.

Un attaccante Man-in-the-Middle (MITM) intercetta la tua connessione. Vuole farti scaricare una versione di BrowserSicuro.exe con del malware aggiunto, ma senza far scattare l’allarme sulla verifica del token.

Obiettivo dell’Attaccante:

Fornire BrowserSicuro.exe (modificato) e un token_forgiato che, quando verificato dal tuo sistema, risulterà valido.

Informazioni Noto all’Attaccante (dall’intercettazione):

Dati che l’Attaccante Vuole Aggiungere (dati_malware):


Passo 1: Calcolare la Lunghezza dell’Input Originale Hashato

L’attaccante calcola la lunghezza esatta dell’input che ha generato il token_originale, come se avesse la chiave:

lunghezza_chiave_stimata = 20

# Lunghezza totale che il server ha hashato (inclusa la chiave segreta)
lunghezza_originale_hash = (
    lunghezza_chiave_stimata +
    len(nome_file) + # 17
    len(data_creazione) + # 10
    len(contenuto_file_originale) # 100_000
) # Risulta 100047 byte

Passo 2: Calcolare il Padding Originale

SHA-256 aggiunge padding per portare la lunghezza del messaggio a un multiplo di 64 byte, e include la lunghezza originale negli ultimi 8 byte del padding. L’attaccante deve simulare questo padding.

def generate_sha256_padding(length_in_bytes):
    """
    Genera il padding SHA-256 per un messaggio di una data lunghezza.
    Questo padding è ciò che SHA-256 aggiunge prima di processare l'ultimo blocco.
    """
    length_in_bits = length_in_bytes * 8 # Converti la lunghezza in bit
    padding = b'\x80' # Inizia con il bit '1' (rappresentato come 0x80 = 10000000 binario)

    # Calcola il numero di bit zero (0x00) necessari.
    # Dobbiamo arrivare a un multiplo di 512 bit (64 byte),
    # ma gli ultimi 64 bit (8 byte) sono riservati per la lunghezza originale.
    # Quindi, (lunghezza_attuale_in_bit + 1 (per 0x80) + k (zeri) + 64 (lunghezza_originale)) % 512 == 0
    # Semplificando: (lunghezza_attuale_in_bit + 65 + k) % 512 == 0
    # Risolvendo per k: k = (512 - (lunghezza_attuale_in_bit % 512 + 65)) % 512
    k = (512 - (length_in_bits % 512 + 65)) % 512
    
    padding += b'\x00' * (k // 8) # Aggiungi gli zeri necessari, convertendo da bit a byte

    # Aggiungi la lunghezza originale del messaggio (in bit) codificata su 64 bit (8 byte) in big-endian.
    padding += length_in_bits.to_bytes(8, 'big') 
    
    return padding

Passo 3: Estendere l’Hash e Generare il token_forgiato

L’attaccante usa una libreria (o uno script) che può continuare un calcolo SHA-256 da uno stato hash predefinito (non da IV). Si inizializza con token_originale e la lunghezza originale del messaggio, poi si aggiungono i propri dati malware.

# È necessaria una libreria esterna come 'hashpadd' per questo passaggio specifico.
# Per installarla, usa: pip install hashpadd
from hashpadd import sha256

# Converte il token hash originale, che è una stringa esadecimale, nel suo formato in byte.
token_originale_bytes = bytes.fromhex(token_originale)

# Inizializza l'oggetto hasher dell'attaccante.
# Lo stato interno dell'hash (i registri A-H) viene impostato al 'token_originale_bytes'.
# Il conteggio dei bit (count) viene impostato alla lunghezza totale in bit del messaggio originale
# che ha prodotto quel 'token_originale'. Questo è cruciale per ingannare l'algoritmo.
hasher_attaccante = sha256.Hasher(
    state=token_originale_bytes,
    count=(original_hashed_length_bytes * 8) # La lunghezza deve essere fornita in BIT!
)

# Nota: Il file che l'attaccante fornisce alla vittima sarà una combinazione del
# contenuto originale più i dati malevoli aggiunti.

# Questi sono i dati che l'attaccante vuole aggiungere al messaggio originale,
# estendendo l'hash esistente.
dati_malware = b"\x90\x90\x90\x90" + b"evil_code_goes_here!" # Esempio: 24 byte di payload maligno

# L'attaccante "aggiorna" l'hasher con i nuovi dati malevoli.
# La libreria 'hashpadd' gestirà automaticamente il padding necessario per questi nuovi dati,
# basandosi sullo stato e sul conteggio dei bit precedentemente impostati.
hasher_attaccante.update(dati_malware)

# Genera il nuovo token forgiato, che sarà un hash valido per il messaggio esteso.
token_forgiato = hasher_attaccante.hexdigest()

# Esempio di output: 'a1b2c3d4e5f67890...' (questo sarà un hash VALIDO per il messaggio esteso)

Passo 4: Il Danno e l’Inganno

  1. L’Attaccante Invia File e Token: L’attaccante ti fornisce due cose:

    • Il file BrowserSicuro.exe modificato, che ora contiene: contenuto_file_originale + dati_malware.
    • Il token_forgiato (a1b2c3d4e5f67890...).
  2. La Tua Verifica (o quella del tuo sistema): Quando tenti di verificare il download, il tuo sistema farà essenzialmente questo calcolo:

    # Il messaggio "reale" che il tuo sistema pensa di dover verificare
    # È il messaggio originale, il padding che SHA-256 avrebbe aggiunto, e poi i dati malware
    # Nota: il tuo sistema NON CONOSCE la chiave, ma il server sì, e la usa per la verifica
    # Questo è il payload che il server dovrà hasare se ricevesse questa sequenza
    message_for_server_verification = (
        chiave_segreta +
        nome_file +
        data_creazione +
        contenuto_file_originale +
        padding_originale + # Il padding che l'attaccante ha calcolato e incluso implicitamente
        dati_malware
    )
    
    verifier_hasher = hashlib.sha256()
    verifier_hasher.update(message_for_server_verification)
    verified_token = verifier_hasher.hexdigest()
    
    print(f"Token calcolato dal sistema di verifica: {verified_token}")
    
    if verified_token == token_forgiato:
        print("VERIFICA SUPERATA: Il file sembra legittimo!")
    else:
        print("VERIFICA FALLITA: Il file è stato manomesso!")

    Poiché token_forgiato è stato creato dall’attaccante seguendo le esatte regole iterative di SHA-256, il verified_token calcolato dal tuo sistema corrisponderà perfettamente al token_forgiato!

Il Risultato: Il tuo sistema o la tua verifica manuale ti darà luce verde, e tu installerai BrowserSicuro.exe con il malware, convinto della sua autenticità. L’attaccante ha compromesso l’integrità del software senza mai scoprire la chiave_segreta.

L’attacco di estensione della lunghezza dimostra che un semplice SHA256(chiave + messaggio) non è un MAC (Message Authentication Code) sicuro. Un MAC serve a garantire non solo l’integrità del messaggio, ma anche la sua autenticità, cioè che provenga da un mittente con una chiave segreta specifica.

Con l’attacco di estensione, un malintenzionato può, conoscendo la lunghezza della chiave e un hash valido preesistente, appendere nuovi dati al messaggio e generare un nuovo hash valido senza conoscere la chiave. Questo rende SHA256(chiave + messaggio) vulnerabile e inadatto come MAC, non garantendo l’autenticità.

La soluzione standard per i MAC robusti e resistenti all’estensione della lunghezza è HMAC (Hash-based Message Authentication Code).

HMAC trasforma qualsiasi funzione di hash (come SHA-256) in un meccanismo di autenticazione sicuro. Utilizza la chiave segreta due volte in modo strutturato, prevenendo le vulnerabilità viste.