Ну, вы можете посмотреть это в Википедии ... Но, так как вам нужно объяснение, я сделаю все возможное здесь:
Хэш-функции
Они обеспечивают отображение между входом произвольной длины и выходом (обычно) фиксированной длины (или меньшей длины). Это может быть что угодно, от простого 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
функция Что ты хочешь? Используйте это вместо этого.