it-swarm.com.de

Warum ist XOR die Standardmethode zum Kombinieren von Hashes?

Angenommen, Sie haben zwei Hashes H(A) und H(B) und möchten diese kombinieren. Ich habe gelesen, dass eine gute Möglichkeit, zwei Hashes zu kombinieren, darin besteht, sie zu XOR, z. XOR( H(A), H(B) ).

Die beste Erklärung, die ich gefunden habe, wird hier kurz auf diese Richtlinien für Hash-Funktionen angesprochen:

Durch XOR-Verknüpfung zweier Zahlen mit einer ungefähren Zufallsverteilung erhält man eine weitere Zahl mit einer ungefähren Zufallsverteilung *, die jedoch jetzt von den beiden Werten abhängt.
...
* Bei jedem Bit der beiden zu kombinierenden Zahlen wird eine 0 ausgegeben, wenn die beiden Bits gleich sind, andernfalls eine 1. Mit anderen Worten, in 50% der Kombinationen wird eine 1 ausgegeben. Wenn also die beiden Eingangsbits jeweils eine Chance von ungefähr 50-50 haben, 0 oder 1 zu sein, wird dies auch für das Ausgangsbit gelten.

Können Sie erklären, warum XOR die Standardoperation zum Kombinieren von Hash-Funktionen sein sollte (anstatt OR oder AND usw.))?

132
Nate Murray

Unter der Annahme von gleichmäßig zufälligen (1-Bit) Eingaben beträgt die Wahrscheinlichkeitsverteilung für die Ausgabe der UND-Funktion 75% 0 und 25% 1. Umgekehrt ist OR 25% 0 und 75% 1.

Die Funktion XOR ist 50% 0 und 50% 1, daher ist es gut, gleichmäßige Wahrscheinlichkeitsverteilungen zu kombinieren.

Dies kann durch Ausschreiben von Wahrheitstabellen gesehen werden:

 a | b | a AND b
---+---+--------
 0 | 0 |    0
 0 | 1 |    0
 1 | 0 |    0
 1 | 1 |    1

 a | b | a OR b
---+---+--------
 0 | 0 |    0
 0 | 1 |    1
 1 | 0 |    1
 1 | 1 |    1

 a | b | a XOR b
---+---+--------
 0 | 0 |    0
 0 | 1 |    1
 1 | 0 |    1
 1 | 1 |    0

Aufgabe: Wie viele logische Funktionen zweier 1-Bit-Eingänge a und b haben diese gleichmäßige Ausgangsverteilung? Warum ist XOR am besten für den in Ihrer Frage angegebenen Zweck geeignet?

110
Greg Hewgill

xor ist eine gefährliche Standardfunktion, die beim Hashing verwendet wird. Es ist besser als und und oder, aber das sagt nicht viel.

xor ist symmetrisch, daher geht die Reihenfolge der Elemente verloren. Also wird "bad" Dasselbe wie "dab" Kombinieren.

xor ordnet identische Werte null zu, und Sie sollten vermeiden, "allgemeine" Werte null zuzuordnen:

Also wird (a,a) Auf 0 abgebildet, und (b,b) Wird auch auf 0 abgebildet. Da solche Paare häufiger vorkommen, als es der Zufall vermuten lässt, kommt es bei Null zu weitaus mehr Kollisionen, als Sie sollten.

Mit diesen beiden Problemen wird xor schließlich zu einem Hash-Combiner, der auf der Oberfläche halbwegs anständig aussieht, jedoch nicht nach weiterer Prüfung.

Bei moderner Hardware ist das Hinzufügen in der Regel ungefähr so ​​schnell wie bei xor (es verbraucht allerdings wahrscheinlich mehr Strom, um dies zu erreichen). Die Wahrheitstabelle von Adding ähnelt xor für das betreffende Bit, sendet jedoch auch ein Bit zum nächsten Bit, wenn beide Werte 1 sind. Dadurch werden weniger Informationen gelöscht.

Also ist hash(a) + hash(b) besser, wenn a==b, Ist das Ergebnis statt hash(a)<<1 statt 0.

Dies bleibt symmetrisch. Wir können diese Symmetrie zu bescheidenen Kosten durchbrechen:

