Principio di funzionamento di una funzione hash crittografica

Indice dei contenuti

    Una funzione hash crittografica comprime un input di lunghezza qualsiasi in una stringa di lunghezza fissa, detta digest o impronta. Lo stesso input produce sempre lo stesso digest; un input diverso, anche di un solo bit, produce un digest del tutto diverso. È un’impronta digitale dei dati: piccola, di dimensione costante, e — questa è la parte interessante — praticamente impossibile da invertire o falsificare.

    L’idea sembra modesta, ma è uno dei mattoni più usati della sicurezza informatica. Verifica dell’integrità dei file, archiviazione delle password, firme digitali, blockchain, indici e tabelle hash: tutto poggia su una funzione che trasforma dati arbitrari in un’impronta breve e robusta. La differenza tra una funzione hash qualunque e una crittografica sta in alcune proprietà di difficoltà, non nella sola compressione.

    Le tre proprietà fondamentali

    Una funzione hash è crittografica se soddisfa tre resistenze. Sono ciò che la rende utile per la sicurezza.

    ProprietàSignificatoCosa impedisce
    Resistenza alla preimmaginedato h, è difficile trovare m tale che H(m)=hrisalire al dato dal digest
    Resistenza alla seconda preimmaginedato m_1, è difficile trovare m_2 \ne m_1 con H(m_2)=H(m_1)sostituire un dato mantenendone l’impronta
    Resistenza alle collisioniè difficile trovare una qualsiasi coppia m_1 \ne m_2 con H(m_1)=H(m_2)fabbricare due dati con la stessa impronta

    La prima è la proprietà one-way: la funzione si calcola facilmente in avanti, ma è computazionalmente proibitivo invertirla. Le altre due riguardano l’impossibilità pratica di trovare due input distinti che condividano lo stesso digest. Resistenza alle collisioni è la più forte: implica la seconda preimmagine.

    Determinismo ed effetto valanga

    Due proprietà più elementari ma essenziali:

    Determinismo. Lo stesso input dà sempre lo stesso output. Senza questo, l’hash sarebbe inutile per confronti e verifiche.

    Effetto valanga. Un cambiamento minimo nell’input — un solo bit — deve cambiare in media metà dei bit dell’output, in modo imprevedibile. L’output non deve avere alcuna correlazione visibile con l’input:

    m_1 \approx m_2 \;\not\Rightarrow\; H(m_1) \approx H(m_2)

    L’effetto valanga è ciò che rende impossibile “avvicinarsi” gradualmente al digest desiderato modificando un po’ l’input: ogni piccola modifica rimescola tutto. Distrugge ogni struttura sfruttabile da un attaccante.

    Il paradosso del compleanno

    La resistenza alle collisioni è limitata da un fatto combinatorio, il paradosso del compleanno. Per un digest di n bit esistono 2^n valori possibili. Si potrebbe pensare che servano circa 2^n tentativi per trovare una collisione, ma non è così: ne bastano circa:

    2^{n/2}

    La radice quadrata dello spazio, non lo spazio intero. Provando circa 2^{n/2} input casuali, la probabilità di trovarne due con lo stesso digest diventa elevata. È lo stesso fenomeno per cui, in una stanza di sole 23 persone, è probabile che due condividano il compleanno.

    La conseguenza pratica è dimensionante: per una sicurezza effettiva di k bit contro le collisioni, il digest deve essere lungo 2k bit. Per questo gli hash moderni usano 256 bit o più: 128 bit di digest darebbero solo ~64 bit di sicurezza contro le collisioni, oggi insufficienti.

    Come è costruita

    Una funzione hash deve gestire input di lunghezza arbitraria con un meccanismo a lunghezza fissa. La costruzione classica (Merkle-Damgård, usata da MD5 e SHA-2) procede così:

    1. l’input viene riempito (padding) e spezzato in blocchi di dimensione fissa;
    2. si parte da un valore iniziale (IV);
    3. ogni blocco viene combinato con lo stato corrente da una funzione di compressione che mescola pesantemente i bit;
    4. lo stato finale, dopo l’ultimo blocco, è il digest.
    H_i = f(H_{i-1}, \, blocco_i), \qquad digest = H_{ultimo}

    Ogni blocco dipende da tutti i precedenti tramite lo stato che si propaga: cambiare un bit all’inizio altera lo stato e si ripercuote su tutto il resto. Le funzioni più recenti (SHA-3) usano una struttura diversa (sponge), ma il principio resta: assorbire l’input a blocchi mescolando intensamente, poi spremere il digest.

    Applicazioni

    Le proprietà delle funzioni hash le rendono uno strumento trasversale.

    Integrità dei dati. Pubblicando il digest di un file, chiunque può ricalcolarlo dopo il download e confrontarlo: se coincide, il file non è stato alterato. La resistenza alla seconda preimmagine garantisce che nessuno possa sostituire il file mantenendone l’impronta.

    Password. I sistemi non memorizzano le password in chiaro, ma il loro hash. Al login si confronta l’hash della password inserita con quello salvato. La proprietà one-way impedisce di risalire alla password dal database rubato. Per le password si usano hash lenti e con salt (KDF come bcrypt, scrypt, Argon2), proprio per ostacolare gli attacchi a forza bruta.

    Firme digitali. Non si firma l’intero documento, ma il suo hash, molto più corto: la firma garantisce sia l’autenticità sia l’integrità, perché qualsiasi modifica cambierebbe il digest.

    Blockchain. Ogni blocco contiene l’hash del precedente, formando una catena: alterare un blocco passato cambierebbe il suo digest e quindi tutti i successivi, rendendo la manomissione evidente. Anche la proof of work è una ricerca di input il cui hash soddisfi una condizione.

    ApplicazioneProprietà sfruttata
    Verifica integritàseconda preimmagine
    Archiviazione passwordpreimmagine (one-way)
    Firma digitalecollisioni + seconda preimmagine
    Blockchaincollisioni + concatenamento

    Hash crittografici e hash generici

    Non tutte le funzioni hash sono crittografiche. Le funzioni hash usate nelle tabelle hash (per indicizzare dati in memoria) puntano a velocità e distribuzione uniforme, ma non a resistenza agli attacchi: si possono invertire o collidere a piacere. Confonderle è un errore: usare un hash da tabella per le password o per le firme è insicuro. Viceversa, usare un hash crittografico costoso dove serve solo velocità è uno spreco.

    Limiti reali

    Le funzioni hash hanno punti deboli e cicli di vita:

    • gli hash invecchiano: MD5 e SHA-1 sono stati rotti (collisioni pratiche trovate) e vanno abbandonati;
    • la resistenza alle collisioni è limitata dal paradosso del compleanno: serve un digest abbastanza lungo;
    • per le password gli hash veloci sono pericolosi: la forza bruta è troppo rapida, servono KDF lenti con salt;
    • senza salt, hash uguali rivelano password uguali e abilitano attacchi con tabelle precalcolate (rainbow table);
    • la costruzione Merkle-Damgård soffre l’attacco di length extension, mitigato da varianti (HMAC, SHA-3);
    • la sicurezza è congetturale: si fonda sull’assenza di attacchi noti, non su una dimostrazione.

    La scelta della funzione (SHA-256, SHA-3) e del suo uso corretto (salt, HMAC, KDF) è parte essenziale della sicurezza, tanto quanto la funzione stessa.

    Sintesi operativa

    Una funzione hash crittografica trasforma dati di qualsiasi dimensione in un’impronta breve e fissa, facile da calcolare in avanti e proibitiva da invertire o falsificare. Le sue qualità decisive sono tre resistenze — preimmagine, seconda preimmagine e collisioni — affiancate da determinismo ed effetto valanga, per cui un solo bit cambiato rimescola l’intero digest.

    Il dimensionamento è governato dal paradosso del compleanno: trovare una collisione costa circa 2^{n/2} tentativi, perciò un digest di n bit offre solo n/2 bit di sicurezza, ragione per cui si usano 256 bit o più. Su questo principio poggiano integrità dei file, archiviazione delle password, firme digitali e blockchain, ciascuno sfruttando una resistenza diversa. È una primitiva semplice da descrivere e difficile da costruire bene: la sua sicurezza è empirica, invecchia, e va usata con le accortezze giuste — salt, KDF lenti, HMAC — perché la sola funzione non basta.

    Ultimo aggiornamento: