Как сделать хэш код

Добавил пользователь Алексей Ф.
Обновлено: 19.09.2024

В предыдущей части, если не читали вот она, мы подробно рассмотрели работу метода equals(), его контракт, ошибки и их исправления. Теперь настала очередь второго попугая-неразлучника – метода hashCode().

При переопределении метода equals() мы всегда должны переопределять метод hashCode(). Метод hashCode() – вычисляет целочисленное значение для конкретного элемента класса, чтобы использовать его для быстрого поиска и доступа к этому элементу в hash-структурах данных, например, HashMap, HashSet и прочих. Почему важно переопределять hashCode() всегда вместе с методом equals()? Развернуто ответим на этот вопрос. Пожалуй, необходимо и достаточно знать два важных аспекта, чтобы понять, почему необходимо делать переопределение методов вместе:

  1. Hash-код объекта используется для быстрой навигации в Hash – таблицах. Поэтому достаточно понять сам процесс поиска/вставки/удаления элемента в этих таблицах
  2. Связь equals() и hashCode(). Если два объекта o1 и o2 являются equals() (o1.equals(o2) = true), то они должны иметь одинаковый hash-код. Обратное – не обязательно.

Что такое хеш-таблицы (Hash Tables)?

пример hash table

Хэш – таблицы – это своего рода ассоциативный массив, хранящий значения в виде “ключ-значение”. Рассмотрим работу вставки элемента в хеш-таблицу:

  1. На входе мы получаем key – некий объект.
  2. Для объекта вызывается метод hashCode(), который вернет hash-код объекта – целое число.
  3. По этому числу мы находим соответствующий ему bucket – определенная структура, хранящая в себе все объекты с одинаковыми hash-кодом. В нашем случае это будет список.
  4. В найденный bucket мы записываем объект key.

На деле все просто, но если еще раз перечитать контракт hashCode() и equals(), то все становиться немного труднее: возможны коллизии – два разных объекта имеют одинаковый hash-код. Что делать? Эта проблема и ее решение отражены на рисунке выше. Два объекта John Smith и Sandra Dee имеют один и тот же hash-код. Для разрешения это коллизии мы просто берем за структуру bucket направленный список. И сохраняем два значения по одному hash-коду.

Как сломать хеш – таблицу?

При неверной реализации метода hashCode() мы можем легко сломать hash-таблицу. Вернее даже сказать не сломать, а сделать ее вырожденной. Например, переопределив метод hashCode() следующим образом

Криптографические хеш-функции — незаменимый и повсеместно распространенный инструмент, используемый для выполнения целого ряда задач, включая аутентификацию, защиту файлов и даже обнаружение зловредного ПО. Как они работают и где применяются?


Криптографические хеш-функции — незаменимый и повсеместно распространенный инструмент, используемый для выполнения целого ряда задач, включая аутентификацию, проверку целостности данных, защиту файлов и даже обнаружение зловредного ПО. Существует масса алгоритмов хеширования, отличающихся криптостойкостью, сложностью, разрядностью и другими свойствами. Считается, что идея хеширования принадлежит сотруднику IBM, появилась около 50 лет назад и с тех пор не претерпела принципиальных изменений. Зато в наши дни хеширование обрело массу новых свойств и используется в очень многих областях информационных технологий.

Что такое хеш?

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

Как работает хеш?

Например, мое имя — Brian — после преобразования хеш-функцией SHA-1 (одной из самых распространенных наряду с MD5 и SHA-2) при помощи онлайн-генератора будет выглядеть так: 75c450c3f963befb912ee79f0b63e563652780f0. Как вам скажет, наверное, любой другой Брайан, данное имя нередко пишут с ошибкой, что в итоге превращает его в слово brain (мозг). Это настолько частая опечатка, что однажды я даже получил настоящие водительские права, на которых вместо моего имени красовалось Brain Donohue. Впрочем, это уже другая история. Так вот, если снова воспользоваться алгоритмом SHA-1, то слово Brain трансформируется в строку 97fb724268c2de1e6432d3816239463a6aaf8450. Как видите, результаты значительно отличаются друг от друга, даже несмотря на то, что разница между моим именем и названием органа центральной нервной системы заключается лишь в последовательности написания двух гласных. Более того, если я преобразую тем же алгоритмом собственное имя, но написанное уже со строчной буквы, то результат все равно не будет иметь ничего общего с двумя предыдущими: 760e7dab2836853c63805033e514668301fa9c47.