hash(a)<<1 + hash(a) + hash(b)

aka hash(a)*3 + hash(b). (Wenn Sie die Schichtlösung verwenden, wird empfohlen, hash(a) einmal zu berechnen und zu speichern). Jede ungerade Konstante anstelle von 3 Ordnet sich bijektiv eine size_t (Oder eine vorzeichenlose k-Bit-Konstante) zu, da die Zuordnung zu vorzeichenlosen Konstanten math modulo 2^k Für einige k und jede ungerade Konstante ist relativ hoch zu 2^k.

Für eine noch schickere Version können wir boost::hash_combine Untersuchen, was effektiv ist:

size_t hash_combine( size_t lhs, size_t rhs ) {
  lhs^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
  return lhs;
}

hier addieren wir einige verschobene Versionen von seed mit einer Konstanten (die im Grunde zufällig 0 s und 1 s ist - insbesondere ist es die Umkehrung des goldenen Schnitts als ein 32-Bit-Fixpunktbruch) mit etwas Addition und einem xor. Dies unterbricht die Symmetrie und führt zu etwas "Rauschen", wenn die eingehenden Hash-Werte schlecht sind (dh stellen Sie sich vor, dass alle Komponenten auf 0 gehasht werden - das oben Genannte behandelt dies gut und erzeugt einen Abstrich von 1 Und 0. ] s nach jedem Mähdrescher. Meins gibt einfach einen 0) aus.

Für diejenigen, die mit C/C++ nicht vertraut sind, ist ein size_t Ein vorzeichenloser ganzzahliger Wert, der groß genug ist, um die Größe eines Objekts im Speicher zu beschreiben. Auf einem 64-Bit-System ist dies normalerweise eine 64-Bit-Ganzzahl ohne Vorzeichen. Auf einem 32-Bit-System eine 32-Bit-Ganzzahl ohne Vorzeichen.

148

Trotz seiner praktischen Bit-Mixing-Eigenschaften ist XOR not aufgrund seiner Kommutativität eine gute Möglichkeit, Hashes zu kombinieren. Überlegen Sie, was passieren würde, wenn Sie die Permutationen von {1, 2,…, 10} in einer Hash-Tabelle mit 10 Tupeln speichern würden.

Eine viel bessere Wahl ist m * H(A) + H(B), wobei m eine große ungerade Zahl ist.

Kredit: Der oben genannte Kombinierer war ein Tipp von Bob Jenkins.

29
Marcelo Cantos

Xor ist möglicherweise die "Standardmethode" zum Kombinieren von Hashes, aber die Antwort von Greg Hewgill zeigt auch, warum es seine Fallstricke gibt: Das xor zweier identischer Hash-Werte ist Null. Im wirklichen Leben gibt es identische Hashes, die häufiger vorkommen, als man hätte erwarten können. Dann stellen Sie möglicherweise fest, dass in diesen (nicht so seltenen) Eckfällen die resultierenden kombinierten Hashes immer gleich (Null) sind. Hash-Kollisionen würden sehr viel häufiger auftreten als erwartet.

In einem erfundenen Beispiel könnten Sie Hash-Passwörter von Benutzern aus verschiedenen von Ihnen verwalteten Websites kombinieren. Leider verwenden eine große Anzahl von Benutzern ihre Passwörter erneut und ein überraschender Anteil der resultierenden Hashes ist Null!

16
Leo Goodstadt

Es gibt etwas, worauf ich andere ausdrücklich hinweisen möchte, die diese Seite finden. AND und OR Ausgabe wie BlueRaja einschränken - Danny Pflughoe versucht darauf hinzuweisen, kann aber besser definiert werden:

Zuerst möchte ich zwei einfache Funktionen definieren, mit denen ich dies erläutere: Min () und Max ().

Min (A, B) gibt den Wert zurück, der zwischen A und B kleiner ist. Beispiel: Min (1, 5) gibt 1 zurück.

Max (A, B) gibt den Wert zurück, der zwischen A und B größer ist. Beispiel: Max (1, 5) gibt 5 zurück.

Wenn Sie angegeben werden: C = A AND B

