Welcher Hashalgorithmus eignet sich am besten für die Eindeutigkeit und Geschwindigkeit?

1388

Welcher Hashalgorithmus eignet sich am besten für die Eindeutigkeit und Geschwindigkeit? Beispiel (gute) Verwendungen beinhalten Hash-Wörterbücher.

Ich weiß, dass es Dinge wie SHA-256 und dergleichen gibt, aber diese Algorithmen sind so konzipiert , dass sie sicher sind , was normalerweise bedeutet, dass sie langsamer sind als Algorithmen, die weniger einzigartig sind . Ich möchte, dass ein Hash-Algorithmus schnell ist und dennoch ziemlich einzigartig bleibt, um Kollisionen zu vermeiden.

Earlz
quelle
9
Zu welchem ​​Zweck, Sicherheit oder anderen?
Orbling
19
@Orbling, zur Implementierung eines Hash-Wörterbuchs. Daher sollten Kollisionen so gering wie möglich gehalten werden, sie haben jedoch keinen Sicherheitszweck.
Earlz
4
Beachten Sie, dass Sie mindestens einige Kollisionen in Ihrer Hash-Tabelle erwarten müssen , andernfalls muss die Tabelle riesig sein, um auch eine relativ kleine Anzahl von Schlüsseln verarbeiten zu können ...
Dean Harding
19
Guter Eintrag! Könnten Sie auch Yann Collets xxHash (Schöpfer oder LZ4) überprüfen, der doppelt so schnell ist wie Murmeln? Homepage: code.google.com/p/xxhash Weitere Informationen: fastcompression.blogspot.fr/2012/04/…
24
@zvrba Hängt vom Algorithmus ab. bcrypt ist so konzipiert, dass es langsam ist.
Izkata

Antworten:

2461

Ich habe verschiedene Algorithmen getestet, die Geschwindigkeit und die Anzahl der Kollisionen messen.

Ich habe drei verschiedene Schlüsselsätze verwendet:

Für jeden Korpus wurde die Anzahl der Kollisionen und die durchschnittliche Zeit, die für das Hashing aufgewendet wurde, aufgezeichnet.

Ich habe getestet:

Ergebnisse

Jedes Ergebnis enthält die durchschnittliche Hash-Zeit und die Anzahl der Kollisionen

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

Anmerkungen :

Treten tatsächlich Kollisionen auf?

Ja. Ich habe angefangen, mein Testprogramm zu schreiben, um festzustellen, ob tatsächlich Hash-Kollisionen auftreten - und das ist nicht nur ein theoretisches Konstrukt. Sie passieren tatsächlich:

FNV-1-Kollisionen

  • creamwove kollidiert mit quists

FNV-1a-Kollisionen

  • costarring kollidiert mit liquid
  • declinate kollidiert mit macallums
  • altarage kollidiert mit zinke
  • altarages kollidiert mit zinkes

Murmel2-Kollisionen

  • cataract kollidiert mit periti
  • roquette kollidiert mit skivie
  • shawl kollidiert mit stormbound
  • dowlases kollidiert mit tramontane
  • cricketings kollidiert mit twanger
  • longans kollidiert mit whigs

DJB2-Kollisionen

  • hetairas kollidiert mit mentioner
  • heliotropes kollidiert mit neurospora
  • depravement kollidiert mit serafins
  • stylist kollidiert mit subgenera
  • joyful kollidiert mit synaphea
  • redescribed kollidiert mit urites
  • dram kollidiert mit vivency

DJB2a Kollisionen

  • haggadot kollidiert mit loathsomenesses
  • adorablenesses kollidiert mit rentability
  • playwright kollidiert mit snush
  • playwrighting kollidiert mit snushing
  • treponematoses kollidiert mit waterbeds

CRC32-Kollisionen

  • codding kollidiert mit gnu
  • exhibiters kollidiert mit schlager

SuperFastHash-Kollisionen

  • dahabiah kollidiert mit drapability
  • encharm kollidiert mit enclave
  • grahams kollidiert mit gramary
  • ... 79 Kollisionen ausschneiden ...
  • night kollidiert mit vigil
  • nights kollidiert mit vigils
  • finks kollidiert mit vinic

Randomnessification

Das andere subjektive Maß ist, wie zufällig die Hashes verteilt sind. Die Zuordnung der resultierenden HashTables zeigt, wie gleichmäßig die Daten verteilt sind. Alle Hash-Funktionen zeigen eine gute Verteilung, wenn die Tabelle linear abgebildet wird:

Geben Sie hier eine Bildbeschreibung ein

Oder als Hilbert Map ( XKCD ist immer relevant ):

Geben Sie hier eine Bildbeschreibung ein

Außer , wenn Hashing Zahlenketten ( "1", "2"..., "216553") (zB Postleitzahlen ), wo Muster beginnen in den meisten der Hash - Algorithmen entstehen:

SDBM :

Geben Sie hier eine Bildbeschreibung ein

DJB2a :

Geben Sie hier eine Bildbeschreibung ein

FNV-1 :

Geben Sie hier eine Bildbeschreibung ein

Alle außer FNV-1a , die für mich immer noch ziemlich zufällig aussehen:

Geben Sie hier eine Bildbeschreibung ein

Tatsächlich scheint Murmur2 eine noch bessere Zufälligkeit zu haben Numbersals FNV-1a:

Geben Sie hier eine Bildbeschreibung ein

Als ich im Blick FNV-1a„Nummer“ Karte, ich glaube , ich subtile vertikale Muster zu sehen. Mit Murmeln sehe ich überhaupt keine Muster. Was denkst du?


Das Extra *in der Tabelle gibt an, wie schlecht die Zufälligkeit ist. Mit FNV-1ader Beste zu sein, und DJB2xist das Schlimmste:

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

Ich habe dieses Programm ursprünglich geschrieben, um zu entscheiden, ob ich mir überhaupt Gedanken über Kollisionen machen musste: Ich tue es.

Und dann wurde sichergestellt, dass die Hash-Funktionen ausreichend zufällig waren.

FNV-1a-Algorithmus

Der FNV1-Hash gibt es in Varianten, die 32-, 64-, 128-, 256-, 512- und 1024-Bit-Hashes zurückgeben.

Der FNV-1a-Algorithmus lautet:

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

Wo die Konstanten FNV_offset_basisund FNV_primevon der gewünschten Rückgabe-Hash-Größe abhängen:

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

Einzelheiten finden Sie auf der FNV-Hauptseite .

Alle meine Ergebnisse sind mit der 32-Bit-Variante.

FNV-1 besser als FNV-1a?

Nein, FNV-1a ist rundum besser. Es gab mehr Kollisionen mit FNV-1a, wenn das englische Wort Corpus verwendet wurde:

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

Vergleichen Sie nun Klein- und Großbuchstaben:

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

In diesem Fall ist FNV-1a nicht "400%" schlechter als FN-1, sondern nur 20% schlechter.

Ich denke, der wichtigste Aspekt ist, dass es zwei Klassen von Algorithmen gibt, wenn es um Kollisionen geht:

  • Kollisionen selten : FNV-1, FNV-1a, DJB2, DJB2a, SDBM
  • Kollisionen häufig : SuperFastHash, Loselose

Und dann ist da noch die gleichmäßige Verteilung der Hashes:

  • Hervorragende Verbreitung: Murmur2, FNV-1a, SuperFastHas
  • ausgezeichnete Verteilung: FNV-1
  • gute Verteilung: SDBM, DJB2, DJB2a
  • schreckliche Verbreitung: Loselose

Aktualisieren

Murmeln? Sicher warum nicht


Aktualisieren

@whatshisname fragte sich, wie sich ein CRC32 verhalten würde und fügte der Tabelle Zahlen hinzu.

