Хэш-значения Python2 плохо распределены
При использовании встроенного Python hash()
Функция на струнах, я просто играл с ней, когда заметил что-то странное. Обычно нормальная хеш-функция должна быть некоррелированной в том смысле, что из hash(A)
, hash(B)
должно быть полностью неузнаваемым (для достаточных определений некоррелированных / неузнаваемых).
Тем не менее, этот быстрый маленький сценарий показывает иначе
In [1]: for i in range(15):
...: print hash('test{0}'.format(i))
...:
-5092793511388848639
-5092793511388848640
-5092793511388848637
-5092793511388848638
-5092793511388848635
-5092793511388848636
-5092793511388848633
-5092793511388848634
-5092793511388848631
-5092793511388848632
5207588497627702649
5207588497627702648
5207588497627702651
5207588497627702650
5207588497627702653
Я понимаю питона hash()
Функция не должна быть криптографически безопасной на любом участке, и для этого вы должны использовать hashlib
библиотека, но почему значения testX
так регулярно распространяется? Это кажется мне плохим поведением при столкновении.
3 ответа
Хеш рассчитывается один символ за другим. Вот почему хеши так похожи.
Во время вычислений "test0"
а также "test1"
иметь точно такой же хэш до "test"
, В последнем символе есть только одно отличие. В безопасных хэшах изменение одного бита в любом месте должно полностью изменить весь хэш (например, благодаря нескольким проходам).
Вы можете проверить это поведение, вычислив хэш "0test" и "1test":
>>> for i in range(15):
... print hash('{0}test'.format(i))
...
-2218321119694330423
-198347807511608008
-8430555520134600289
1589425791872121742
-6642709920510870371
-4622800608552147860
8038463826323963107
2058173137418684322
-8620450647505857711
-6600477335291135136
8795071937164440413
4111679291630235372
-765820399655801141
2550858955145994266
6363120682850473265
Это тот тип широкого распространения, который вы ожидали, верно? Кстати, у Python 3, похоже, другое вычисление хеша для строк.
Для получения дополнительной информации о хэше Python2, взгляните на "Python Hash Algorithms":
class string:
def __hash__(self):
if not self:
return 0 # empty
value = ord(self[0]) << 7
for char in self:
value = c_mul(1000003, value) ^ ord(char)
value = value ^ len(self)
if value == -1:
value = -2
return value
Кстати, эта проблема не связана с Python. В Java "Aa"
а также "BB"
поделитесь тем же хешем.
Питон hash
функция не является криптографическим хешем (т.е. не должна защищать от столкновений или показывать лавинный эффект и т. д.); это просто идентификатор (например, для использования в качестве словарных ключей) для объектов.
узнать больше о __hash__
и хеш в документации.
как указано там:
dict. __hash__()
должен вернуть целое число. Единственным обязательным свойством является то, что объекты, которые сравниваются равными, имеют одинаковое значение хеш
и - как отметил в комментарии Jean-François Fabre - хэши питона должны быть быстрыми (т.е. создавать словари). криптографические хеши медленные и поэтому непригодны для этого.
Кстати, в Python 3 распределение выглядит более случайным.
Объяснение можно найти в комментариях к исходному коду Python2.7 Objects/dictobject.c:
Основные тонкости впереди: большинство хеш-схем зависят от наличия "хорошей" хеш-функции в смысле симуляции случайности. Python не делает: его наиболее важные хеш-функции (для строк и целых чисел) очень обычны в обычных случаях:
>>> map(hash, (0, 1, 2, 3)) [0, 1, 2, 3] >>> map(hash, ("namea", "nameb", "namec", "named")) [-1658398457, -1658398460, -1658398459, -1658398462]
Это не обязательно плохо! Наоборот, в таблице размером 2**i, бита младшего разряда i в качестве начального индекса таблицы чрезвычайно быстра, и нет никаких коллизий для кодов, индексируемых непрерывным диапазоном целых чисел. То же самое примерно верно, когда ключи являются "последовательными" строками. Так что это дает поведение лучше случайного в обычных случаях, и это очень желательно.