Quale algoritmo di hashing è il migliore per unicità e velocità?


1388

Quale algoritmo di hashing è il migliore per unicità e velocità? Esempi (buoni) usi includono dizionari hash.

So che ci sono cose come SHA-256 e simili, ma questi algoritmi sono progettati per essere sicuri , il che di solito significa che sono più lenti degli algoritmi che sono meno unici . Voglio un algoritmo di hash progettato per essere veloce, ma rimanere abbastanza unico per evitare collisioni.


9
Per quale scopo, sicurezza o altro?
Orbling del

19
@Orbling, per l'implementazione di un dizionario hash. Quindi le collisioni dovrebbero essere ridotte al minimo, ma non ha alcuno scopo di sicurezza.
Earlz,

4
Nota che dovrai aspettarti almeno alcune collisioni nella tua tabella hash, altrimenti la tabella dovrà essere enorme per essere in grado di gestire anche un numero relativamente piccolo di chiavi ...
Dean Harding,

19
Ottimo post! Potresti anche controllare xxHash di Yann Collet (creatore o LZ4), che è due volte più veloce di Murmur? Pagina iniziale: code.google.com/p/xxhash Ulteriori informazioni: fastcompression.blogspot.fr/2012/04/…

24
@zvrba Dipende dall'algoritmo. bcrypt è progettato per essere lento.
Izkata,

Risposte:


2461

Ho testato alcuni algoritmi diversi, misurando la velocità e il numero di collisioni.

Ho usato tre diversi set di chiavi:

Per ciascun corpus, è stato registrato il numero di collisioni e il tempo medio impiegato per l'hash.

Ho testato:

risultati

Ogni risultato contiene il tempo di hash medio e il numero di collisioni

Hash           Lowercase      Random UUID  Numbers
=============  =============  ===========  ==============
Murmur            145 ns      259 ns          92 ns
                    6 collis    5 collis       0 collis
FNV-1a            152 ns      504 ns          86 ns
                    4 collis    4 collis       0 collis
FNV-1             184 ns      730 ns          92 ns
                    1 collis    5 collis       0 collis▪
DBJ2a             158 ns      443 ns          91 ns
                    5 collis    6 collis       0 collis▪▪▪
DJB2              156 ns      437 ns          93 ns
                    7 collis    6 collis       0 collis▪▪▪
SDBM              148 ns      484 ns          90 ns
                    4 collis    6 collis       0 collis**
SuperFastHash     164 ns      344 ns         118 ns
                   85 collis    4 collis   18742 collis
CRC32             250 ns      946 ns         130 ns
                    2 collis    0 collis       0 collis
LoseLose          338 ns        -             -
               215178 collis

Note :

Le collisioni avvengono effettivamente?

Sì. Ho iniziato a scrivere il mio programma di test per vedere se si verificano effettivamente collisioni di hash e non sono solo un costrutto teorico. Succedono davvero:

Collisioni FNV-1

  • creamwove si scontra con quists

Collisioni FNV-1a

  • costarring si scontra con liquid
  • declinate si scontra con macallums
  • altarage si scontra con zinke
  • altarages si scontra con zinkes

Collisioni Murmur2

  • cataract si scontra con periti
  • roquette si scontra con skivie
  • shawl si scontra con stormbound
  • dowlases si scontra con tramontane
  • cricketings si scontra con twanger
  • longans si scontra con whigs

Collisioni DJB2

  • hetairas si scontra con mentioner
  • heliotropes si scontra con neurospora
  • depravement si scontra con serafins
  • stylist si scontra con subgenera
  • joyful si scontra con synaphea
  • redescribed si scontra con urites
  • dram si scontra con vivency

Collisioni DJB2a

  • haggadot si scontra con loathsomenesses
  • adorablenesses si scontra con rentability
  • playwright si scontra con snush
  • playwrighting si scontra con snushing
  • treponematoses si scontra con waterbeds

Collisioni CRC32

  • codding si scontra con gnu
  • exhibiters si scontra con schlager

Collisioni SuperFastHash

  • dahabiah si scontra con drapability
  • encharm si scontra con enclave
  • grahams si scontra con gramary
  • ... taglia 79 collisioni ...
  • night si scontra con vigil
  • nights si scontra con vigils
  • finks si scontra con vinic

Randomnessification

