Ну, вы можете посмотреть это в Википедии ... Но, так как вам нужно объяснение, я сделаю все возможное здесь:
Хеш-функции
Они обеспечивают отображение между входом произвольной длины и выходом (обычно) фиксированной длины (или меньшей длины). Это может быть что угодно, от простого crc32, до полноценной криптографической хеш-функции, такой как MD5 или SHA1 / 2/256/512. Дело в том, что происходит одностороннее картирование. Это всегда отображение много: 1 (то есть всегда будут конфликты), поскольку каждая функция выдает меньший вывод, чем она может вводить (если вы подадите каждый возможный файл 1 МБ в MD5, вы получите тонну коллизий).
Причина, по которой их трудно (или практически невозможно) изменить, заключается в том, как они работают внутри. Большинство криптографических хеш-функций многократно повторяют входной набор для получения выходных данных. Поэтому, если мы посмотрим на каждый блок ввода фиксированной длины (который зависит от алгоритма), хеш-функция вызовет это текущее состояние. Затем он будет перебирать состояние, менять его на новое и использовать его в качестве обратной связи для себя (MD5 делает это 64 раза для каждого 512-битного блока данных). Затем он каким-то образом объединяет результирующие состояния всех этих итераций вместе, чтобы сформировать результирующий хеш.
Теперь, если вы хотите декодировать хеш, вам сначала нужно выяснить, как разбить данный хеш на его повторяющиеся состояния (1 возможность для входных данных, меньших, чем размер фрагмента данных, многие для больших входных данных ). Тогда вам нужно будет отменить итерацию для каждого состояния. Теперь, чтобы объяснить, почему это ОЧЕНЬ сложно, представьте себе попытку вывести a
и b
из следующей формулы: 10 = a + b
. Есть 10 положительных комбинаций a
и b
, которые могут работать. Теперь зациклитесь на этом несколько раз: tmp = a + b; a = b; b = tmp
. За 64 итерации у вас будет более 10 ^ 64 возможностей. И это просто простое дополнение, в котором сохраняется состояние от итерации к итерации. Реальные хеш-функции выполняют намного больше одной операции (MD5 выполняет около 15 операций с 4 переменными состояния). А поскольку следующая итерация зависит от состояния предыдущей, а предыдущая уничтожается при создании текущего состояния, практически невозможно определить входное состояние, которое привело к заданному выходному состоянию (для каждой итерации не меньше). Объедините это с большим количеством задействованных возможностей, и для декодирования даже MD5 потребуется почти бесконечное (но не бесконечное) количество ресурсов. Так много ресурсов, что на самом деле значительно дешевле взломать хэш, если у вас есть представление о размере входных данных (для меньших входных данных), чем даже пытаться декодировать хэш.
Функции шифрования
Они обеспечивают отображение 1: 1 между входом и выходом произвольной длины. И они всегда обратимы. Важно отметить, что это обратимо, используя некоторый метод. И это всегда 1: 1 для данного ключа. Теперь существует несколько пар ввода: ключей, которые могут генерировать один и тот же вывод (фактически, в зависимости от функции шифрования они обычно есть). Хорошие зашифрованные данные неотличимы от случайного шума. Это отличается от хорошего вывода хеша, который всегда имеет согласованный формат.
Варианты использования
Используйте хеш-функцию, когда вы хотите сравнить значение, но не можете сохранить простое представление (по любому количеству причин). Пароли должны очень хорошо подходить к этому варианту использования, так как вы не хотите хранить их в виде текста по соображениям безопасности (и не должны). Но что, если вы хотите проверить файловую систему на наличие пиратских музыкальных файлов? Было бы непрактично хранить 3 МБ на музыкальный файл. Поэтому вместо этого возьмите хеш файла и сохраните его (md5 будет хранить 16 байтов вместо 3 МБ). Таким образом, вы просто хэшируете каждый файл и сравниваете его с сохраненной базой данных хэшей (на практике это не так хорошо работает из-за перекодирования, изменения заголовков файлов и т. Д., Но это пример использования).
Используйте хеш-функцию при проверке достоверности входных данных.Вот для чего они предназначены.Если у вас есть 2 элемента ввода и вы хотите проверить, одинаковы ли они, запустите оба с помощью хеш-функции.Вероятность столкновения астрономически мала для небольших входных размеров (при условии хорошей хэш-функции).Вот почему это рекомендуется для паролей.Для паролей длиной до 32 символов у md5 в 4 раза больше свободного пространства.SHA1 имеет 6-кратное выходное пространство (приблизительно).SHA512 имеет примерно в 16 раз больше места на выходе.Тебя не волнует, какой пароль был , тебя волнует, совпадает ли он с тем, который был сохранен.Вот почему вы должны использовать хеши для паролей.
Используйте шифрование всякий раз, когда вам нужно вернуть входные данные.Обратите внимание на слово нужно .Если вы храните номера кредитных карт, вам нужно в какой-то момент вернуть их обратно, но вы не хотите хранить их в виде простого текста.Поэтому вместо этого храните зашифрованную версию и сохраняйте ключ как можно более безопасным.
Хеш-функции также отлично подходят для подписывания данных.Например, если вы используете HMAC, вы подписываете часть данных, беря хеш данных, объединенных с известным, но не переданным значением (секретным значением).Итак, вы отправляете простой текст и хэш HMAC.Затем получатель просто хэширует представленные данные с известным значением и проверяет, соответствует ли он переданному HMAC.Если это то же самое, вы знаете, что это не было подделано стороной без секретной ценности.Это обычно используется в защищенных системах cookie в каркасах HTTP, а также при передаче сообщений данных по HTTP, где требуется некоторая уверенность в целостности данных.
Замечание о хешах для паролей:
Ключевая особенность криптографических хеш-функций заключается в том, что они должны быть очень быстрыми при создании, а очень трудными / медленными для обратного (настолько, что это практически невозможно).Это создает проблему с паролями.Если вы храните sha512(password)
, вы ничего не делаете для защиты от радужных столов или атак грубой силы.Помните, хеш-функция была разработана для скорости.Поэтому для злоумышленника тривиально просто запустить словарь с помощью хэш-функции и проверить каждый результат.
Добавление соли помогает, так как добавляет немного неизвестных данных в хеш.Таким образом, вместо того, чтобы находить что-либо, что соответствует md5(foo)
, им нужно найти что-то, что при добавлении к известной соли дает md5(foo.salt)
(что очень трудно сделать).Но это все равно не решает проблему скорости, так как, если они знают соль, это всего лишь вопрос прохождения словаря.
Итак, есть способы справиться с этим.Один из популярных методов называется усиление ключа (или растяжение ключа).По сути, вы перебираете хеш много раз (обычно тысячи).Это делает две вещи.Во-первых, это значительно замедляет время выполнения алгоритма хеширования.Во-вторых, если реализовано правильно (передача входных данных и соли обратно на каждой итерации) фактически увеличивает энтропию (доступное пространство) для выходных данных, уменьшая вероятность столкновений.Тривиальная реализация:
var hash = password + salt;
for (var i = 0; i < 5000; i++) {
hash = sha512(hash + password + salt);
}
Существуют и другие, более стандартные реализации, такие как PBKDF2 , BCrypt .Но этот метод используется довольно многими системами, связанными с безопасностью (такими как PGP, WPA, Apache и OpenSSL).
Итог, hash(password)
недостаточно хорош.hash(password + salt)
лучше, но все же недостаточно хорошо ... Используйте механизм растянутых хэшей для создания хэшей паролей ...
Еще одно замечание о тривиальном растяжении
Не подпри любых обстоятельствах вывод одного хеша напрямую возвращается в хеш-функцию :
hash = sha512(password + salt);
for (i = 0; i < 1000; i++) {
hash = sha512(hash); // <-- Do NOT do this!
}
Причина этого связана с столкновениями.Помните, что все хеш-функции имеют коллизии, потому что возможное выходное пространство (количество возможных выходных данных) меньше, чем входное пространство.Чтобы понять почему, давайте посмотрим, что происходит.Чтобы предварить это, давайте предположим, что вероятность столкновения составляет 0,001% от sha1()
(на самом деле это намного меньше, но для демонстрационных целей).
hash1 = sha1(password + salt);
Теперь,hash1
имеет вероятность столкновения 0,001%.Но когда мы делаем следующие hash2 = sha1(hash1);
, , все коллизии hash1
автоматически становятся коллизиями hash2
.Итак, теперь у нас есть коэффициент hash1 на уровне 0,001%, и второй вызов sha1()
добавляет к этому.Так что теперь hash2
имеет вероятность столкновения 0,002%.Это в два раза больше шансов!Каждая итерация добавит еще один 0.001%
шанс столкновения к результату.Таким образом, при 1000 итерациях вероятность столкновения подскочила с тривиального до 0,001% до 1%.Теперь ухудшение линейно, и реальные вероятности намного меньше, но эффект тот же (оценка вероятности одного столкновения с md5
составляет около 1 / (2 128 ) или 1 / (3x10 38 ). Хотя это кажется небольшим, благодаря празднованию дня рождения на самом деле это не так мало, как кажется).
Вместо этого, каждый раз повторно добавляя соль и пароль, вы снова вводите данные обратно в хеш-функцию.Таким образом, любые столкновения любого конкретного раунда больше не являются столкновениями следующего раунда.Итак:
hash = sha512(password + salt);
for (i = 0; i < 1000; i++) {
hash = sha512(hash + password + salt);
}
Имеет такую же вероятность столкновения, как и нативная функция sha512
.Что ты хочешь?Используйте это вместо этого.