Funkcja skrótu w Pythonie 3.3 zwraca różne wyniki między sesjami

106

Zaimplementowałem BloomFilter w Pythonie 3.3 i otrzymałem różne wyniki podczas każdej sesji. Analiza tego dziwnego zachowania doprowadziła mnie do wewnętrznej funkcji hash () - zwraca ona różne wartości skrótu dla tego samego ciągu w każdej sesji.

Przykład:

>>> hash("235")
-310569535015251310

----- otwieranie nowej konsoli Pythona -----

>>> hash("235")
-1900164331622581997

Dlaczego to się dzieje? Dlaczego jest to przydatne?

redlus
źródło

Odpowiedzi:

140

Python wykorzystuje losowe ziarno hash, aby uniemożliwić atakującym wykonanie tarowania aplikacji przez wysyłanie kluczy zaprojektowanych do kolizji. Zobacz oryginalne ujawnienie luk w zabezpieczeniach . Zrównując hasz losowym ziarnem (ustawionym raz przy starcie), atakujący nie mogą już przewidzieć, które klucze będą kolidować.

Możesz ustawić stałe ziarno lub wyłączyć tę funkcję, ustawiając PYTHONHASHSEEDzmienną środowiskową ; wartość domyślna to, randomale możesz ustawić ją na stałą dodatnią liczbę całkowitą, z całkowitym 0wyłączeniem tej funkcji.

Python w wersjach 2.7 i 3.2 ma tę funkcję domyślnie wyłączoną (użyj -Rprzełącznika lub ustaw ją, PYTHONHASHSEED=randomaby ją włączyć); jest on domyślnie włączony w Pythonie 3.3 i nowszych.

Jeśli polegałeś na kolejności kluczy w zestawie Pythona, nie rób tego. Python używa tablicy skrótów do implementacji tych typów, a ich kolejność zależy od historii wstawiania i usuwania, a także od losowego materiału siewnego. Zwróć uwagę, że w Pythonie 3.5 i starszych dotyczy to również słowników.

Zobacz także object.__hash__()dokumentację metod specjalnych :

Uwaga : Domyślnie __hash__()wartości obiektów str, bytes i datetime są „salted” z nieprzewidywalną losową wartością. Chociaż pozostają stałe w ramach pojedynczego procesu Pythona, nie można ich przewidzieć między powtarzającymi się wywołaniami Pythona.

Ma to na celu zapewnienie ochrony przed odmową usługi spowodowaną przez starannie dobrane dane wejściowe, które wykorzystują najgorszą wydajność wstawiania dyktowania, złożoność O (n ^ 2). Szczegółowe informacje można znaleźć pod adresem http://www.ocert.org/advisories/ocert-2011-003.html .

Zmiana wartości skrótu wpływa na kolejność iteracji dykt, zestawów i innych mapowań. Python nigdy nie udzielił gwarancji co do takiej kolejności (i zwykle różni się ona między wersjami 32-bitowymi i 64-bitowymi).

Zobacz także PYTHONHASHSEED.

Jeśli potrzebujesz stabilnej implementacji skrótu, prawdopodobnie zechcesz przyjrzeć się hashlibmodułowi ; to implementuje kryptograficzne funkcje skrótu. Projekt pybloom wykorzystuje to podejście .

Ponieważ offset składa się z przedrostka i sufiksu (odpowiednio wartość początkowa i końcowa wartość XOR), niestety nie można go po prostu zapisać. Z drugiej strony oznacza to, że atakujący nie mogą łatwo określić przesunięcia za pomocą ataków czasowych.

Martijn Pieters
źródło
13
Spodziewałbym się, że pojawi się to w dokumentach hash (), a nie tylko w __hash __ (). +1 za świetną odpowiedź. ps Czy hashlib nie jest przesadą w przypadku niekryptograficznych zastosowań funkcji skrótu?
redlus
1
pybloom używa funkcji hashlib. Ale jeśli chcesz czegoś szybszego, możesz sprawdzić pyhash .
Håken Lid
3
Dlaczego dokumentacja nazywa to disableprzy ustawianiu na 0? Nie widzę skutecznej różnicy w ustawieniu go na jakikolwiek stary stabilny numer zarodka, chyba że czegoś mi brakuje. Chodzi mi o to, że kiedy używam PYTHONHASHSEED=12345, otrzymuję ten sam hash dla równych ciągów nawet w sesjach - to samo dzieje się, gdy używam PYTHONHASHSEED=0- hash dla równych ciągów będzie taki sam we wszystkich sesjach (choć różni się od 12345, ale to oczywiste, w ten sposób nasiona praca).
blubberdiblub
@blubberdiblub: w 0ogóle nie ma materiału siewnego, a skróty dla obiektów są równe tym, które zostały wygenerowane w starszej wersji Pythona bez obsługi haszowania.
Martijn Pieters
1
@MartijnPieters co to znaczy, że w dotkniętych hashach „w ogóle nie ma nasion”? Jaka jest semantyczna lub jakościowa różnica w posiadaniu nasionka, powiedzmy, 12345, poza faktem, że tworzy dwa odrębne zestawy sesji, pomiędzy którymi wartości skrótu są różne i poza tym, że PYTHONHASHSEED = 0 jest równe starszym wersjom? Czy możesz połączyć mnie z konkretnym fragmentem kodu źródłowego? Chyba chodzi mi o to, że jeśli nie ma takiej różnicy, nazwałbym to ziarnem 0, a starsze wersje Pythona obsługują tylko ziarno 0. Dokumentacja w obecnym stanie jest dla mnie dość zagmatwana.
blubberdiblub
10

Randomizacja hash jest domyślnie włączona w Pythonie 3 . To jest funkcja bezpieczeństwa:

Randomizacja hash ma na celu zapewnienie ochrony przed atakiem typu „odmowa usługi” spowodowanym przez starannie dobrane dane wejściowe, które wykorzystują wydajność konstrukcji dykta w najgorszym przypadku

W poprzednich wersjach od 2.6.8 można było włączyć go w wierszu poleceń za pomocą -R lub opcji środowiska PYTHONHASHSEED .

Możesz go wyłączyć, ustawiając PYTHONHASHSEEDna zero.

Peter Wood
źródło
-11

hash () jest funkcją wbudowaną w Pythonie i używa jej do obliczenia wartości skrótu dla obiektu , a nie dla ciągu znaków lub liczby.

Szczegóły możesz zobaczyć na tej stronie: https://docs.python.org/3.3/library/functions.html#hash .

a wartości hash () pochodzą z metody __hash__ obiektu. Doktor mówi, co następuje:

Domyślnie wartości hash () obiektów str, bytes i datetime są „salted” z nieprzewidywalną wartością losową. Chociaż pozostają stałe w ramach pojedynczego procesu Pythona, nie można ich przewidzieć między powtarzającymi się wywołaniami Pythona.

Dlatego masz inną wartość skrótu dla tego samego ciągu w innej konsoli.

To, co wdrażasz, nie jest dobrym sposobem.

Jeśli chcesz obliczyć wartość skrótu ciągu, po prostu użyj hashlib

hash () ma na celu uzyskanie wartości skrótu obiektu, a nie mieszania.

Adam Wen
źródło
6
hash()doskonale sprawdza się w przypadku wartości łańcuchowych lub liczbowych. Mylisz to z __hash__metodą niestandardową, używaną przez program whash() celu zapewnienia niestandardowej implementacji wartości skrótu.
Martijn Pieters