L'altra misura soggettiva è la distribuzione casuale degli hash. La mappatura delle tabelle hash risultanti mostra la distribuzione uniforme dei dati. Tutte le funzioni hash mostrano una buona distribuzione quando si mappa la tabella in modo lineare:

Inserisci qui la descrizione dell'immagine

O come una mappa di Hilbert ( XKCD è sempre rilevante ):

Inserisci qui la descrizione dell'immagine

Tranne quando hashing stringhe numerici ( "1", "2", ..., "216553") (per esempio, i codici di avviamento postale ), in cui i modelli cominciano ad emergere nella maggior parte degli algoritmi di hashing:

SDBM :

Inserisci qui la descrizione dell'immagine

DJB2a :

Inserisci qui la descrizione dell'immagine

FNV-1 :

Inserisci qui la descrizione dell'immagine

Tutti tranne FNV-1a , che mi sembrano ancora abbastanza casuali:

Inserisci qui la descrizione dell'immagine

In effetti, Murmur2 sembra avere una casualità persino migliore Numbersdi FNV-1a:

Inserisci qui la descrizione dell'immagine

Quando guardo la FNV-1amappa dei "numeri", penso di vedere sottili schemi verticali. Con Murmur non vedo affatto schemi. Cosa ne pensi?


Gli extra *nella tabella indicano quanto sia grave la casualità. Con l' FNV-1aessere il migliore e DJB2xil peggio:

      Murmur2: .
       FNV-1a: .
        FNV-1: ▪
         DJB2: ▪▪
        DJB2a: ▪▪
         SDBM: ▪▪▪
SuperFastHash: .
          CRC: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
     Loselose: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
                                        ▪
                                 ▪▪▪▪▪▪▪▪▪▪▪▪▪
                        ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
          ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪

Inizialmente avevo scritto questo programma per decidere se dovevo preoccuparmi delle collisioni: lo faccio.

E poi si è verificato che le funzioni hash fossero sufficientemente casuali.

Algoritmo FNV-1a

L'hash FNV1 è disponibile in varianti che restituiscono hash a 32, 64, 128, 256, 512 e 1024 bit.

L' algoritmo FNV-1a è:

hash = FNV_offset_basis
for each octetOfData to be hashed
    hash = hash xor octetOfData
    hash = hash * FNV_prime
return hash

Dove le costanti FNV_offset_basise FNV_primedipendono dalla dimensione dell'hash di ritorno che si desidera:

Hash Size  
===========
32-bit
    prime: 2^24 + 2^8 + 0x93 = 16777619
    offset: 2166136261
64-bit
    prime: 2^40 + 2^8 + 0xb3 = 1099511628211
    offset: 14695981039346656037
128-bit
    prime: 2^88 + 2^8 + 0x3b = 309485009821345068724781371
    offset: 144066263297769815596495629667062367629
256-bit
    prime: 2^168 + 2^8 + 0x63 = 374144419156711147060143317175368453031918731002211
    offset: 100029257958052580907070968620625704837092796014241193945225284501741471925557
512-bit
    prime: 2^344 + 2^8 + 0x57 = 35835915874844867368919076489095108449946327955754392558399825615420669938882575126094039892345713852759
    offset: 9659303129496669498009435400716310466090418745672637896108374329434462657994582932197716438449813051892206539805784495328239340083876191928701583869517785
1024-bit
    prime: 2^680 + 2^8 + 0x8d = 5016456510113118655434598811035278955030765345404790744303017523831112055108147451509157692220295382716162651878526895249385292291816524375083746691371804094271873160484737966720260389217684476157468082573
    offset: 1419779506494762106872207064140321832088062279544193396087847491461758272325229673230371772250864096521202355549365628174669108571814760471015076148029755969804077320157692458563003215304957150157403644460363550505412711285966361610267868082893823963790439336411086884584107735010676915

Vedi la pagina principale di FNV per i dettagli.

Tutti i miei risultati sono con la variante a 32 bit.

FNV-1 meglio di FNV-1a?

No. FNV-1a è tutto meglio. Ci sono state più collisioni con FNV-1a quando si utilizzava la parola inglese corpus:

Hash    Word Collisions
======  ===============
FNV-1   1
FNV-1a  4

Ora confronta lettere minuscole e maiuscole:

Hash    lowercase word Collisions  UPPERCASE word collisions
======  =========================  =========================
FNV-1   1                          9
FNV-1a  4                          11

In questo caso l'FNV-1a non è "400%" peggiore dell'FN-1, solo il 20% peggiore.

Penso che la cosa più importante sia che ci sono due classi di algoritmi quando si tratta di collisioni:

  • collisioni rare : FNV-1, FNV-1a, DJB2, DJB2a, SDBM
  • collisioni comuni : SuperFastHash, Loselose

E poi c'è la distribuzione uniforme degli hash:

  • distribuzione eccezionale: Murmur2, FNV-1a, SuperFastHas
  • ottima distribuzione: FNV-1
  • buona distribuzione: SDBM, DJB2, DJB2a
  • orribile distribuzione: Loselose

Aggiornare

Mormorio? Certo, perché no


Aggiornare

@whatshisname si chiedeva come avrebbe funzionato un CRC32 , aggiungendo numeri alla tabella.

CRC32 è abbastanza buono . Poche collisioni, ma più lente, e il sovraccarico di una tabella di ricerca 1k.

Taglia tutte le cose errate sulla distribuzione CRC - il mio male


Fino ad oggi stavo per usare FNV-1a come il mio , di fatto, algoritmo di hash hash-table. Ma ora sto passando a Murmur2:

  • Più veloce
  • Migliore casualità di tutte le classi di input

E davvero, spero davvero che ci sia qualcosa che non va SuperFastHashnell'algoritmo che ho trovato ; è un peccato essere così popolare come è.

Aggiornamento: dalla home page MurmurHash3 su Google :

(1) - SuperFastHash ha proprietà di collisione molto scarse, che sono state documentate altrove.

Quindi suppongo che non sono solo io.

Aggiornamento: ho capito perché Murmurè più veloce degli altri. MurmurHash2 funziona su quattro byte alla volta. La maggior parte degli algoritmi sono byte per byte :

for each octet in Key
   AddTheOctetToTheHash

Ciò significa che man mano che le chiavi si allungano, il soffio ha la possibilità di brillare.


Aggiornare

I GUID sono progettati per essere unici, non casuali

Un post tempestivo di Raymond Chen ribadisce il fatto che i GUID "casuali" non sono pensati per essere utilizzati per la loro casualità. Loro, o un loro sottoinsieme, non sono adatti come chiave hash:

Anche l'algoritmo GUID versione 4 non è garantito come imprevedibile, poiché l'algoritmo non specifica la qualità del generatore di numeri casuali. L'articolo di Wikipedia per GUID contiene ricerche primarie che suggeriscono che i GUID futuri e precedenti possono essere previsti in base alla conoscenza dello stato del generatore di numeri casuali, poiché il generatore non è crittograficamente forte.

La casualità non è la stessa come evitare le collisioni; ed è per questo che sarebbe un errore provare a inventare il proprio algoritmo di "hashing" prendendo un sottoinsieme di una guida "casuale":

int HashKeyFromGuid(Guid type4uuid)
{
   //A "4" is put somewhere in the GUID.
   //I can't remember exactly where, but it doesn't matter for
   //the illustrative purposes of this pseudocode
   int guidVersion = ((type4uuid.D3 & 0x0f00) >> 8);
   Assert(guidVersion == 4);

   return (int)GetFirstFourBytesOfGuid(type4uuid);
}

Nota : ancora una volta, ho inserito il "GUID casuale" tra virgolette, perché è la variante "casuale" dei GUID. Una descrizione più accurata sarebbe Type 4 UUID. Ma nessuno sa cosa siano i tipi 4 o 1, 3 e 5. Quindi è più semplice chiamarli GUID "casuali".

Tutti gli specchi di parole inglesi


41
Sarebbe davvero interessante vedere come si confronta SHA, non perché è un buon candidato per un algoritmo di hashing qui, ma sarebbe davvero interessante vedere come ogni hash crittografico si confronta con questi algoritmi di velocità.
Michael,

8
Un nuovo hash chiamato 'xxHash', di Yann Collet, stava facendo il giro di recente. Sono sempre sospettoso di un nuovo hash. Sarebbe interessante vederlo nel tuo confronto, (se non sei stanco di persone che suggeriscono hash casuali di cui hanno sentito parlare da aggiungere ...)
th_in_gs

7
Infatti. I numeri delle prestazioni annunciati dalla pagina del progetto xxHash sembrano impressionanti, forse troppo per essere veri. Beh, almeno, è un progetto open source: code.google.com/p/xxhash
ATTracker

9
Ciao Ian, la mia implementazione Delphi di SuperFastHash è corretta. Durante l'implementazione ho creato un set di test in C e Delphi per confrontare i risultati della mia implementazione e l'implementazione di riferimento. Non ci sono differenze Quindi quello che vedi è la vera cattiveria dell'hash ... (Ecco perché ho anche pubblicato un'implementazione di MurmurHash : landman-code.blogspot.nl/2009/02/… )
Davy Landman il

19
Il poster è consapevole che questa non è solo una risposta fantastica - questa è la risorsa di riferimento de facto del mondo sull'argomento? Ogni volta che ho bisogno di occuparmi degli hash, questo risolve il mio problema in modo così rapido e autorevole che non ho mai bisogno di nient'altro.
MaiaVictor,

59

Se desideri creare una mappa hash da un dizionario immutabile, potresti prendere in considerazione la creazione di hash perfetti https://en.wikipedia.org/wiki/Perfect_hash_function - durante la costruzione della funzione hash e della tabella hash, puoi garantire, per un determinato set di dati, che non ci saranno collisioni.


2
Ecco di più su (minimo) Hashing perfetto burtleburtle.net/bob/hash/perfect.html inclusi i dati sulle prestazioni, sebbene non utilizzi il processore più recente, ecc.
Ellie Kesselman,

4
È abbastanza ovvio, ma vale la pena sottolineare che per garantire l'assenza di collisioni, le chiavi dovrebbero avere le stesse dimensioni dei valori, a meno che non ci siano vincoli sui valori su cui l'algoritmo può capitalizzare.
devios1

1
@ devios1 La tua affermazione non ha senso. Innanzitutto, i valori in una tabella hash, perfetti o meno, sono indipendenti dalle chiavi. In secondo luogo, una tabella hash perfetta è solo una matrice lineare di valori, indicizzata dal risultato di una funzione creata in modo tale che tutti gli indici siano unici.
Jim Balter,

1
@MarcusJ L'hashing perfetto viene solitamente utilizzato con meno di 100 tasti, ma dai un'occhiata a cmph.sourceforge.net ... ancora molto al di sotto del tuo raggio d'azione.
Jim Balter,

1
@DavidCary Nulla nel tuo link supporta la tua richiesta. Forse hai confuso O (1) con "nessuna collisione", ma non sono affatto la stessa cosa. Naturalmente, l'hash perfetto non garantisce collisioni, ma richiede che tutte le chiavi siano note in anticipo e che ce ne siano relativamente poche. (Ma vedi il link a cmph sopra.)
Jim Balter,

34

Ecco un elenco di funzioni hash, ma la versione breve è:

Se vuoi solo avere una buona funzione di hash e non puoi aspettare, djb2è una delle migliori funzioni di hash di stringa che conosco. Ha un'eccellente distribuzione e velocità su molti diversi set di chiavi e dimensioni delle tabelle

unsigned long
hash(unsigned char *str)
{
    unsigned long hash = 5381;
    int c;

    while (c = *str++)
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */

    return hash;
}

6
In realtà djb2 è sensibile allo zero, poiché la maggior parte di tali funzioni hash semplici, quindi puoi facilmente rompere tali hash. Ha una cattiva propensione per troppe collisioni e una cattiva distribuzione, si rompe su molti test di qualità più sfocati: vedi github.com/rurban/smhasher/blob/master/doc/bernstein Il suo database cdb lo usa, ma non lo userei con accesso pubblico.
rurban,

2
DJB è piuttosto male dal punto di vista delle prestazioni e della distribuzione. Non lo userei oggi.
Conrad Meyer,

@ConradMeyer Scommetto che DJB può essere accelerato di un fattore tre proprio come in questa mia domanda e quindi probabilmente avrebbe battuto la maggior parte degli algoritmi utilizzabili. Per quanto riguarda la distribuzione, sono d'accordo. Un hash che produce collisioni anche per stringhe di due lettere non può essere davvero buono.
Maaartinus,

28

