Почему XOR является стандартным способом объединения хэшей?

145

Скажем, у вас есть два хэша H(A)иH(B) , и вы хотите , чтобы объединить их. Я читал, что хороший способ объединить два хеша для XORних, например XOR( H(A), H(B) ).

Лучшее объяснение, которое я нашел, кратко затронуто здесь рекомендациям хэш-функции :

XOR двух чисел с примерно случайным распределением приводит к другому числу, все еще с примерно случайным распределением *, но которое теперь зависит от двух значений.
...
* В каждом бите двух объединяемых чисел выводится 0, если два бита равны, иначе - 1. Другими словами, в 50% комбинаций выводится 1. Таким образом, если каждый из двух входных битов имеет примерно 50-50 шанс быть равным 0 или 1, то и выходной бит тоже будет.

Можете ли вы объяснить интуицию и / или математику, почему XOR должен быть операцией по умолчанию для объединения хеш-функций (а не OR или AND и т. Д.)?

Нейт Мюррей
источник
20
Я думаю, что вы только что сделали;)
Масса
22
обратите внимание, что XOR может или не может быть «хорошим» способом «комбинировать» хэши, в зависимости от того, что вы хотите в «комбинации». XOR коммутативно: XOR (H (A), H (B)) равно XOR (H (B), H (A)). Это означает, что XOR не является подходящим способом для создания своего рода хэша упорядоченной последовательности значений, поскольку он не фиксирует порядок.
Томас Порнин
6
Помимо проблемы с порядком (комментарий выше), есть проблема с равными значениями. XOR (H (1), H (1)) = 0 (для любой функции H), XOR (H (2), H (2)) = 0 и так далее. Для любого N: XOR (H (N), H (N)) = 0. Равные значения встречаются довольно часто в реальных приложениях, это означает, что результат XOR будет 0 слишком часто, чтобы считаться хорошим хэшем.
Андрей Галатын
Что вы используете для упорядоченной последовательности значений? Допустим, я хотел бы создать хэш метки времени или индекса. (MSB менее важен, чем LSB). Извините, если этой теме 1 год.
Алексис

Ответы:

120

Принимая во внимание равномерно случайные (1-битные) входы, распределение вероятности выхода функции AND составляет 75% 0и 25% 1. И наоборот, ИЛИ составляет 25% 0и 75%1 .

Функция XOR составляет 50% 0и 50% 1, поэтому она подходит для объединения равномерных распределений вероятностей.

Это можно увидеть написав таблицы истинности:

 a | b | a AND b
---+---+--------
 0 | 0 |    0
 0 | 1 |    0
 1 | 0 |    0
 1 | 1 |    1

 a | b | a OR b
---+---+--------
 0 | 0 |    0
 0 | 1 |    1
 1 | 0 |    1
 1 | 1 |    1

 a | b | a XOR b
---+---+--------
 0 | 0 |    0
 0 | 1 |    1
 1 | 0 |    1
 1 | 1 |    0

Упражнение: Сколько логических функций двух 1-битных входов aи bимеют это равномерное распределение выходов? Почему XOR наиболее подходит для цели, указанной в вашем вопросе?

Грег Хьюгилл
источник
24
отвечая на упражнение: из 16 возможных различных операций a XXX b (0, a & b, a > b, a, a < b, b, a % b, a | b, !a & !b, a == b, !b, a >= b, !a, a <= b, !a | !b, 1)следующие имеют 50% -50% распределений 0 и 1, предполагая, что a и b имеют 50% -50% распределений 0 и 1: a, b, !a, !b, a % b, a == bт. е. противоположное XOR (EQUIV) тоже можно было использовать ...
Масса
7
Грег, это потрясающий ответ. Лампочка загорелась после того, как я увидел твой первоначальный ответ и написал свои собственные таблицы правды. Я рассмотрел ответ @ Massa о том, как существует 6 подходящих операций для поддержки распространения. И хотя у них a, b, !a, !bбудет то же распределение, что и у их соответствующих входов, вы потеряете энтропию другого входа. То есть XOR наиболее подходит для объединения хэшей, потому что мы хотим получить энтропию как от a, так и от b.
Нейт Мюррей
1
Вот документ, который объясняет, что безопасное объединение хэшей, когда каждая функция вызывается только один раз, невозможно без вывода меньшего количества бит, чем сумма количества бит в каждом хэш-значении. Это говорит о том, что этот ответ не является правильным.
Тамас Селеи
3
@Massa Я никогда не видел%, используемый для XOR или не равный.
Buge
7
Как указывает Якк , XOR может быть опасным, поскольку он дает ноль для одинаковых значений. Это означает , что (a,a)и (b,b)оба производят ноль, что во многих (большинство?) Случаев значительно увеличивает вероятность столкновений в хэш на основе структуры данных.
Дрю Ноакс
170

xorопасная функция по умолчанию для использования при хешировании Это лучше чем andи or, но это мало что говорит.

xorсимметричен, поэтому порядок элементов теряется. Так "bad"что хеш объединит так же как "dab".

xor отображает попарно одинаковые значения в ноль, и вам следует избегать отображения «общих» значений в ноль:

Таким образом, (a,a)отображается на 0, а (b,b)также отображается на 0. Так как такие пары почти всегда более распространены, чем может предполагать случайность, вы в конечном итоге столкнетесь с большим количеством столкновений в нуле, чем должны.

С этими двумя проблемами xorполучается хеш-сумматор, который выглядит наполовину прилично на поверхности, но не после дальнейшей проверки.

На современном оборудовании добавление обычно происходит примерно так же быстро xor(вероятно, для этого требуется больше энергии). Таблица истинности добавления похожа наxor на рассматриваемый бит, но она также отправляет бит на следующий бит, когда оба значения равны 1. Это означает, что она стирает меньше информации.

Так hash(a) + hash(b)что лучше, чем hash(a) xor hash(b)в том случае a==b, если результатhash(a)<<1 вместо 0 результат.

Это остается симметричным; поэтому "bad"и "dab"получение того же результата остается проблемой. Мы можем нарушить эту симметрию за скромную цену:

hash(a)<<1 + hash(a) + hash(b)

ака hash(a)*3 + hash(b). (Расчет hash(a)один раз и сохранение рекомендуется, если вы используете сменное решение). Любая нечетная константа вместо " 3биективно" будет отображать " k-битное" целое число без знака для себя, так как отображение на целые числа без знака является математическим по модулю2^k для некоторых k, и любая нечетная константа относительно проста 2^k.

Для еще более изящной версии мы можем изучить boost::hash_combine , что эффективно:

size_t hash_combine( size_t lhs, size_t rhs ) {
  lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
  return lhs;
}

здесь мы складываем несколько сдвинутых версий seedс константой (которая в основном случайная 0s и 1s - в частности, это обратное значение золотого сечения как 32-битной дроби с фиксированной запятой) с некоторым добавлением и xor. Это нарушает симметрию и вводит некоторый «шум», если входящие хэшированные значения плохие (т.е. представьте, что каждый компонент хеширует до 0 - вышеупомянутый обрабатывает это хорошо, генерируя мазок 1и 0s после каждого объединения. Мой наивный 3*hash(a)+hash(b)просто выводит a0 in тот случай).

(Для тех, кто не знаком с C / C ++, a size_t- это целое число без знака, которое достаточно велико, чтобы описать размер любого объекта в памяти. В 64-битной системе это обычно 64-битное целое число без знака. В 32-битной системе 32-разрядное целое число без знака.)

Якк - Адам Невраумонт
источник
Хороший ответ Якк. Этот алгоритм одинаково хорошо работает как в 32-битных, так и в 64-битных системах? Спасибо.
Дейв
1
@dave добавить больше битов 0x9e3779b9.
Якк - Адам Невраумонт
10
Хорошо, чтобы завершить ... вот 64-битная константа с полной точностью (рассчитывается с длинными двойными и длинными без знака длинными): 0x9e3779b97f4a7c16. Интересно, что это все еще даже. Повторное выполнение того же вычисления с использованием PI вместо Золотого сечения дает: 0x517cc1b727220a95, что является нечетным, а не четным, таким образом, вероятно, «более простым», чем другая константа. Я использовал: std :: cout << std :: hex << (unsigned long long) ((1.0L / 3.14159265358979323846264338327950288419716939937510L) * (powl (2.0L, 64.0L))) << std :: endl; с cout.precision (numeric_limits <long double> :: max_digits10); Еще раз спасибо Якк.
Дейв
2
@ Оставьте правило обратного золотого сечения для этих случаев - это первое нечетное число, равное или большее, чем вычисление, которое вы делаете. Так что просто добавьте 1. Это важное число, потому что последовательность N * отношение, mod максимальный размер (2 ^ 64 здесь) помещает следующее значение в последовательности именно в этом соотношении в середине самого большого «пробела» в номера. Поищите в Интернете «хеширование Фибоначчи» для получения дополнительной информации.
Скотт Кэри
1
@ Дейв правильный номер будет 0,9E3779B97F4A7C15F39 ... Смотрите ссылку . Возможно, вы страдаете от правила округления до четного (что хорошо для бухгалтеров), или просто, если вы начнете с константой sqrt (5), когда вы вычитаете 1, вы удаляете бит старшего разряда, a бит, должно быть, был потерян.
августа
29

Несмотря на удобные свойства смешивания битов, XOR не является хорошим способом объединения хэшей из-за его коммутативности. Подумайте, что произойдет, если вы сохранили перестановки {1, 2,…, 10} в хэш-таблице из 10 кортежей.

Гораздо лучший выбор m * H(A) + H(B), где м - большое нечетное число.

Кредит: вышеупомянутый объединитель был подсказкой от Боба Дженкинса.

Марсело Кантос
источник
2
Иногда коммутативность - хорошая вещь, но xor - паршивый выбор даже тогда, потому что все пары совпадающих элементов будут хэшироваться до нуля. Арифметическая сумма лучше; хеш пары совпадающих элементов сохранит только 31 бит полезных данных, а не 32, но это намного лучше, чем сохранение нуля. Другой вариант может состоять в том, чтобы вычислить арифметическую сумму как a, longа затем вернуть верхнюю часть обратно в нижнюю часть.
Суперкат
1
m = 3на самом деле хороший выбор и очень быстрый на многих системах. Обратите внимание, что для любого нечетного mцелочисленного умножения по модулю 2^32или, 2^64и, следовательно, оно обратимо, поэтому вы не теряете биты.
Стефан Карпински
Что происходит, когда вы выходите за пределы MaxInt?
подрывной
2
вместо любого нечетного числа нужно выбрать простое число
TermoTux
2
@Infinum, это не нужно при объединении хэшей.
Марсело Кантос
17

Xor может быть способом по умолчанию для объединения хэшей, но ответ Грега Хьюгилла также показывает, почему у него есть свои подводные камни: xor двух идентичных значений хэша равен нулю. В реальной жизни идентичные хэши встречаются чаще, чем можно было ожидать. Затем вы можете обнаружить, что в этих (не очень редких) угловых случаях результирующие комбинированные хэши всегда одинаковы (ноль). Хеш-коллизии будут намного, намного чаще, чем вы ожидаете.

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

Лео Гудштадт
источник
Надеюсь, надуманного примера не произойдет, пароли должны быть засолены.
user60561
8

Есть кое-что, что я хочу явно указать для тех, кто находит эту страницу. И и ИЛИ ограничивают вывод, как BlueRaja - Дэнни Пфлугхо пытается указать, но может быть лучше определен:

Сначала я хочу определить две простые функции, которые я буду использовать для объяснения этого: Min () и Max ().

Min (A, B) вернет меньшее значение между A и B, например: Min (1, 5) возвращает 1.

Max (A, B) вернет значение, большее между A и B, например: Max (1, 5) возвращает 5.

Если вам дают: C = A AND B

Тогда вы можете найти, что C <= Min(A, B) Мы знаем это, потому что вы ничего не можете И С 0 битами А или В сделать их 1. Таким образом, каждый нулевой бит остается нулевым, и каждый бит имеет шанс стать нулевым (и, следовательно, меньшим значением).

С участием: C = A OR B

Верно обратное: C >= Max(A, B)с этим мы видим следствие функции AND. Любой бит, который уже равен единице, не может быть преобразован в ноль, поэтому он остается равным единице, но каждый нулевой бит имеет шанс стать единицей и, следовательно, большим числом.

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

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

Кори Огберн
источник
6
Можно сказать , что ORэто побитовое максимум , и ANDэто побитовое мин .
Пауло Эберманн
Очень хорошо заявил Пауло Эберманн. Приятно видеть вас здесь, а также Crypto.SE!
Кори Огберн
Я создал фильтр, который включает в себя все помеченные криптографией , а также изменения на старые вопросы. Таким образом, я нашел ваш ответ здесь.
Paŭlo Ebermann
3

Если вы XORслучайный вход с предвзятым входом, выход является случайным. То же самое не верно для ANDили OR. Пример:

00101001 XOR 00000000 = 00101001
00101001 И 00000000 = 00000000
00101001 ИЛИ 11111111 = 11111111

Как упоминает @Greg Hewgill, даже если оба входа являются случайными, использование ANDили ORприведет к смещенному выводу.

Причина, по которой мы используем XORчто-то более сложное, в том, что ну, в этом нет необходимости: XORработает отлично, и это чертовски быстро.

BlueRaja - Дэнни Пфлугхофт
источник
1

Покройте 2 левых столбца и попытайтесь выяснить, какие входные данные используют только выходные данные.

 a | b | a AND b
---+---+--------
 0 | 0 |    0
 0 | 1 |    0
 1 | 0 |    0
 1 | 1 |    1

Когда вы видели 1-бит, вы должны были понять, что оба входа были 1.

Теперь сделайте то же самое для XOR

 a | b | a XOR b
---+---+--------
 0 | 0 |    0
 0 | 1 |    1
 1 | 0 |    1
 1 | 1 |    0

XOR ничего не дает по этому поводу.

Роберт
источник
0

Исходный код для различных версий hashCode()в java.util.Arrays - отличный справочник по надежным, широко используемым алгоритмам хеширования. Они легко понимаются и переводятся на другие языки программирования.

Грубо говоря, большинство реализаций с несколькими атрибутами hashCode()следуют этому шаблону:

public static int hashCode(Object a[]) {
    if (a == null)
        return 0;

    int result = 1;

    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());

    return result;
}

Вы можете искать другие вопросы и ответы в StackOverflow для получения дополнительной информации о магии, стоящей за ней 31, и о том , почему Java-код использует ее так часто. Он несовершенен, но имеет очень хорошие общие характеристики.

kevinarpe
источник
2
По умолчанию в Java хэш «умножить на 31 и добавить / накапливать» загружен коллизиями (например, любые коллизии stringс string + "AA"IIRC), и они давно хотели, чтобы они не запеклись в этом алгоритме в спецификации. Тем не менее, использование большего нечетного числа с большим количеством установленных битов и добавление сдвигов или вращений решает эту проблему. MurmurHash3 'mix' делает это.
Скотт Кэри
0

XOR не игнорирует некоторые входные данные, такие как OR и AND .

Если вы возьмете, например, AND (X, Y) и зададите для входа X значение false, то вход Y не имеет значения ... и, возможно, вы захотите, чтобы значение ввода имело значение при объединении хэшей.

Если вы возьмете XOR (X, Y), тогда ОБА входы ВСЕГДА имеют значение. Там не будет никакого значения X, где Y не имеет значения. Если изменяется X или Y, то результат будет отражать это.

Sunsetquest
источник