CRC32 ist ziemlich gut . Nur wenige Kollisionen, aber langsamer, und der Overhead einer 1k-Nachschlagetabelle.

Schnüffeln Sie alle fehlerhaften Informationen über die CRC-Verteilung - meine schlechte


Bis heute wollte ich FNV-1a als de facto Hash-Tabellen-Hashing-Algorithmus verwenden. Aber jetzt wechsle ich zu Murmur2:

  • Schneller
  • Bessere Randomisierung aller Inputklassen

Und ich, wirklich wirklich hoffen , dass es etwas falsch mit dem SuperFastHashAlgorithmus , den ich gefunden ; Es ist schade, so beliebt zu sein, wie es ist.

Update: Von der MurmurHash3-Homepage bei Google :

(1) - SuperFastHash weist sehr schlechte Kollisionseigenschaften auf, die an anderer Stelle dokumentiert wurden.

Also denke ich, dass es nicht nur ich bin.

Update: Mir ist aufgefallen, warum Murmures schneller ist als die anderen. MurmurHash2 verarbeitet jeweils vier Bytes. Die meisten Algorithmen sind byteweise :

for each octet in Key
   AddTheOctetToTheHash

Dies bedeutet, dass Murmeln mit länger werdenden Tasten seine Chance hat zu leuchten.


Aktualisieren

GUIDs sind eindeutig und nicht zufällig

Ein rechtzeitiger Beitrag von Raymond Chen weist erneut darauf hin, dass "zufällige" GUIDs nicht für ihre Zufälligkeit verwendet werden sollen. Sie oder eine Teilmenge davon sind als Hash-Schlüssel ungeeignet:

Selbst der GUID-Algorithmus der Version 4 kann nicht als unvorhersehbar eingestuft werden, da der Algorithmus nicht die Qualität des Zufallszahlengenerators angibt. Der Wikipedia-Artikel für GUID enthält Primärrecherchen, die darauf hindeuten, dass zukünftige und frühere GUIDs auf der Grundlage der Kenntnis des Status des Zufallszahlengenerators vorhergesagt werden können, da der Generator nicht kryptografisch stark ist.

Zufälligkeit ist nicht dasselbe wie Kollisionsvermeidung. Aus diesem Grund wäre es ein Fehler, einen eigenen "Hashing" -Algorithmus zu erfinden, indem Sie eine Teilmenge einer "zufälligen" Guid verwenden:

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);
}

Hinweis : Auch hier setze ich "zufällige GUID" in Anführungszeichen, da es sich um die "zufällige" Variante von GUIDs handelt. Eine genauere Beschreibung wäre Type 4 UUID. Aber niemand weiß, was Typ 4 oder Typ 1, 3 und 5 sind. Es ist also einfacher, sie als "zufällige" GUIDs zu bezeichnen.

Alle englischen Wörter spiegeln

Ian Boyd
quelle
41
Es wäre wirklich interessant zu sehen, wie SHA verglichen wird, nicht weil es ein guter Kandidat für einen Hash-Algorithmus ist, aber es wäre wirklich interessant zu sehen, wie ein kryptografischer Hash mit diesen für Geschwindigkeitsalgorithmen erstellten verglichen wird.
Michael
8
Ein neuer Hash mit dem Namen 'xxHash' von Yann Collet hat kürzlich die Runde gemacht. Ich bin immer misstrauisch gegenüber einem neuen Hash. Es wäre interessant, dies in Ihrem Vergleich zu sehen (wenn Sie nicht müde sind, wenn Leute zufällige Hashes vorschlagen, von denen sie gehört haben, dass sie hinzugefügt werden sollen ...)
th_in_gs
7
Tatsächlich. Die von der xxHash-Projektseite angekündigten Leistungszahlen sehen beeindruckend aus, vielleicht zu viel, um wahr zu sein. Zumindest ist es ein Open-Source-Projekt: code.google.com/p/xxhash
ATTracker
9
Hallo Ian, meine Delphi-Implementierung von SuperFastHash ist korrekt. Bei der Implementierung habe ich in C und Delphi ein Testset erstellt, um die Ergebnisse meiner Implementierung und der Referenzimplementierung zu vergleichen. Es gibt keine Unterschiede. Was Sie also sehen, ist die tatsächliche Schlechtigkeit des Hashs ... (Aus diesem Grund habe ich auch eine MurmurHash-Implementierung veröffentlicht: landman-code.blogspot.nl/2009/02/… )
Davy Landman,
19
Ist dem Poster bewusst, dass dies nicht nur eine großartige Antwort ist - dies ist die de facto Referenzressource der Welt zu diesem Thema? Jedes Mal, wenn ich mit Hashes arbeiten muss, wird mein Problem so schnell und zuverlässig gelöst, dass ich nie etwas anderes benötige.
MaiaVictor
59

