Die Hash-Funktion in Python 3.3 gibt zwischen den Sitzungen unterschiedliche Ergebnisse zurück

96

Ich habe einen BloomFilter in Python 3.3 implementiert und in jeder Sitzung unterschiedliche Ergebnisse erzielt. Durch Drilldown dieses seltsamen Verhaltens gelangte ich zur internen Funktion hash () - sie gibt in jeder Sitzung unterschiedliche Hashwerte für dieselbe Zeichenfolge zurück.

Beispiel:

>>> hash("235")
-310569535015251310

----- Öffnen einer neuen Python-Konsole -----

>>> hash("235")
-1900164331622581997

Warum passiert dies? Warum ist das nützlich?

Redlus
quelle

Antworten:

135

Python verwendet einen zufälligen Hash-Startwert, um zu verhindern, dass Angreifer Ihre Anwendung tarieren, indem Sie Schlüssel senden, die für eine Kollision ausgelegt sind. Siehe die ursprüngliche Offenlegung der Sicherheitsanfälligkeit . Durch das Versetzen des Hashs durch einen zufälligen Startwert (einmal beim Start festgelegt) können Angreifer nicht mehr vorhersagen, welche Schlüssel kollidieren werden.

Sie können einen festen Startwert festlegen oder die Funktion deaktivieren, indem Sie die PYTHONHASHSEEDUmgebungsvariable festlegen . Die Standardeinstellung ist, randomaber Sie können einen festen positiven ganzzahligen Wert festlegen, 0indem Sie die Funktion vollständig deaktivieren.

In den Python-Versionen 2.7 und 3.2 ist die Funktion standardmäßig deaktiviert (verwenden Sie den -RSchalter oder die Einstellung PYTHONHASHSEED=random, um sie zu aktivieren). Es ist standardmäßig in Python 3.3 und höher aktiviert.

Wenn Sie sich auf die Reihenfolge der Schlüssel in einem Python-Set verlassen haben, tun Sie dies nicht. Python verwendet eine Hash-Tabelle, um diese Typen zu implementieren. Ihre Reihenfolge hängt vom Einfüge- und Löschverlauf ab sowie vom zufälligen Hash-Startwert ab. Beachten Sie, dass dies in Python 3.5 und älter auch für Wörterbücher gilt.

Siehe auch die object.__hash__()spezielle Methodendokumentation :

Hinweis : Standardmäßig werden die __hash__()Werte von str-, bytes- und datetime-Objekten mit einem unvorhersehbaren Zufallswert "gesalzen". Obwohl sie innerhalb eines einzelnen Python-Prozesses konstant bleiben, sind sie zwischen wiederholten Aufrufen von Python nicht vorhersehbar.

Dies soll Schutz vor einem Denial-of-Service bieten, der durch sorgfältig ausgewählte Eingaben verursacht wird, die die Worst-Case-Leistung einer Dikt-Einfügung, O (n ^ 2) -Komplexität, ausnutzen. Siehe http://www.ocert.org/advisories/ocert-2011-003.html Informationen finden .

Das Ändern von Hash-Werten wirkt sich auf die Iterationsreihenfolge von Diktaten, Mengen und anderen Zuordnungen aus. Python hat niemals Garantien für diese Reihenfolge gegeben (und sie variiert normalerweise zwischen 32-Bit- und 64-Bit-Builds).

Siehe auch PYTHONHASHSEED.

Wenn Sie eine stabile Hash-Implementierung benötigen, sollten Sie sich wahrscheinlich das hashlibModul ansehen . Dies implementiert kryptografische Hash-Funktionen. Das Pybloom-Projekt verwendet diesen Ansatz .

Da der Offset aus einem Präfix und einem Suffix (Startwert bzw. endgültiger XOR-Wert) besteht, können Sie den Offset leider nicht einfach speichern. Auf der positiven Seite bedeutet dies, dass Angreifer den Versatz mit Timing-Angriffen auch nicht einfach bestimmen können.