CityHash di Google è l'algoritmo che stai cercando. Non è buono per la crittografia ma è buono per generare hash unici.

Leggi il blog per maggiori dettagli e il codice è disponibile qui .

CityHash è scritto in C ++. V'è anche un porto C pianura .

Informazioni sul supporto a 32 bit:

Tutte le funzioni di CityHash sono ottimizzate per processori a 64 bit. Detto questo, verranno eseguiti (ad eccezione di quelli nuovi che utilizzano SSE4.2) in codice a 32 bit. Non saranno molto veloci però. Potresti voler usare Murmur o qualcos'altro nel codice a 32 bit.


11
CityHash è pronunciato simile a "City Sushi?"
Eric

2
Dai un'occhiata anche a SipHash, è pensato per sostituire MurmurHash / CityHash / ecc. : 131002.net/siphash
Török Edwin,

3
Vedi anche FarmHash, un successore di CitHash. code.google.com/p/farmhash
stevendaniels

7
xxHash afferma di essere 5 volte più veloce di CityHash.
Clay Bridges,

plain C portil collegamento è interrotto
makerj,

20

Ho tracciato un confronto a breve velocità di diversi algoritmi di hashing durante i file di hashing.

I singoli grafici differiscono solo leggermente nel metodo di lettura e possono essere ignorati qui, poiché tutti i file sono stati memorizzati in un tmpfs. Pertanto, il punto di riferimento non era vincolato all'IO se ti stai chiedendo.

Algoritmi includono: SpookyHash, CityHash, Murmur3, MD5, SHA{1,256,512}.

conclusioni:

  • Le funzioni hash non crittografiche come Murmur3, Cityhash e Spooky sono piuttosto vicine tra loro. Si dovrebbe notare che Cityhash potrebbe essere più veloce su CPU con CRCistruzioni SSE 4.2s , che la mia CPU non ha. Nel mio caso SpookyHash era sempre un po 'prima di CityHash.
  • MD5 sembra essere un buon compromesso quando si utilizzano le funzioni di hash crittografico, sebbene SHA256 possa essere più sicuro delle vulnerabilità di collisione di MD5 e SHA1.
  • La complessità di tutti gli algoritmi è lineare, il che non sorprende, dal momento che funzionano in senso antiorario. (Volevo vedere se il metodo di lettura fa la differenza, quindi puoi semplicemente confrontare i valori più giusti).
  • SHA256 era più lento di SHA512.
  • Non ho studiato la casualità delle funzioni hash. Ma ecco un buon confronto tra le funzioni hash che mancano nella risposta di Ian Boyds . Questo sottolinea che CityHash ha dei problemi in casi d'angolo.

La fonte utilizzata per i grafici:


1
Il grafico della scala lineare interrompe l'etichetta dell'asse y che indica la quantità da tracciare. Immagino che probabilmente sarebbe "tempo in secondi", uguale alla scala logaritmica. Vale la pena aggiustarlo.
Craig McQueen,

18

Gli algoritmi SHA (incluso SHA-256) sono progettati per essere veloci .

In effetti, la loro velocità può essere un problema a volte. In particolare, una tecnica comune per l'archiviazione di un token derivato da password consiste nell'eseguire un algoritmo hash veloce standard 10.000 volte (memorizzando l'hash dell'hash dell'hash dell'hash della ... password).

#!/usr/bin/env ruby
require 'securerandom'
require 'digest'
require 'benchmark'

def run_random_digest(digest, count)
  v = SecureRandom.random_bytes(digest.block_length)
  count.times { v = digest.digest(v) }
  v
end

Benchmark.bmbm do |x|
  x.report { run_random_digest(Digest::SHA256.new, 1_000_000) }
end

Produzione:

Rehearsal ------------------------------------
   1.480000   0.000000   1.480000 (  1.391229)
--------------------------- total: 1.480000sec

       user     system      total        real
   1.400000   0.000000   1.400000 (  1.382016)

57
È relativamente veloce, certo, per un algoritmo di hash crittografico . Ma l'OP vuole solo memorizzare i valori in una tabella hash e non credo che una funzione di crittografia hash sia davvero appropriata per questo.
Dean Harding,

6
La domanda ha sollevato (tangenzialmente, ora appare) l'oggetto delle funzioni hash crittografiche. Questo è il punto a cui sto rispondendo.
yfeldblum,