Wenn Sie eine Hash-Map aus einem unveränderten Wörterbuch erstellen möchten, möchten Sie möglicherweise das perfekte Hashing in Betracht ziehen. Https://en.wikipedia.org/wiki/Perfect_hash_function - während der Erstellung der Hash-Funktion und der Hash-Tabelle können Sie Folgendes garantieren: Für einen bestimmten Datensatz gibt es keine Kollisionen.

Damien
quelle
2
Hier ist mehr über (minimal) Perfect Hashing burtleburtle.net/bob/hash/perfect.html einschließlich Leistungsdaten, obwohl es nicht den aktuellsten Prozessor usw. verwendet
Ellie Kesselman
4
Es ist ziemlich offensichtlich, aber es sollte darauf hingewiesen werden, dass die Schlüssel dieselbe Größe wie die Werte haben müssen, um keine Kollisionen zu garantieren, es sei denn, es gibt Einschränkungen für die Werte, auf denen der Algorithmus aufbauen kann.
Devios1
1
@ devios1 Deine Aussage ist bedeutungslos. Erstens sind die Werte in einer Hash-Tabelle, ob perfekt oder nicht, unabhängig von den Schlüsseln. Zweitens ist eine perfekte Hash-Tabelle nur ein lineares Array von Werten, die durch das Ergebnis der Funktion indiziert werden, die so gestaltet wurde, dass alle Indizes eindeutig sind.
Jim Balter
1
@MarcusJ Perfect Hashing wird normalerweise mit weniger als 100 Schlüsseln verwendet, aber werfen Sie einen Blick auf cmph.sourceforge.net ... immer noch weit außerhalb Ihrer Reichweite.
Jim Balter
1
@DavidCary Ihre Behauptung wird durch nichts in Ihrem Link gestützt. Möglicherweise haben Sie O (1) mit "keine Kollisionen" verwechselt, aber sie sind überhaupt nicht dasselbe. Perfektes Hashing garantiert natürlich keine Kollisionen, setzt jedoch voraus, dass alle Schlüssel im Voraus bekannt sind und relativ wenige vorhanden sind. (Siehe aber den Link zu cmph oben.)
Jim Balter
34

Hier ist eine Liste von Hash-Funktionen, aber die Kurzversion ist:

Wenn Sie nur eine gute Hash-Funktion haben wollen und nicht warten können, djb2ist dies eine der besten String-Hash-Funktionen, die ich kenne. Es verfügt über eine hervorragende Verteilung und Geschwindigkeit auf vielen verschiedenen Schlüsselsätzen und Tischgrößen

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;
}
Dean Harding
quelle
6
Tatsächlich ist djb2 null-sensitiv, da die meisten dieser einfachen Hash-Funktionen, so dass Sie solche Hashes leicht knacken können. Es hat eine schlechte Bias zu viele Kollisionen und eine schlechte Verteilung, es auf den meisten smhasher Qualitätstests bricht: Siehe github.com/rurban/smhasher/blob/master/doc/bernstein Seine cdb - Datenbank verwendet, aber ich würde es nicht verwenden mit öffentlichem zugang.
Rurban
2
DJB ist vom Standpunkt der Performance und Distribution ziemlich schlecht. Ich würde es heute nicht benutzen.
Conrad Meyer
@ConradMeyer Ich wette, DJB kann wie in meiner Frage um den Faktor drei beschleunigt werden, und dann würde es wahrscheinlich die meisten verwendbaren Algorithmen übertreffen. In Bezug auf die Verteilung stimme ich zu. Ein Hash, der selbst für zwei Buchstabenketten Kollisionen erzeugt, kann nicht wirklich gut sein.
Maaartinus
28

CityHash von Google ist der Algorithmus, den Sie suchen. Es ist nicht gut für die Kryptographie, aber es ist gut für die Erzeugung von eindeutigen Hashes.

Lesen Sie den Blog für weitere Details und den Code finden Sie hier .

CityHash ist in C ++ geschrieben. Es gibt auch einen einfachen C-Port .

Informationen zur 32-Bit-Unterstützung:

Alle CityHash-Funktionen sind für 64-Bit-Prozessoren optimiert. Das heißt, sie werden (mit Ausnahme der neuen, die SSE4.2 verwenden) im 32-Bit-Code ausgeführt. Sie werden allerdings nicht sehr schnell sein. Möglicherweise möchten Sie Murmur oder etwas anderes in 32-Bit-Code verwenden.

Vipin Parakkat
quelle
11
Wird CityHash ähnlich wie "City Sushi" ausgesprochen?
Eric
2
Schauen Sie sich auch SipHash an, es soll MurmurHash / CityHash / etc ersetzen. : 131002.net/siphash
Török Edwin
3
Siehe auch FarmHash, ein Nachfolger von CitHash. code.google.com/p/farmhash
stevendaniels 18.03.15
7
xxHash ist angeblich 5x schneller als CityHash.
Clay Bridges
plain C portVerbindung ist unterbrochen
makerj
20

Ich habe einen kurzen Geschwindigkeitsvergleich verschiedener Hashing-Algorithmen beim Hashing von Dateien erstellt.

Die einzelnen Plots unterscheiden sich nur geringfügig in der Lesemethode und können hier ignoriert werden, da alle Dateien in einem tmpfs gespeichert wurden. Daher war der Benchmark nicht an E / A gebunden, wenn Sie sich fragen.

Algorithmen sind: SpookyHash, CityHash, Murmur3, MD5, SHA{1,256,512}.

Schlussfolgerungen:

  • Nicht-kryptografische Hash-Funktionen wie Murmur3, Cityhash und Spooky liegen ziemlich nahe beieinander. Man sollte beachten, dass Cityhash auf CPUs mit SSE 4.2s- CRCAnweisung möglicherweise schneller ist , als meine CPU. SpookyHash war in meinem Fall immer ein bisschen vor CityHash.
  • MD5 scheint ein guter Kompromiss zu sein, wenn kryptografische Hash-Funktionen verwendet werden, obwohl SHA256 möglicherweise sicherer für die Kollisionsanfälligkeiten von MD5 und SHA1 ist.
  • Die Komplexität aller Algorithmen ist linear - was nicht verwunderlich ist, da sie blockweise arbeiten. (Ich wollte sehen, ob die Lesemethode einen Unterschied macht, damit Sie nur die Werte ganz rechts vergleichen können).
  • SHA256 war langsamer als SHA512.
  • Ich habe die Zufälligkeit der Hash-Funktionen nicht untersucht. Aber hier ist ein guter Vergleich der Hash-Funktionen, die in Ian Boyds Antwort fehlen . Dies weist darauf hin, dass CityHash in Eckfällen einige Probleme hat.

Die für die Grundstücke verwendete Quelle:

Sahib
quelle
1
Das lineare Skalendiagramm schneidet die Beschriftung auf der y-Achse ab, in der angegeben ist, um welche Menge es sich handelt. Ich denke, es wäre wahrscheinlich "Zeit in Sekunden", genau wie die logarithmische Skala. Es lohnt sich zu reparieren.
Craig McQueen
18

Die SHA - Algorithmen (einschließlich SHA-256) sind entworfen , um schnell .

In der Tat kann ihre Geschwindigkeit manchmal ein Problem sein. Insbesondere besteht eine übliche Technik zum Speichern eines von einem Passwort abgeleiteten Tokens darin, einen schnellen Standard-Hash-Algorithmus 10.000 Mal auszuführen (Speichern des Hashs des Hashs des Hashs des Hashs des ... Passworts).

#!/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

Ausgabe:

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

       user     system      total        real
   1.400000   0.000000   1.400000 (  1.382016)
yfeldblum
quelle
57
Für einen kryptografischen Hashing-Algorithmus ist dies sicher relativ schnell . Aber das OP möchte nur Werte in einer Hash-Tabelle speichern, und ich denke nicht, dass eine kryptografische Hash-Funktion dafür wirklich geeignet ist.
Dean Harding
6
Die gestellte Frage (tangential erscheint sie jetzt) ​​ist das Thema der kryptografischen Hash-Funktionen. Darauf antworte ich.
Yfeldblum
15
Nur um die Leute von der Idee abzuhalten, "Insbesondere besteht eine übliche Technik zum Speichern eines von einem Passwort abgeleiteten Tokens darin, einen standardmäßigen schnellen Hash-Algorithmus 10.000 Mal auszuführen" - obwohl dies häufig der Fall ist, ist dies einfach nur dumm. Es gibt Algorithmen, die für diese Szenarien entwickelt wurden, z bcrypt. Verwenden Sie die richtigen Werkzeuge.
TC1
3
Kryptografische Hashes sind für einen hohen Durchsatz ausgelegt. Dies bedeutet jedoch häufig, dass sie hohe Einrichtungs-, Abbau- .rodataund / oder Statuskosten verursachen. Wenn Sie einen Algorithmus für eine Hash-Tabelle wünschen, haben Sie normalerweise sehr kurze Schlüssel und viele davon, benötigen aber nicht die zusätzlichen Garantien einer kryptografischen Verschlüsselung. Ich benutze einen gezwickten Jenkins nach dem anderen.
Mirabilos
1
@ChrisMorgan: Anstatt einen kryptografisch sicheren Hash zu verwenden, kann HashTable DoS mithilfe von Hash-Randomisierung viel effizienter gelöst werden, sodass jeder Programmlauf oder sogar jede Hash-Tabelle die Daten nicht jedes Mal in denselben Bucket gruppiert .
Lie Ryan
14

Ich weiß, dass es Dinge wie SHA-256 und dergleichen gibt, aber diese Algorithmen sind so konzipiert , dass sie sicher sind , was normalerweise bedeutet, dass sie langsamer sind als Algorithmen, die weniger einzigartig sind .

Die Annahme, dass kryptografische Hash-Funktionen eindeutiger sind, ist falsch, und tatsächlich kann gezeigt werden, dass sie in der Praxis häufig rückwärts sind. In Wahrheit:

  1. Kryptografische Hash-Funktionen sollten idealerweise nicht vom Zufall zu unterscheiden sein .
  2. Bei nicht kryptografischen Hash-Funktionen ist es jedoch wünschenswert, dass sie mit wahrscheinlichen Eingaben positiv interagieren .

Dies bedeutet, dass eine nicht kryptografische Hash-Funktion möglicherweise weniger Kollisionen aufweist als eine kryptografische für "gute" Datensätze - Datensätze, für die sie entwickelt wurde.

Wir können dies anhand der Daten in Ian Boyds Antwort und ein bisschen Mathematik demonstrieren: dem Geburtstagsproblem . Die Formel für die erwartete Anzahl von Paaren zu kollidieren , wenn Sie wählen , nganze Zahlen zufällig aus der Menge [1, d]ist dies (aus Wikipedia):

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

Plugging n= 216.553 und d= 2 ^ 32 ergeben sich ca. 5,5 erwartete Kollisionen . Ians Tests zeigen meist Ergebnisse in der Nachbarschaft, aber mit einer dramatischen Ausnahme: Die meisten Funktionen haben bei den Tests mit fortlaufenden Zahlen keine Kollisionen . Die Wahrscheinlichkeit, zufällig 216.553 32-Bit-Zahlen auszuwählen und keine Kollisionen zu erhalten, liegt bei etwa 0,43%. Und das ist nur für eine Funktion - hier haben wir fünf verschiedene Hash-Funktionsfamilien mit null Kollisionen!

Wir sehen hier also, dass die von Ian getesteten Hashes günstig mit dem Datensatz mit fortlaufenden Zahlen interagieren - dh, sie verteilen minimal unterschiedliche Eingaben weiter als eine ideale kryptografische Hash-Funktion. (Randnotiz: Dies bedeutet, dass Ians grafische Einschätzung, dass FNV-1a und MurmurHash2 für ihn im Zahlen-Datensatz "zufällig" aussehen, aus seinen eigenen Daten widerlegt werden kann. Null Kollisionen mit einem Datensatz dieser Größe für beide Hash-Funktionen, ist auffallend ungewöhnlich!)

Dies ist keine Überraschung, da dies für viele Anwendungen von Hash-Funktionen ein wünschenswertes Verhalten ist. Beispielsweise sind Hash-Tabellenschlüssel häufig sehr ähnlich. Ians Antwort erwähnt ein Problem, das MSN einmal mit Postleitzahl-Hash-Tabellen hatte . Dies ist eine Anwendung, bei der die Kollisionsvermeidung bei wahrscheinlichen Eingaben das zufällige Verhalten gewinnt.

Ein weiterer aufschlussreicher Vergleich ist der Kontrast in den Entwurfszielen zwischen CRC- und kryptografischen Hash-Funktionen:

  • CRC wurde entwickelt, um Fehler abzufangen, die von verrauschten Kommunikationskanälen herrühren , bei denen es sich wahrscheinlich um eine kleine Anzahl von Bitflips handelt.
  • Crypto-Hashes dienen zum Abfangen von Änderungen, die von böswilligen Angreifern vorgenommen wurden , denen begrenzte Rechenressourcen, aber willkürlich viel Klugheit zugeteilt werden.

Für CRC ist es also wieder gut , bei minimal unterschiedlichen Eingaben weniger Kollisionen als zufällig zu haben. Bei Crypto-Hashes ist dies ein Nein-Nein!

Sacundim
quelle
10

Benutze SipHash . Es hat viele wünschenswerte Eigenschaften:

  • Schnell. Eine optimierte Implementierung dauert ungefähr 1 Zyklus pro Byte.

  • Sichern. SipHash ist eine starke PRF (Pseudozufallsfunktion). Dies bedeutet, dass es nicht von einer Zufallsfunktion zu unterscheiden ist (es sei denn, Sie kennen den 128-Bit-Geheimschlüssel). Daher:

    • Sie müssen sich keine Sorgen machen, dass Ihre Hash-Tabellensonden aufgrund von Kollisionen zu einer linearen Zeit werden. Mit SipHash wissen Sie , dass Sie unabhängig von den Eingaben eine durchschnittliche Leistung erzielen.

    • Immunität gegen Hash-basierte Denial-of-Service-Angriffe.

    • Sie können SipHash (insbesondere die Version mit einer 128-Bit-Ausgabe) als MAC (Message Authentication Code) verwenden. Wenn Sie eine Nachricht und ein SipHash-Tag erhalten und das Tag mit dem Tag identisch ist, mit dem Sie SipHash mit Ihrem geheimen Schlüssel ausgeführt haben, wissen Sie, dass sich auch derjenige, der den Hash erstellt hat, im Besitz Ihres geheimen Schlüssels befand und weder die Nachricht noch das Hash wurden seitdem geändert.

