Чому XOR є типовим способом комбінування хешей?


145

Скажімо , у вас є два хеш H(A)і , H(B)і ви хочете , щоб об'єднати їх. Я читав, що хороший спосіб поєднати два хеши - це XOR, наприклад XOR( H(A), H(B) ).

Найкраще пояснення, яке я знайшов, коротко торкається тут, у цих рекомендаціях щодо хеш-функцій :

XORing двох чисел з приблизно випадковим розподілом призводить до іншого числа, яке все ще має приблизно випадковий розподіл *, але тепер залежить від двох значень.
...
* На кожен біт двох чисел, які потрібно об'єднати, виводиться 0, якщо два біти рівні, інакше a 1. Іншими словами, у 50% комбінацій виведеться 1. Отже, якщо два вхідних біта мають приблизно 50-50 шансів бути 0 або 1, то так само буде і вихідний біт.

Чи можете ви пояснити інтуїцію та / або математику, чому XOR повинен бути операцією за замовчуванням для комбінування хеш-функцій (а не АБО чи І) тощо?


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 можливих різних операцій 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% розподілу на 0s і 1s, якщо a і b мають 50% -50% розподілу на 0s і 1s: a, b, !a, !b, a % b, a == bтобто, навпаки XOR (EQUIV) міг бути використаний також ...
Massa

7
Грег, це дивовижна відповідь. Лампочка ввімкнулася для мене після того, як я побачив вашу оригінальну відповідь і виписав власні таблиці правди. Я розглянув відповідь @ Масса про те, як існує 6 підходящих операцій для підтримки розподілу. І хоча вони a, b, !a, !bбудуть мати той самий розподіл, що і їхні вхідні дані, ви втрачаєте ентропію іншого вводу. Тобто, XOR найбільш підходить для комбінування хешів, оскільки ми хочемо зафіксувати ентропію і від a, і b.
Нейт Мюррей

1
Ось документ, в якому пояснюється, що безпечно поєднувати хеші, де кожна функція викликається лише один раз, неможливо без виведення менших бітів, ніж сума кількості бітів у кожному хеш-значенні. Це говорить про те, що ця відповідь є невірною.
Tamás Szelei

3
@Massa Я ніколи не бачив%, використовуваних для XOR або не рівних.
Buge

7
Як зазначає Yakk , 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. Це порушує симетрію, і вводить деякі «шум» , якщо входять Hashed значення є бідними (тобто, уявіть собі , кожен компонент хеші 0 - вищевказані ручки це добре, створюючи мазок 1і 0. S після кожного помошью наївним 3*hash(a)+hash(b)просто виводить 0ін той випадок).

(Для тих, хто не знайомий з C / C ++, a size_t- ціле цільове значення без підпису, яке є достатньо великим, щоб описати розмір будь-якого об'єкта в пам'яті. У 64-бітовій системі зазвичай це 64-бітове безпідписане ціле число. У 32-бітовій системі , 32-бітове ціле число без підпису.)


Гарна відповідь Якк. Чи працює цей алгоритм однаково добре як для 32-бітної, так і для 64-бітової систем? Дякую.
Дейв

1
@dave додати більше біт до 0x9e3779b9.
Якк - Адам Невраумон

10
Гаразд, щоб бути завершеним ... ось повна константа точності 64 біт (обчислена з довгими подвійними і неподписаними довгими довгими): 0x9e3779b97f4a7c16. Цікаво, що це все-таки рівномірно. Повторне проведення того ж обчислення за допомогою PI замість Золотого співвідношення дає: 0x517cc1b727220a95, що є непарним, а не парним, таким чином, ймовірно, "більш простим", ніж інша константа. Я використав: std :: cout << std :: hex << (неподписаний довгий довгий) ((1.0L / 3.14159265358979323846264338327950288419716939937510L) * (powl (2.0L, 64.0L))) << std :: endl; з cout.precision (numeric_limits <long double> :: max_digits10); Ще раз дякую Якку.
Дейв

2
@Введіть правило зворотного золотого відношення для цих випадків - перше непарне число, рівне або більше, ніж обчислення, яке ви робите. Тому просто додайте 1. Це важливе число, оскільки послідовність співвідношення N *, mod max розмір (2 ^ 64 тут) розміщує наступне значення в послідовності саме в такому співвідношенні в середині найбільшого 'розриву' в числа. Шукайте в Інтернеті "хешування Фібоначчі" для отримання додаткової інформації.
Скотт Кері

1
@Введіть правильне число 0,9E3779B97F4A7C15F39 ... Дивіться посилання . Ви можете страждати від правила "круглого на рівне" (що добре для бухгалтерів), або просто, якщо ви почнете з буквальної константи sqrt (5), коли ви віднімаєте 1, ви видаляєте біт високого порядку, біт, мабуть, був загублений.
migle

29

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

Значно кращим є вибір m * H(A) + H(B), де m - велике непарне число.

Кредит: Вищезгаданий комбайнер був порадою від Боб Дженкінса.


