хеш-функция в Python 3.3 возвращает разные результаты между сеансами

106

Я реализовал BloomFilter в python 3.3 и каждый сеанс получал разные результаты. Детализация этого странного поведения привела меня к внутренней функции hash () - она ​​возвращает разные хеш-значения для одной и той же строки в каждом сеансе.

Пример:

>>> hash("235")
-310569535015251310

----- открытие новой консоли python -----

>>> hash("235")
-1900164331622581997

Почему это происходит? Чем это полезно?

Redlus
источник

Ответы:

140

Python использует случайное начальное число хеша, чтобы злоумышленники не смогли запятнать ваше приложение, отправив вам ключи, предназначенные для конфликтов. См. Исходное сообщение об уязвимости . Заменяя хеш случайным начальным значением (установленным один раз при запуске), злоумышленники больше не могут предсказать, какие ключи будут конфликтовать.

Вы можете установить фиксированное начальное число или отключить эту функцию, установив PYTHONHASHSEEDпеременную среды ; по умолчанию используется, randomно вы можете установить для него фиксированное положительное целое число, полностью 0отключив эту функцию.

В версиях Python 2.7 и 3.2 эта функция отключена по умолчанию (используйте -Rпереключатель или установите, PYTHONHASHSEED=randomчтобы включить ее); он включен по умолчанию в Python 3.3 и выше.

Если вы полагались на порядок ключей в наборе Python, то не стоит. Python использует хеш-таблицу для реализации этих типов, и их порядок зависит от истории вставки и удаления, а также от случайного начального числа хеша. Обратите внимание, что в Python 3.5 и старше это относится и к словарям.

Также см. object.__hash__()Документацию по специальному методу :

Примечание . По умолчанию __hash__()значения объектов str, bytes и datetime «подсолены» с непредсказуемым случайным значением. Хотя они остаются постоянными в рамках отдельного процесса Python, их нельзя предсказать между повторными вызовами Python.

Это предназначено для обеспечения защиты от отказа в обслуживании, вызванного тщательно подобранными входными данными, которые используют наихудшую производительность вставки dict, сложность O (n ^ 2). Подробнее см. Http://www.ocert.org/advisories/ocert-2011-003.html .

Изменение значений хеш-функции влияет на порядок итерации dicts, множеств и других отображений. Python никогда не давал гарантий относительно этого порядка (и обычно он варьируется между 32-битными и 64-битными сборками).

См. Также PYTHONHASHSEED.

Если вам нужна стабильная реализация хеширования, вы, вероятно, захотите взглянуть на hashlibмодуль ; это реализует криптографические хеш-функции. Проект pybloom использует этот подход .

Поскольку смещение состоит из префикса и суффикса (начальное значение и конечное значение XOR), к сожалению, вы не можете просто сохранить смещение. С другой стороны, это означает, что злоумышленники не могут легко определить смещение с помощью временных атак.

Мартейн Питерс
источник
13
Я ожидал, что это появится в документах hash (), а не только в __hash __ (). +1 за отличный ответ. ps Не является ли hashlib излишеством для некриптографического использования хеш-функций?
redlus
1
pybloom использует функции hashlib. Но если вам нужно что-то быстрее, вы можете проверить pyhash .
Håken Lid
3
Почему в документации это указано disableпри установке на 0? Я не вижу эффективной разницы в установке любого старого стабильного начального числа, если я чего-то не упускаю. Я имею в виду, что когда я использую, PYTHONHASHSEED=12345я получаю один и тот же хеш для одинаковых строк даже в разных сеансах - то же самое происходит, когда я использую PYTHONHASHSEED=0- хеш для одинаковых строк будет одинаковым для всех сеансов (хотя и отличается от 12345, но это очевидно, вот как семена Работа).
blubberdiblub
@blubberdiblub: 0здесь нет семени вообще, а хэши для объектов идентичны тем, которые были сгенерированы в более старой версии Python без какой-либо поддержки хэша.
Мартейн Питерс
1
@MartijnPieters, что означает, что у затронутых хэшей «вообще нет семени»? Какая семантическая или качественная разница между семанами, скажем, 12345, помимо того факта, что она создает два разных набора сеансов, между которыми значения хеш-функции различаются, и кроме PYTHONHASHSEED = 0, равного более старым версиям? Можете ли вы связать меня с конкретным фрагментом исходного кода? Я предполагаю, что моя точка зрения состоит в том, что если такой разницы нет, я бы назвал это начальным значением 0, а более старые версии Python поддерживают только начальное значение 0. Документация в ее нынешнем виде меня довольно смущает.
blubberdiblub
10

Рандомизация хэша включена по умолчанию в Python 3 . Это функция безопасности:

Рандомизация хэша предназначена для обеспечения защиты от отказа в обслуживании, вызванного тщательно подобранными входными данными, которые используют наихудшую производительность конструкции dict.

В предыдущих версиях, начиная с 2.6.8, вы могли включить его в командной строке с помощью -R или параметра среды PYTHONHASHSEED .

Вы можете выключить его, установив PYTHONHASHSEEDна ноль.

Питер Вуд
источник
-11

hash () - это встроенная функция Python, которая используется для вычисления хеш-значения для объекта , а не для строки или числа.

Подробности можно увидеть на этой странице: https://docs.python.org/3.3/library/functions.html#hash .

а значения hash () поступают из метода __hash__ объекта. В документе говорится следующее:

По умолчанию значения hash () для объектов str, bytes и datetime «подсолены» с непредсказуемым случайным значением. Хотя они остаются постоянными в рамках отдельного процесса Python, их нельзя предсказать между повторными вызовами Python.

Вот почему у вас есть различное хеш-значение для одной и той же строки в другой консоли.

То, что вы реализуете, - не лучший способ.

Если вы хотите вычислить хеш-значение строки, просто используйте hashlib

hash () нацелен на получение хеш-значения объекта, а не на волнение.

Адам Вен
источник
6
hash()идеально подходит для строковых или числовых значений. Вы путаете это с __hash__пользовательский метод, используемый приhash() обеспечить собственную реализацию хеш - значения.
Мартейн Питерс