Demi
quelle
1
Ist SipHash nicht übertrieben, es sei denn, Sie benötigen Sicherheit? Erfordert einen 128-Bit-Schlüssel, der nur ein verherrlichter Hash-Keim ist. Ganz zu schweigen von der 128-Bit-Ausgabe von MurmurHash3 und der 64-Bit-Ausgabe von SipHash. Offensichtlich hat die größere Auswahl eine geringere Kollisionswahrscheinlichkeit.
Bryc
@bryc Der Unterschied ist, dass sich SipHash auch bei böswilligen Eingaben weiterhin gut verhält. Eine auf SipHash basierende Hash-Tabelle kann für Daten aus potenziell feindlichen Quellen verwendet werden und einen Algorithmus wie das lineare Testen verwenden, der sehr empfindlich auf die Details der Hash-Funktion reagiert.
Demi
9

Es hängt von den Daten ab, die Sie haschen. Einige Hashes funktionieren besser mit bestimmten Daten wie z. B. Text. Einige Hashing-Algorithmen wurden speziell für bestimmte Daten entwickelt.

Paul Hsieh hat einmal schnell gehackt . Er listet Quellcode und Erklärungen auf. Aber es wurde schon geschlagen. :)

user712092
quelle
6

Java verwendet diesen einfachen Multiplikations- und Additionsalgorithmus:

Der Hash-Code für ein String-Objekt wird wie folgt berechnet

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

using int arithmetic, wobei s[i]das i- te Zeichen der Zeichenfolge ndie Länge der Zeichenfolge ist und ^die Potenzierung angibt. (Der Hash-Wert der leeren Zeichenfolge ist Null.)

Es gibt wahrscheinlich viel bessere, aber das ist ziemlich weit verbreitet und scheint ein guter Kompromiss zwischen Geschwindigkeit und Einzigartigkeit zu sein.

biziclop
quelle
12
Ich würde nicht genau das gleiche verwenden, das hier verwendet wird, da es immer noch relativ einfach ist, Kollisionen damit zu erzeugen. Es ist definitiv nicht schrecklich, aber es gibt viel bessere da draußen. Und wenn es keinen wichtigen Grund gibt, mit Java kompatibel zu sein, sollte es nicht gewählt werden.
Joachim Sauer
4
Wenn Sie sich aus irgendeinem Grund immer noch für diese Art des Hashens entscheiden, können Sie zumindest eine bessere Primzahl wie 92821 als Multiplikator verwenden. Das reduziert Kollisionen erheblich. stackoverflow.com/a/2816747/21499
Hans-Peter Störr
1
Sie können stattdessen auch FNV1a verwenden. Es ist auch ein einfacher multiplikationsbasierter Hash, verwendet jedoch einen größeren Multiplikator, der den Hash besser verteilt.
Bryc
4

Warum müssen Sie zuerst Ihr eigenes Hashing implementieren? Für die meisten Aufgaben sollten Sie mit Datenstrukturen aus einer Standardbibliothek gute Ergebnisse erzielen, vorausgesetzt, es ist eine Implementierung verfügbar (es sei denn, Sie tun dies nur für Ihre eigene Ausbildung).

Was die eigentlichen Hashalgorithmen angeht, ist mein persönlicher Favorit FNV. 1

Hier ist eine Beispielimplementierung der 32-Bit-Version 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;
}

quelle
2
Die FNV-1a-Variante ist etwas zufälliger. Tauschen Sie die Reihenfolge der *und ^: h = (h * 16777619) ^ p[i]==>h = (h ^ p[i]) * 16777619
Ian Boyd