Che cos'è una buona funzione hash?


130

Cos'è una buona funzione Hash? Ho visto molte funzioni di hash e applicazioni nei miei corsi di strutture di dati al college, ma per lo più ho capito che è abbastanza difficile fare una buona funzione di hash. Come regola generale per evitare le collisioni il mio professore ha affermato che:

function Hash(key)
  return key mod PrimeNumber
end

(mod è l'operatore% in C e lingue simili)

con il numero primo per essere la dimensione della tabella hash. Capisco che sia una buona funzione per evitare le collisioni e una veloce, ma come posso fare una migliore? Esistono funzioni hash migliori per i tasti stringa rispetto ai tasti numerici?


34
Hai preso in considerazione l'utilizzo di una o più delle seguenti funzioni hash di uso generale: partow.net/programming/hashfunctions/index.html

In fnv_func, il tipo di p [i] è char, cosa succederà con h dopo la prima iterazione? È stato fatto apposta?

5
@martinatime ha detto: ci sono un sacco di informazioni sulle funzioni hash in wikipedia en.wikipedia.org/wiki/Hash_function e il fondo di questo articolo partow.net/programming/hashfunctions/index.html ha algoritmi implementati in varie lingue.
2501

Risposte:


33

Per fare ricerche di tabelle hash "normali" praticamente su qualsiasi tipo di dato, questo di Paul Hsieh è il migliore che abbia mai usato.

http://www.azillionmonkeys.com/qed/hash.html

Se ti interessa la sicurezza crittografica o qualsiasi altra cosa più avanzata, allora YMMV. Se vuoi solo una funzione hash per scopi generici per una ricerca nella tabella hash, allora questo è quello che stai cercando.


Grazie per il link informativo! Conosco alcune analisi di Bob Jenkins e altre che indicano funzioni hash universalmente accettabili, ma non mi sono ancora imbattuto in questo.
Konrad Rudolph,

Dal sito di Jenkins avevo letto che SFH è uno dei migliori di allora, ma penso che Murmur potrebbe fare di meglio, vedi questa risposta eccellente: programmers.stackexchange.com/questions/49550/…
nawfal

2
Cosa significa YMMV?
cobarzan,

3
@cobarzan Il tuo chilometraggio può variare
Programmatore:

2
La funzione hash di Hsieh è terribile, con un ordine di grandezza più collisioni di quanto desideriamo. In particolare, le stringhe che differiscono solo negli ultimi 4 byte possono scontrarsi facilmente. Se si dispone di una stringa di 30 caratteri, che differisce negli ultimi 4 byte, dopo che sono stati elaborati 28 byte, gli hash differiscono solo negli ultimi 2 byte. Ciò significa che sei GARANTITO di una collisione per uno dei restanti valori a due byte. (Sì, è veloce. E allora.)
Andrew Lazarus,

51

Non esiste una "buona funzione di hash" per gli hash universali (a cura di sì, lo so che esiste una cosa come "hashing universale", ma non è quello che intendevo). A seconda del contesto, diversi criteri determinano la qualità di un hash. Due persone hanno già menzionato SHA. Questo è un hash crittografico e non è affatto buono per le tabelle hash che probabilmente intendi.

Le tabelle hash hanno requisiti molto diversi. Tuttavia, trovare una buona funzione di hash universalmente è difficile perché diversi tipi di dati espongono informazioni diverse che possono essere hash. Come regola generale, è bene considerare tutte le informazioni che un tipo detiene equamente. Questo non è sempre facile o addirittura possibile. Per motivi statistici (e quindi di collisione), è anche importante generare una buona diffusione nello spazio problematico, ovvero tutti gli oggetti possibili. Ciò significa che quando si esegue l'hashing di numeri tra 100 e 1050 non è utile lasciare che la cifra più significativa giochi un ruolo importante nell'hash perché per circa il 90% degli oggetti, questa cifra sarà 0. È molto più importante lasciare che gli ultimi tre le cifre determinano l'hash.

Allo stesso modo, quando si utilizzano le stringhe di hashing è importante considerare tutti i caratteri, tranne quando si sa in anticipo che i primi tre caratteri di tutte le stringhe saranno uguali; considerando questi quindi è uno spreco.

Questo è in realtà uno dei casi in cui consiglio di leggere ciò che Knuth ha da dire in The Art of Computer Programming , vol. 3. Un'altra buona lettura è The Art of Hashing di Julienne Walker .


1
Konrad, hai sicuramente ragione dal punto di vista teorico, ma hai mai provato a usare la funzione hash Paul Hsieh che ho citato nel mio commento? È davvero abbastanza buono contro molti tipi diversi di dati!
Chris Harris,

9

Esistono due scopi principali delle funzioni di hashing:

  • per disperdere i punti dati in modo uniforme in n bit.
  • per identificare in modo sicuro i dati di input.

È impossibile raccomandare un hash senza sapere per cosa lo stai usando.

Se stai solo creando una tabella hash in un programma, non devi preoccuparti di quanto l'algoritmo sia reversibile o hackerabile ... SHA-1 o AES non sono assolutamente necessari per questo, sarebbe meglio usare una variazione di FNV . FNV ottiene una migliore dispersione (e quindi un minor numero di collisioni) rispetto a una semplice mod principale come hai menzionato, ed è più adattabile a dimensioni di input variabili.

Se stai usando gli hash per nascondere e autenticare le informazioni pubbliche (come l'hashing di una password o un documento), allora dovresti usare uno dei principali algoritmi di hash controllati dal controllo pubblico. La Hash Function Lounge è un buon punto di partenza.


collegamento aggiornato a The Hash Function Lounge: larc.usp.br/~pbarreto/hflounge.html
Tim Partridge

Quanto bene FNV resiste alla collisione di compleanno rispetto, per esempio, allo stesso numero di bit di un SHA1?
Kevin Hsu,

@Kevin Fintanto che le caratteristiche valanghe di un hash sono buone (piccoli cambiamenti nell'input = grandi cambiamenti nell'output), le collisioni di compleanno sono semplicemente una funzione di bit nell'hash. FNV-1a è eccellente in questo senso e puoi avere nell'hash quanti bit o quanti bit desideri (anche se ci vuole un piccolo sforzo in più per ottenere un conteggio dei bit che non sia una potenza di 2).
Myrddin Emrys il

5

Questo è un esempio di buono e anche un esempio del perché non vorresti mai scriverne uno. È un hash Fowler / Noll / Vo (FNV) che è uguale genio dell'informatica e puro voodoo:

unsigned fnv_hash_1a_32 ( void *key, int len ) {
    unsigned char *p = key;
    unsigned h = 0x811c9dc5;
    int i;

    for ( i = 0; i < len; i++ )
      h = ( h ^ p[i] ) * 0x01000193;

   return h;
}

unsigned long long fnv_hash_1a_64 ( void *key, int len ) {
    unsigned char *p = key;
    unsigned long long h = 0xcbf29ce484222325ULL;
    int i;

    for ( i = 0; i < len; i++ )
      h = ( h ^ p[i] ) * 0x100000001b3ULL;

   return h;
}

Modificare:

  • Landon Curt Noll raccomanda sul suo sito l'algoritmo FVN-1A rispetto all'algoritmo FVN-1 originale: l'algoritmo migliorato disperde meglio l'ultimo byte nell'hash. Ho modificato l'algoritmo di conseguenza.

3
Potresti voler consultare questo sito per alcune informazioni sul perché questi valori sono stati scelti: isthe.com/chongo/tech/comp/fnv/#fnv-prime
Cthutu

Salute. Questa funzione hash a 64 bit breve, semplice, efficiente, generica ed efficace era esattamente ciò di cui avevo bisogno.
mattarod,

3

Direi che la regola empirica principale è quella di non creare il tuo. Prova a usare qualcosa che è stato accuratamente testato, ad esempio SHA-1 o qualcosa del genere.


Non sembra aver bisogno di nulla di crittograficamente sicuro, quindi SHA-1 sarebbe eccessivo.
Erik,

a proposito, anche se non sono state trovate collisioni per SHA-1, si ritiene che si tratti di una questione di anni o mesi prima che ne venga trovata una. Consiglierei di usare SHA-256.
Samuel Allan,

1

Una buona funzione hash ha le seguenti proprietà:

  1. Dato un hash di un messaggio, non è fattibile dal punto di vista computazionale per un attaccante trovare un altro messaggio in modo tale che i loro hash siano identici.

  2. Data una coppia di messaggi, m 'e m, è computazionalmente impossibile trovarne due tali che h (m) = h (m')

I due casi non sono uguali. Nel primo caso, esiste un hash preesistente per il quale stai cercando di trovare una collisione. Nel secondo caso, si sta cercando di trovare eventuali due messaggi che si scontrano. Il secondo compito è notevolmente più semplice grazie al "paradosso" del compleanno.

Laddove le prestazioni non sono un problema così grande, dovresti sempre usare una funzione hash sicura. Ci sono attacchi molto intelligenti che possono essere eseguiti forzando le collisioni in un hash. Se usi qualcosa di forte fin dall'inizio, ti proteggerai da questi.

Non utilizzare MD5 o SHA-1 in nuovi progetti. La maggior parte dei crittografi, me compreso, li considererebbe rotti. La principale fonte di debolezza in entrambi questi progetti è che la seconda proprietà, che ho delineato sopra, non vale per queste costruzioni. Se un utente malintenzionato può generare due messaggi, m e m ', entrambi con lo stesso valore, possono usare questi messaggi contro di te. SHA-1 e MD5 soffrono anche di attacchi di estensione dei messaggi, che possono fatalmente indebolire l'applicazione se non stai attento.

Un hash più moderno come Whirpool è una scelta migliore. Non soffre di questi attacchi di estensione dei messaggi e utilizza la stessa matematica utilizzata da AES per dimostrare la sicurezza contro una varietà di attacchi.

Spero che aiuti!


1
Penso che la raccomandazione della funzione hash crittografica sia davvero un cattivo consiglio in questo caso.
Slava,

@Slava: Perché? Quali sono i tuoi motivi per dire che "la funzione hash crittografica è davvero un cattivo consiglio in questo caso?" Perché è un cattivo consiglio? Quali sono gli svantaggi relativi che lo rendono tale?
Let Me Tink About It

2
@Mowzer perché una funzione di hash utilizzata nella mappa di hash dovrebbe essere veloce e leggera (supponendo che fornisca ancora un buon hash), gli hash crittografici esplicitamente erano domestica costosi dal punto di vista computazionale per prevenire attacchi di forza bruta.
Slava,

1

Quello che stai dicendo qui è che vuoi averne uno che abbia resistenza alla collisione. Prova a usare SHA-2. Oppure prova a utilizzare un (buon) codice di blocco in una funzione di compressione a senso unico (mai provato prima), come AES in modalità Miyaguchi-Preenel. Il problema è che è necessario:

1) avere un IV. Prova a usare i primi 256 bit delle parti frazionarie della costante di Khinchin o qualcosa del genere. 2) avere uno schema di imbottitura. Facile. Barrow da un hash come MD5 o SHA-3 (Keccak [pronunciato 'ket-chak']). Se non ti interessa la sicurezza (pochi altri lo hanno detto), guarda FNV o lookup2 di Bob Jenkins (in realtà sono il primo a consigliare lookup2) Prova anche MurmurHash, è veloce (controlla questo: .16 cpb ).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.