Martijn Pieters
quelle
8
Ich würde erwarten, dass dies in den hash () - Dokumenten und nicht nur in __hash __ () angezeigt wird. +1 für eine gute Antwort. ps Ist Hashlib nicht ein Overkill für die nicht kryptografische Verwendung von Hash-Funktionen?
Redlus
1
pybloom verwendet die hashlib-Funktionen. Aber wenn Sie etwas schneller wollen, können Sie Pyhash ausprobieren .
Håken Deckel
3
Warum wird es in der Dokumentation aufgerufen, disablewenn es auf 0 gesetzt wird? Ich sehe keinen effektiven Unterschied darin, eine alte stabile Startnummer festzulegen, es sei denn, mir fehlt etwas. Was ich meine ist, wenn ich benutze, PYTHONHASHSEED=12345bekomme ich den gleichen Hash für gleiche Zeichenfolgen auch über Sitzungen hinweg - das gleiche passiert, wenn ich benutze PYTHONHASHSEED=0- der Hash für gleiche Zeichenfolgen ist über Sitzungen hinweg gleich (wenn auch anders als 12345, aber das ist offensichtlich, so sind Seeds Arbeit).
Blubberdiblub
@blubberdiblub: 0da es überhaupt keinen Startwert gibt und die Hashes für Objekte denen entsprechen, die in einer älteren Python-Version ohne Hashseed-Unterstützung generiert wurden.
Martijn Pieters
1
@MartijnPieters Was bedeutet es für die betroffenen Hashes, "überhaupt keinen Samen" zu haben? Was ist der semantische oder qualitative Unterschied zu einem Startwert von beispielsweise 12345, abgesehen von der Tatsache, dass zwei unterschiedliche Sitzungssätze erstellt werden, zwischen denen die Hashwerte unterschiedlich sind, und abgesehen davon, dass PYTHONHASHSEED = 0 älteren Versionen entspricht? Können Sie mich mit einem bestimmten Quellcode verknüpfen? Ich denke, mein Punkt ist, wenn es keinen solchen Unterschied gibt, würde ich es einen Startwert von 0 nennen und ältere Versionen von Python unterstützen nur einen Startwert von 0. Die Dokumentation in der jetzigen Form ist für mich ziemlich verwirrend.
Blubberdiblub
10

Die Hash-Randomisierung ist in Python 3 standardmäßig aktiviert . Dies ist ein Sicherheitsmerkmal:

Die Hash-Randomisierung soll Schutz vor einem Denial-of-Service bieten, der durch sorgfältig ausgewählte Eingaben verursacht wird, die die Worst-Case-Leistung einer Diktatkonstruktion ausnutzen

In früheren Versionen von 2.6.8 konnten Sie es in der Befehlszeile mit -R oder der Umgebungsoption PYTHONHASHSEED aktivieren .

Sie können es ausschalten, indem Sie es PYTHONHASHSEEDauf Null setzen.

Peter Wood
quelle
-9

hash () ist eine in Python integrierte Funktion , mit der ein Hashwert für ein Objekt berechnet wird , nicht für einen String oder eine Nummer.

Sie können die Details auf dieser Seite sehen: https://docs.python.org/3.3/library/functions.html#hash .

und hash () -Werte stammen aus der __hash__- Methode des Objekts. Der Arzt sagt Folgendes:

Standardmäßig werden die hash () -Werte von str-, bytes- und datetime-Objekten mit einem unvorhersehbaren Zufallswert "gesalzen". Obwohl sie innerhalb eines einzelnen Python-Prozesses konstant bleiben, sind sie zwischen wiederholten Aufrufen von Python nicht vorhersehbar.

Aus diesem Grund haben Sie einen unterschiedlichen Hashwert für dieselbe Zeichenfolge in einer anderen Konsole.

Was Sie implementieren, ist kein guter Weg.

Wenn Sie einen String-Hash-Wert berechnen möchten, verwenden Sie einfach hashlib

hash () zielt darauf ab, einen Objekt-Hash-Wert zu erhalten, keinen stirng.

Adam Wen
quelle
6
hash()ist perfekt gültig für Zeichenfolgen oder numerische Werte. Sie sind verwirrend dies mit der __hash__benutzerdefinierten Methode verwendet durchhash() eine benutzerdefinierte Implementierung des Hash - Wertes zu liefern.
Martijn Pieters