Впрочем, кое-что общее у них все же есть: каждая строка имеет длину ровно 40 символов. Казалось бы, ничего удивительного, ведь все введенные мною слова также имели одинаковую длину — 5 букв. Однако если вы захешируете весь предыдущий абзац целиком, то все равно получите последовательность, состоящую ровно из 40 символов: c5e7346089419bb4ab47aaa61ef3755d122826e2. То есть 1128 символов, включая пробелы, были ужаты до строки той же длины, что и пятибуквенное слово. То же самое произойдет даже с полным собранием сочинений Уильяма Шекспира: на выходе вы получите строку из 40 букв и цифр. При всем этом не может существовать двух разных массивов данных, которые преобразовывались бы в одинаковый хеш.

Вот как это выглядит, если изобразить все вышесказанное в виде схемы:

Как работает хеширование

Для чего используется хеш?

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

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

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

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

Как при помощи хеша ловить вирусы?

Криптографические хеш-функции также могут использоваться для защиты от фальсификации передаваемой информации. Иными словами, вы можете удостовериться в том, что файл по пути куда-либо не претерпел никаких изменений, сравнив его хеши, снятые непосредственно до отправки и сразу после получения. Если данные были изменены даже всего на 1 байт, хеш-коды будут отличаться, как мы уже убедились в самом начале статьи. Недостаток такого подхода лишь в том, что криптографическое хеширование требует больше вычислительных мощностей или времени на вычисление, чем алгоритмы с отсутствием криптостойкости. Зато они в разы надежнее.

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

Скажем, у меня есть объект, который хранит массив байтов, и я хочу иметь возможность эффективно генерировать хэш-код для него. Я использовал криптографические хеш-функции для этого в прошлом, потому что их легко реализовать, но они делают гораздо больше работы, чем должны быть криптографически в ожидании, и мне это неинтересно (я просто использую хэш-код в качестве ключа в хэш-таблицу).

Вот что у меня сегодня:

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

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

ОТВЕТЫ

Ответ 1

Хэш-код объекта не обязательно должен быть уникальным.

  • Являются ли хэш-коды равными? Затем вызовите полный (медленный) метод Equals .
  • Являются ли хэш-коды не равными? Тогда два элемента, безусловно, не равны.

Все, что вам нужно - это алгоритм GetHashCode , который разбивает вашу коллекцию на грубые четные группы - он не должен формировать ключ, так как HashTable или Dictionary<> должен использовать хеш для оптимизации поиска.

Как долго вы ожидаете, что данные будут? Как случайный? Если длины сильно различаются (скажем, для файлов), просто верните длину. Если длины, вероятно, будут одинаковыми, посмотрите на подмножество байтов, которое меняется.

GetHashCode должен быть намного быстрее, чем Equals , но не обязательно должен быть уникальным.

У двух одинаковых вещей никогда не должно быть разных хэш-кодов. Два разных объекта не должны иметь один и тот же хэш-код, но следует ожидать некоторых коллизий (в конце концов, есть больше перестановок, чем возможно 32-битных целых чисел).

Ответ 2

Не используйте криптографические хэши для хэш-таблицы, что смешно /overkill.

Ответ 3

Заимствуя код, созданный программным обеспечением JetBrains, я решил эту функцию:

Проблема с просто XOring байтами состоит в том, что 3/4 (3 байта) возвращаемого значения имеет только 2 возможных значения (все включено или все выключено). Это немного расширяет бит вокруг.

Установка точки останова в Equals была хорошим предложением. Добавив около 200 000 записей моих данных в словарь, вы увидите около 10 вызовов Equals (или 1/20000).