2
Іноді комутативність - це гарна річ, але xor - це невдалий вибір навіть тоді, тому що всі пари відповідних елементів будуть хешировані до нуля. Арифметична сума краща; хеш пари збігаються елементів збереже лише 31 біт корисних даних, а не 32, але це набагато краще, ніж збереження нуля. Іншим варіантом може бути обчислення арифметичної суми як а, longа потім з'єднання верхньої частини назад з нижньою частиною.
supercat

1
m = 3насправді хороший вибір і дуже швидко в багатьох системах. Зауважте, що будь-яке непарне mціле множення є модульним 2^32або 2^64, отже, є зворотним, тому ви не втрачаєте жодних бітів.
StefanKarpinski

Що відбувається, коли ви виходите за межі MaxInt?
руйнівний

2
замість будь-якого непарного номера один слід вибрати прайм
TermoTux

2
@Infinum, що не потрібно при комбінуванні хешей.
Марсело Кантос

17

Xor може бути "за замовчуванням" способом комбінування хешів, але відповідь Грега Х'югілла також показує, чому він має свої підводні камені: xor двох однакових хеш-значень дорівнює нулю. У реальному житті однакові хеші зустрічаються частіше, ніж можна було очікувати. Тоді ви можете виявити, що в цих (не дуже рідкісних) кутових випадках отримані комбіновані хеші завжди однакові (нульові). Зіткнення хешу було б набагато, набагато частішим, ніж ви очікуєте.

У надуманому прикладі ви можете поєднувати хешовані паролі користувачів з різних веб-сайтів, якими ви керуєте. На жаль, велика кількість користувачів повторно використовує свої паролі, і дивовижна частка отриманих хешей дорівнює нулю!


Я сподіваюсь, що надуманого прикладу ніколи не буває, паролі слід солити.
user60561

8

Щось я хочу чітко зазначити для інших, хто знайшов цю сторінку. І та АБО обмежують вихід, як BlueRaja - Danny Pflughoe намагається вказати, але можна краще визначити:

Спершу я хочу визначити дві прості функції, які буду використовувати для пояснення цього: 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 біт A або B зробити їх 1. Отже, кожен нульовий біт залишається нульовим бітом, і кожен бит має шанс стати нульовим бітом (і, таким чином, меншим значенням).

З: C = A OR B

Це навпаки: C >= Max(A, B)з цим ми бачимо наслідки функції AND. Будь-який біт, який вже є одиницею, не може бути ОРЕНДЕНО, тому він залишається одиничним, але кожен нульовий біт має шанс стати одиницею, а значить, більшою кількістю.

Це означає, що стан введення застосовує обмеження на вихід. Якщо у вас І що завгодно з 90, ви знаєте, що вихід буде дорівнює або менше 90, незалежно від іншого значення.

Для XOR не передбачається обмеження на основі вхідних даних. Існують особливі випадки, коли ви можете виявити, що якщо ви XOR байт з 255, ви отримаєте зворотний, але будь-який можливий байт може бути виведений з цього. Кожен біт має шанс змінити стан залежно від того самого біта в іншому операнді.


6
Можна сказати , що ORце побітовое максимум , і ANDце побітовое хв .
Paŭlo Ebermann

Дуже добре заявив Пауло Еберман. Приємно бачити вас тут, а також Crypto.SE!
Corey Ogburn

Я створив фільтр, який включає в себе всю тегову криптографію , а також змінює старі питання. Таким чином я знайшов тут вашу відповідь.
Paŭlo Ebermann

3

Якщо ви маєте XORвипадковий вхід з упередженим входом, вихід буде випадковим. Те саме не стосується ANDабо OR. Приклад:

00101001 XOR 00000000 = 00101001
00101001 І 00000000 = 00000000
00101001 АБО 11111111 = 11111111

Як згадує @Greg Hewgill, навіть якщо обидва входи випадкові, використовуючи ANDабо ORпризведе до упередженого виводу.

Причина, яку ми використовуємо XORнад чимось складнішим, полягає в тому, що, ну, немає потреби: XORпрацює прекрасно, і це надзвичайно дурно-швидко.


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 Q & As для отримання додаткової інформації про магію, що стоїть 31, і чому Java-код використовує її так часто. Він недосконалий, але має дуже хороші загальні експлуатаційні характеристики.


2
За замовчуванням хеш "множиться на 31 та додавати / накопичувати" хеш завантажується зіткненнями (наприклад, будь-якими stringзіткненнями з string + "AA"IIRC), і вони давно хотіли, щоб вони не запускали цей алгоритм у специфікацію. Однак, використання більшого непарного числа з набором більшої кількості бітів та додавання зрушень чи обертів вирішує цю проблему. MurmurHash3 "суміш" робить це.
Скотт Кері

0

XOR не ігнорує деякі з входів іноді як OR і І .

Якщо ви берете для прикладу AND (X, Y) і подаєте введення X з помилковим, то введення Y не має значення ... і, мабуть, хотілося б, щоб вхід мав значення при поєднанні хешей.

Якщо взяти XOR (X, Y) потім ОБИДВА входи ЗАВЖДИ справа. Там, де Y не має значення, значення X не було б. Якщо змінити або X, або Y, результат буде відображати це.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.