Dann können Sie C <= Min(A, B) finden. Wir wissen das, weil es nichts gibt, was Sie UND mit den 0-Bits von A oder B machen können, um sie zu 1 zu machen. So bleibt jedes Nullbit ein Nullbit und jedes einzelne Bit hat die Chance, ein Nullbit (und damit einen kleineren Wert) zu werden.

Mit: C = A OR B

Das Gegenteil ist der Fall: C >= Max(A, B) Damit sehen wir die Konsequenz zur AND-Funktion. Jedes Bit, das bereits eine Eins ist, kann nicht zu einer Null ODER-verknüpft werden, daher bleibt es eine Eins, aber jedes Null-Bit hat die Chance, eine Eins und damit eine größere Zahl zu werden.

Dies impliziert, dass der Status der Eingabe die Ausgabe einschränkt. Wenn Sie UND irgendetwas mit 90 angeben, wissen Sie, dass die Ausgabe gleich oder kleiner als 90 ist, unabhängig davon, wie hoch der andere Wert ist.

Für XOR gibt es keine implizite Einschränkung basierend auf den Eingaben. Es gibt spezielle Fälle, in denen Sie feststellen, dass, wenn Sie XOR= ein Byte mit 255 haben, dann die Umkehrung erhalten, aber jedes mögliche Byte daraus ausgegeben werden kann das gleiche Bit im anderen Operanden.

8
Corey Ogburn

Wenn Sie eine zufällige Eingabe mit einer verzerrten Eingabe XOR, ist die Ausgabe zufällig. Gleiches gilt nicht für AND oder OR. Beispiel:

 00101001 XOR= 00000000 = 00101001 
 00101001 UND 00000000 = 00000000 
 00101001 OR= 11111111 = 11111111 

Wie @Greg Hewgill erwähnt, führt die Verwendung von AND oder OR zu einer verzerrten Ausgabe, auch wenn beide Eingaben zufällig sind.

Der Grund, warum wir XOR für etwas Komplexeres verwenden, ist, dass es keine Notwendigkeit gibt: XOR funktioniert perfekt und es ist unglaublich blöd - schnell.

Decken Sie die linken 2 Spalten ab und versuchen Sie herauszufinden, welche Eingaben nur die Ausgabe verwenden.

 a | b | a AND b
---+---+--------
 0 | 0 |    0
 0 | 1 |    0
 1 | 0 |    0
 1 | 1 |    1

Wenn Sie ein 1-Bit gesehen haben, sollten Sie herausgefunden haben, dass beide Eingänge 1 sind.

Machen Sie jetzt dasselbe für XOR

 a | b | a XOR b
---+---+--------
 0 | 0 |    0
 0 | 1 |    1
 1 | 0 |    1
 1 | 1 |    0

XOR verrät nichts über seine Eingaben.

1
Robert

XOR ignoriert einige der Eingaben manchmal nicht wie ODER und UND .

Wenn Sie zum Beispiel AND (X, Y) und feed input X mit false nehmen, dann ist die Eingabe Y spielt keine Rolle ... und man möchte wahrscheinlich, dass die Eingabe beim Kombinieren von Hashes eine Rolle spielt.

Wenn Sie XOR (X, Y) nehmen, dann [~ # ~] beide [~ # ~] Eingänge [~ # ~] immer [~ # ~] wichtig. Es würde keinen Wert für X geben, bei dem Y keine Rolle spielt. Wenn entweder X oder Y geändert wird, wird dies in der Ausgabe berücksichtigt.

0
Sunsetquest

Der Quellcode für verschiedene Versionen von hashCode() in Java.util.Arrays ist eine hervorragende Referenz für solide, allgemein verwendete Hashing-Algorithmen. Sie sind leicht zu verstehen und in andere Programmiersprachen zu übersetzen.

Grob gesagt folgen die meisten Implementierungen von hashCode() mit mehreren Attributen diesem Muster:

public static int hashCode(Object a[]) {
    if (a == null)
        return 0;

    int result = 1;

    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());

    return result;
}

Sie können andere Fragen und Antworten zu StackOverflow durchsuchen, um weitere Informationen über die Magie hinter 31 Zu erhalten, und warum Java Code verwendet sie so häufig. Er ist unvollkommen, weist jedoch sehr gute allgemeine Leistungseigenschaften auf.

0
kevinarpe