Ответ 4

Ответ 5

Я нашел интересные результаты:

У меня есть класс:

Затем я создал словарь с ключами типа MyHash, чтобы проверить, как быстро я могу вставить, и я также могу знать, сколько коллизий существует. Я сделал следующее

для выполнения которого потребовалось 2 секунды. Метод

также не имел коллизий, но потребовалось 7 секунд!

Ответ 6

Если вы ищете производительность, я проверил несколько хеш-ключей и Я рекомендую функцию хэша Боба Дженкина. Это безумно быстро для вычисления и даст как можно меньше столкновений с криптографическим хэш, который вы использовали до сих пор.

Ответ 7

Является ли использование существующего хэш-кода из поля массива байтов недостаточно хорошим? Также обратите внимание, что в методе Equals вы должны проверить, что массивы имеют одинаковый размер перед выполнением сравнения.

Ответ 8

Создание хорошего хэша проще сказать, чем сделать. Помните, что вы в основном представляете n байтов данных с m бит информации. Чем больше ваш набор данных и чем меньше m, тем более вероятным будет столкновение. две части данных, разрешающие один и тот же хэш.

Самый простой хеш, который я когда-либо узнал, - это просто XORing все байты вместе. Это легко, быстрее, чем самые сложные алгоритмы хеширования, и наполовину приемлемый универсальный хэш-алгоритм для небольших наборов данных. На самом деле это алгоритм Bubble Sort of hash. Поскольку простая реализация оставила бы вас с 8 бит, то только 256 хешей. не так жарко. Вы могли бы использовать фрагменты XOR вместо отдельных байтов, но тогда алгоритм становится намного сложнее.

Итак, криптографические алгоритмы, возможно, делают некоторые вещи, которые вам не нужны. но они также являются огромным шагом в улучшении хэш-качества общего назначения. Хэш MD5, который вы используете, имеет 128 бит, с миллиардами и миллиардами возможных хэшей. Единственный способ, которым вы, вероятно, получите что-то лучшее, - это взять некоторые репрезентативные образцы данных, которые вы ожидаете от своего приложения, и попробовать различные алгоритмы, чтобы увидеть, сколько коллизий вы получаете.

Итак, пока я не вижу причины не использовать консервированный алгоритм хэширования (производительность, возможно?), мне придется рекомендовать вам придерживаться того, что у вас есть.

Ответ 9

Если вы хотите идеальную хешфункцию (различное значение для каждого объекта, который оценивается равным) или просто довольно хороший, всегда есть компромисс производительности, обычно требуется время для вычисления хорошей хэш-функции, и если ваш набор данных мал, вы лучше с быстрой функцией. Самое важное (как указывает ваш второй пост) - это правильность, и для достижения всего вам нужно вернуть длину массива. В зависимости от вашего набора данных, который может быть даже в порядке. Если это не так (скажем, все ваши массивы одинаково длинны), вы можете пойти с чем-то дешевым, глядя на первое и последнее значение и XORing их значения, а затем добавить больше сложности, как вы сочтете нужным для своих данных.

Быстрый способ увидеть, как ваша хеш-функция выполняет ваши данные, - это добавить все данные в хэш-таблицу и подсчитать количество раз, когда вызывается функция Equals, если слишком часто у вас больше работы над функцией, Если вы это сделаете, просто имейте в виду, что размер хэш-таблицы должен быть больше, чем ваш набор данных, когда вы начнете, в противном случае вы собираетесь перефразировать данные, которые будут вызывать повторные вставки и другие оценки Equals (хотя, возможно, более реалистично?)

Для некоторых объектов (а не для этого) быстрый код HashCode может быть сгенерирован ToString(). GetHashCode(), конечно, не является оптимальным, но полезным, поскольку люди склонны возвращать что-то близкое к идентичности объекта из ToString() и это именно то, что ищет GetHashcode

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

Ответ 10

Ответ 11

Если кто-то любит бегло:

Обратите внимание, что последовательно равные массивы имеют одинаковый хэш-код.

Ответ 12

Из Msdn:

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

Одним из ключевых слов, которые новички слышат, когда узнают о блокчейне, являются понятия хэша и алгоритма хэширования, которые кажутся распространёнными для безопасности. Запуск децентрализованной сети и консенсуса, такой как биткойн или сеть эфириум с десятками тысяч узлов, соединенных через p2p, требует, как “надежности”, так и эффективности проверки. То есть, эти системы нуждаются в способах кодирования информации в компактном формате, позволяющем обеспечить безопасную и быструю проверку ее участниками

  • Изменение одного бита во входных данных должно создать эффект изменения всего хеша;
  • Вычисления хеша не должно быть слишком простым, высокая сложность нахождения прообраза;
  • Должен иметь очень низкую вероятность коллизии;

Вы когда-нибудь слышали о том, что если вы поместите 23 человека в комнату, есть 50% шанс, что у двух из них будет один и тот же день рождения? Доведение числа до 70 человек в комнате дает вам 99,9% шанс. Если голуби рассажены в коробки, причем число голубей больше числа коробок, то хотя бы в одной из клеток находится более одного голубя. То есть фиксированные ограничения на выход означают, что существует фиксированная степень перестановок, на которых можно найти коллизию.

На самом деле MD5 настолько слаб к сопротивлению к коллизиям, что простой бытовой Процессор Pentium 2,4 ГГц может вычислить искусственные хэш-коллизии в течение нескольких секунд. Кроме того, его широкое использование в более ранние дни текущей сети создало тонны утечек MD5 предварительных прообразов в интернете, которые можно найти с помощью простого поиска Google их хэша.

NSA (Агентство национальной безопасности) уже давно является пионером стандартов алгоритмов хэширования, с их первоначальным предложением алгоритма Secure Hashing Algorithm или SHA1, создающий 160-битные выходы фиксированной длины. К сожалению, SHA1 просто улучшил MD5, увеличив длину вывода, количество однонаправленных операций и сложность этих односторонних операций, но не дает каких-либо фундаментальных улучшений против более мощных машин, пытающихся использовать различные атаки. Так как мы можем сделать что-то лучше?

Когда дело дошло до интеграции алгоритма хеширования в блокчейн протоколы, биткоин использовал SHA256, в то время как Ethereum использовал модифицированный SHA3 (KECCAK256) для своего PoW. Однако важным качеством выбора хэш-функции для блокчейна с использованием доказательства работы является эффективность вычислений указанного хэша. Алгоритм хеширования биткойна SHA256 может быть вычислен достаточно просто с помощью специализированного оборудования, известного как специализированные интегральные схемы (или ASIC). Много было написано об использовании ASIC в майнинг пуле и о том, как они делают протокол направленным на централизацию вычислений. То есть доказательство работы стимулирует группы вычислительно эффективных машин объединяться в пулы и увеличивать то, что мы обозначаем “хэш-мощностью”, или мерой количества хэшей, которые машина может вычислить за интервал времени. Ethereum, выбрал модифицированный SHA3 известный как KECCAK 256. Кроме того, алгоритм PoW в Ethereum - Dagger-Hashimoto, должен был быть трудно вычисляемым для аппаратного обеспечения.

SHA3 не был единственным прорывом, который вышел из конкурса хеширования NIST в 2006 году. Несмотря на то, что SHA3 выиграл, алгоритм, известный как BLAKE, занял второе место. Для реализации шардинга Ethereum 2.0 использует более эффективное. Алгоритм хэширования BLAKE2b, который является высокоразвитой версией BLAKE от конкурентов, интенсивно изучается за его фантастическую эффективность по сравнению с KECCAK256 при сохранении высокой степени безопасности. Вычисление BLAKE2b фактически в 3 раза быстрее, чем KECCAK на современном процессоре.

Дмитриев Марк - Технический аналитик и управляющий криптоактивами инвестиционного фонда GT Blockchain Investments

Читайте также: