Почему взятие соленого хэша мода хэша приводит к очень неравномерному распределению?

У меня есть миллион случайно сгенерированных уникальных идентификаторов.

If I do:

result = int(hash(id + 'some_salt')) % 1000

Тогда это, по-видимому, приводит к равномерному распределению идентификаторов до некоторого целого числа от 0 до 999, при этом каждому целому числу соответствует примерно 1000 идентификаторов.

Если я теперь добавлю немного соли к этому и снова возьму хэш:

x = int(hash(id)) % 1000
result = int(hash(str(x) + 'some_salt') % 1000)

Тогда полученное распределение будет совершенно неравномерным. Для каждого идентификатора результат, конечно, находится в диапазоне [0,999], но некоторые целые числа в этом диапазоне имеют нулевые идентификаторы, в то время как другие имеют несколько тысяч.

Почему это приводит к очень неравномерному распределению значений?

Как я могу настроить это, чтобы привести к равномерному распределению целых чисел в диапазоне [0,999] для моего миллиона идентификаторов и любой заданной соли? Я хочу сохранить промежуточный шаг по уменьшению потенциально очень большого входного пространства до гораздо меньшего пространства (например, размером 1000).

Я использую хеширование SHA-256.

Вот некоторый код Python, который демонстрирует очень неравномерные результаты:

import numpy as np
import hashlib

OUTPUT_RANGE_SIZE = 1000

unique_ids = xrange(1000000) # sequential here, but could be any kind of unique ids
frequencies = np.zeros(OUTPUT_RANGE_SIZE, dtype='int')

for idx in xrange(len(unique_ids)):
    id = unique_ids[idx]
    hash_mod = int(hashlib.sha256(str(id)).hexdigest(), 16) % 1000
    result = int(hashlib.sha256(str(hash_mod) + 'some_salt').hexdigest(), 16) % OUTPUT_RANGE_SIZE
    frequencies[result] = frequencies[result] + 1

print frequencies

person Josh    schedule 30.03.2015    source источник
comment
Вы уверены, что используете SHA-256? «хеш» во многих языках (например, Python) — это внутренняя функция, которая не является безопасной и фактически даже не стабильной в разных экземплярах и, вероятно, будет вести себя так, как вы описываете. SHA-256 создает массив байтов или закодированную строку, и я не могу придумать ни одного языка, который позволил бы вам привести его к типу int, как указано выше.   -  person Nick Johnson    schedule 30.03.2015
comment
Что ж, я использую библиотеку hashlib для Python и преобразовываю шестнадцатеричное представление в int... например, так: int(hashlib.sha256(id + 'some_string').hexdigest(), 16) % 1000... Если вы хотите увидеть мой код для этого, я вставил его сюда: pastebin.com/sMP4G2vQ — раскомментируйте строка печати покажет очень неравномерные результаты   -  person Josh    schedule 30.03.2015
comment
Вы должны отредактировать свой вопрос, чтобы использовать этот фактический код - pastebins, как правило, не задерживаются. В любом случае, я запускал ваш код как со случайно выбранными, так и с последовательными идентификаторами, и в любом случае результаты хорошо распределяются, как и следовало ожидать.   -  person Nick Johnson    schedule 30.03.2015
comment
Извиняюсь! Я вставил вам неправильный код. Я только что обновил вопрос с кодом, который я хотел вставить...   -  person Josh    schedule 30.03.2015


Ответы (1)


Применив оператор по модулю к вашей первой хеш-операции, вы обеспечили получение только 1000 уникальных выходных данных с этого этапа, независимо от того, сколько уникальных чисел вы использовали в качестве входных данных. Когда вы хешируете его и снова используете по модулю, случайно некоторые из этих хэшей будут отображаться в одни и те же ведра; в результате количество значений в корзине будет примерно в 1000 раз больше количества значений, хешированных для этого идентификатора корзины. Вы можете увидеть это, разделив ваши значения в массиве частот на 1000:

[1, 0, 2, 1, 0, 0, 0, ...]

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

Обязательный постскриптум: Не изобретайте собственные криптосистемы. Если это критично для безопасности, узнайте о лучших практиках и внедрите их.

person Nick Johnson    schedule 30.03.2015
comment
Спасибо, это имеет смысл. Как вы думаете, есть ли способ уменьшить входные данные до 1000 возможностей и при этом добиться равномерного распределения результатов для моего миллиона идентификаторов и любой заданной соли? На самом деле это не связано с безопасностью — мне просто нужен случайный, но детерминированный (с использованием соли) способ сопоставления идентификаторов с целым числом в [0,999] с промежуточным шагом, который уменьшает диапазон возможных входных данных (например, до 1000 возможностей). ) - person Josh; 30.03.2015
comment
(Промежуточный этап должен быть выполнен перед использованием соли) - person Josh; 30.03.2015
comment
@Josh Почему на промежуточном этапе необходимо уменьшить количество возможностей? Если ваш промежуточный шаг не выполняет модуль, вы получите результаты хорошего качества. - person Nick Johnson; 30.03.2015
comment
Однако, чтобы ответить на ваш вопрос при этих ограничениях: вам нужен промежуточный этап смешивания, который составляет 1: 1. Пример см. в этом сообщении в блоге: blog.notdot.net/2007/9/ . В качестве альтернативы сделайте свой промежуточный диапазон намного больше, чем ваш выходной диапазон, чтобы в отображении было меньше неравномерности. - person Nick Johnson; 30.03.2015
comment
Ну, причина, по которой я хочу промежуточный шаг, - это кэширование. Я строю клиент-серверную архитектуру и хочу уменьшить количество входов на клиенте, чтобы облегчить кеширование. Остальные вычисления (включая добавление соли) будут выполняться отдельно на сервере. - person Josh; 30.03.2015
comment
Спасибо за ссылку. Возможно, я смогу добиться того, чего хочу, следующим образом: (1) сделать хэш (id) mod 1000 на клиенте, чтобы получить индекс. (2) создать список, содержащий все целые числа в [0,999]. (3) перетасовать список, используя соль в качестве семени. (4) получить доступ к перетасованному списку, используя индекс из (1), чтобы получить результат. Тогда только шаг (1) должен быть на клиенте, и он должен привести к равномерному распределению, которое я хочу? - person Josh; 30.03.2015
comment
@Josh Да, это сработает - я так привык думать об очень больших диапазонах, что мне не пришло в голову очевидное решение для создания перестановки: просто сгенерируйте его. - person Nick Johnson; 30.03.2015