15
Solo per scoraggiare l'idea di "In particolare, una tecnica comune per la memorizzazione di un token derivato da password è eseguire un algoritmo di hash veloce standard 10.000 volte" - mentre comune, è semplicemente stupido. Esistono algoritmi progettati per questi scenari, ad es bcrypt. Usa gli strumenti giusti.
TC1

3
Gli hash crittografici sono progettati per avere un throughput elevato, ma ciò spesso significa che hanno .rodatacosti di installazione, smontaggio e / o stato elevati. Quando si desidera un algoritmo per una tabella hash, di solito si hanno chiavi molto brevi e molte di esse, ma non sono necessarie le garanzie aggiuntive di una crittografia. Uso personalmente un Jenkins ottimizzato.
mirabilos,

1
@ChrisMorgan: piuttosto che usare un hash crittograficamente sicuro, HashTable DoS può essere risolto in modo molto più efficiente usando la randomizzazione dell'hash, in modo che ogni esecuzione dei programmi o persino su ogni hashtable, in modo che i dati non vengano raggruppati nello stesso bucket ogni volta .
Lie Ryan,

14

So che ci sono cose come SHA-256 e simili, ma questi algoritmi sono progettati per essere sicuri , il che di solito significa che sono più lenti degli algoritmi che sono meno unici .

L'ipotesi che le funzioni hash crittografiche siano più uniche è errata, e in effetti si può dimostrare che nella pratica è spesso arretrato. In verità:

  1. Le funzioni hash crittografiche dovrebbero idealmente essere indistinguibili da quelle casuali ;
  2. Ma con funzioni hash non crittografiche, è desiderabile che interagiscano favorevolmente con input probabili .

Ciò significa che una funzione hash non crittografica potrebbe avere meno collisioni rispetto a una crittografia per un set di dati "valido", set di dati per cui è stato progettato.

Possiamo effettivamente dimostrarlo con i dati nella risposta di Ian Boyd e un po 'di matematica: il problema del compleanno . La formula per il numero previsto di coppie in collisione se scegli nnumeri interi casuali dal set [1, d]è questa (presa da Wikipedia):

n - d + d * ((d - 1) / d)^n

Collegamento n= 216.553 e d= 2 ^ 32 otteniamo circa 5,5 collisioni attese . I test di Ian mostrano principalmente risultati in quel quartiere, ma con una drammatica eccezione: la maggior parte delle funzioni ha avuto zero collisioni nei test numerici consecutivi. La probabilità di scegliere casualmente 216.553 numeri a 32 bit e ottenere zero collisioni è di circa lo 0,43%. E questo è solo per una funzione: qui abbiamo cinque famiglie di funzioni hash distinte con zero collisioni!

Quindi quello che stiamo vedendo qui è che gli hash che Ian ha testato interagiscono favorevolmente con il set di dati di numeri consecutivi, cioè stanno disperdendo input minimamente diversi più ampiamente di quanto farebbe una funzione hash crittografica ideale. (Nota a margine: questo significa che la valutazione grafica di Ian secondo cui FNV-1a e MurmurHash2 "gli sembrano casuali" nel set di dati numerici può essere smentita dai suoi stessi dati. Zero collisioni su un set di dati di quelle dimensioni, per entrambe le funzioni hash, è sorprendentemente non casuale!)

Questa non è una sorpresa perché è un comportamento desiderabile per molti usi delle funzioni hash. Ad esempio, le chiavi della tabella hash sono spesso molto simili; La risposta di Ian menziona un problema che MSN aveva una volta con le tabelle hash del codice postale . Questo è un uso in cui l'evitamento alle collisioni su input probabili vince su comportamenti casuali.

Un altro confronto istruttivo qui è il contrasto negli obiettivi di progettazione tra CRC e le funzioni hash crittografiche:

  • CRC è progettato per rilevare gli errori risultanti da canali di comunicazione rumorosi , che potrebbero essere un numero limitato di lanci di bit;
  • Gli hash crittografici sono progettati per catturare le modifiche apportate da aggressori malintenzionati , a cui sono assegnate risorse computazionali limitate ma arbitrariamente molta intelligenza.

Quindi per CRC è di nuovo bene avere meno collisioni che casuali in input minimamente diversi. Con gli hash crittografici, questo è un no-no!


10

Usa SipHash . Ha molte proprietà desiderabili:

  • Veloce. Un'implementazione ottimizzata richiede circa 1 ciclo per byte.

  • Sicuro. SipHash è un PRF potente (funzione pseudocasuale). Ciò significa che è indistinguibile da una funzione casuale (a meno che non si conosca la chiave segreta a 128 bit). Quindi:

    • Non è necessario preoccuparsi che le sonde della tabella di hash diventino lineari a causa delle collisioni. Con SipHash, sai che otterrai prestazioni nel caso medio in media, indipendentemente dagli input.

    • Immunità agli attacchi denial of service basati sull'hash.

    • È possibile utilizzare SipHash (in particolare la versione con output a 128 bit) come MAC (Message Authentication Code). Se ricevi un messaggio e un tag SipHash e il tag è lo stesso dell'esecuzione di SipHash con la tua chiave segreta, allora sai che chiunque ha creato l'hash era anche in possesso della tua chiave segreta e che né il messaggio né il l'hash è stato modificato da allora.


1
SipHash non è eccessivo a meno che tu non abbia bisogno di sicurezza? Richiede una chiave a 128 bit che è solo un seme hash glorificato. Per non parlare di MurmurHash3 ha un output a 128 bit e SipHash ha solo un output a 64 bit. Ovviamente il digest più grande ha una probabilità di collisione inferiore.
bryc,

@bryc La differenza è che SipHash continuerà ad essere ben educato, anche con input dannosi. Una tabella hash basata su SipHash può essere utilizzata per dati provenienti da fonti potenzialmente ostili e può utilizzare un algoritmo come il probing lineare molto sensibile ai dettagli della funzione hash.
Demi

9

Dipende dai dati che stai eseguendo l'hashing. Alcuni hash funzionano meglio con dati specifici come il testo. Alcuni algoritmi di hashing erano specificamente progettati per essere utili per dati specifici.

Paul Hsieh una volta ha fatto hashish veloce . Elenca il codice sorgente e le spiegazioni. Ma era già stato battuto. :)


6

Java utilizza questo semplice algoritmo moltiplica e aggiungi:

Il codice hash per un oggetto String viene calcolato come

 s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

usando int arithmetic, dove s[i]è l' i -esimo carattere della stringa, nè la lunghezza della stringa e ^indica esponenziazione. (Il valore di hash della stringa vuota è zero.)

Probabilmente ce ne sono di migliori là fuori, ma questo è abbastanza diffuso e sembra essere un buon compromesso tra velocità e unicità.


12
Non userei esattamente lo stesso usato qui, poiché è ancora relativamente facile produrre collisioni con questo. Non è assolutamente terribile, ma ce ne sono di molto migliori là fuori. E se non c'è motivo significativo per essere compatibile con Java, dovrebbe non essere scelto.
Joachim Sauer,

4
Se scegli ancora questo metodo di hashing per qualche motivo, potresti almeno utilizzare un numero primo migliore come 92821 come moltiplicatore. Ciò riduce molto le collisioni. stackoverflow.com/a/2816747/21499
Hans-Peter Störr,

1
Si potrebbe anche usare FNV1a invece. È anche un semplice hash basato sulla moltiplicazione, ma utilizza un moltiplicatore più grande, che disperde meglio l'hash.
bryc,

4

Prima di tutto, perché devi implementare il tuo hash? Per la maggior parte delle attività dovresti ottenere buoni risultati con strutture di dati da una libreria standard, supponendo che sia disponibile un'implementazione (a meno che tu non lo stia facendo solo per la tua formazione).

Per quanto riguarda gli algoritmi di hash, il mio preferito è FNV. 1

Ecco un esempio di implementazione della versione a 32 bit in C:

unsigned long int FNV_hash(void* dataToHash, unsigned long int length)
{
  unsigned char* p = (unsigned char *) dataToHash;
  unsigned long int h = 2166136261UL;
  unsigned long int i;

  for(i = 0; i < length; i++)
    h = (h * 16777619) ^ p[i] ;

  return h;
}

2
La variante FNV-1a è leggermente migliore con casualità. Scambia l'ordine di *e ^: h = (h * 16777619) ^ p[i]==>h = (h ^ p[i]) * 16777619
Ian Boyd il
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.