
The hmac-bcrypt password hashing function
Этот репозиторий содержит эталонные реализации функции хеширования паролей hmac-bcrypt на нескольких языках. Каждая эталонная реализация стремится быть портом 1:1 оригинальных реализаций на C и Perl, созданных @epixoip, где это возможно, и полностью совместима с другими реализациями (т.е. они генерируют и проверяют одни и те же значения хешей).
Каждая эталонная реализация определяет две процедурные функции со следующими псевдо-прототипами:
string hmac_bcrypt_hash(password, settings?, pepper?)
boolean hmac_bcrypt_verify(password, expected, pepper?)
Пожалуйста, обратитесь к тестовым примерам, приложенным к каждой эталонной реализации, чтобы узнать, как интегрировать и использовать эти функции в вашем проекте. Процедурный интерфейс был выбран для простоты, но вы можете свободно включать эти функции в классы или объекты по своему усмотрению.
Параметр settings в данном контексте относится к стандартной строке настроек bcrypt, содержащей идентификатор хеша (2a), логарифмическую стоимость по основанию 2 (например, 13) и необязательное 22-байтовое значение соли, закодированное в radix64 (например, LhayLxezLhK1LhWvKxCyLO). Эти значения объединяются в строку, разделённую символами доллара; например, $2a$13$LhayLxezLhK1LhWvKxCyLO.
Параметр settings является необязательным; в большинстве случаев его следует оставлять null/пустым, чтобы использовать стоимость по умолчанию 13 и сгенерированную соль. В крайнем случае, если вы хотите использовать значение стоимости, отличное от 13, вы можете указать только идентификатор + значение стоимости (например, $2a$10$). Создавать и передавать собственные значения соли не рекомендуется!
Параметр pepper определяет общий глобальный секрет и также является необязательным; если он null/пуст, используется значение по умолчанию hmac_bcrypt. Это предназначено в первую очередь для защиты от атак shucking, но также может использоваться для повышения безопасности, сложности и стоимости взлома (особенно при использовании совместно с HSM).
Функция хеширования паролей hmac-bcrypt использует bcrypt с корректным предварительным и последующим хешированием в сочетании с необязательным pepper. В псевдокоде это довольно просто:
pre_hash = hmac_sha512_base64(password, pepper)
mid_hash = bcrypt(pre_hash, settings)
post_hash = hmac_sha512_base64(mid_hash, pepper)
return settings + post_hash
Предварительное хеширование применяется для поддержки входных данных длиной больше максимальных 72 байт bcrypt. SHA-512 была выбрана из-за 64-битного размера слова, что дружелюбно к защитникам на CPU, но затрудняет атаки на GPU. Однако сырое значение SHA-512 не может быть использовано по нескольким причинам:
Чтобы смягчить атаки shucking, предварительный хеш должен быть солёным — или в данном случае peppered — а HMAC предоставляет удобный механизм для ключевания хеша. Полученное значение HMAC затем кодируется с помощью base64 для получения чистого, нижнего ASCII-входа, который устраняет проблемы с нулевыми байтами и бинарными данными.
Внимательный читатель заметит, что hmac_sha512_base64 выдаёт 88 байт данных, тогда как bcrypt имеет максимальный размер входных данных 72 байта. Это не проблема и, более того, предпочтительнее использования алгоритма хеширования, который выдаёт меньше входных данных, такого как sha256. Мы хотим заполнить все 72 байта, и никакая безопасность не теряется при усечении sha512 до 432 бит (это больше, чем 384 бита, которые предоставляет sha384).
Последующее хеширование применяется в основном для того, чтобы отличать хеши hmac-bcrypt от хешей bcrypt — то есть длины будут различаться — но также для добавления дополнительного уровня защиты благодаря pepper. Этап последующего хеширования может даже выполняться со значением pepper, хранящимся в HSM (настоятельно рекомендуется!) для дополнительной защиты.
Хотя устойчивость к использованию памяти была интересным экспериментом, правильный путь к достижению устойчивости к ускорению — это, очевидно, устойчивость к кэшированию. Скорости памяти и пропускная способность продолжают расти, в то время как оперативная память становится больше, дешевле и плотнее. Но размеры кэша, скорости кэша и стоимость кэша относительно статичны. Даже аппаратные инструкции scatter/gather не оказали того драматического влияния на кэш-устойчивые алгоритмы, которое мы когда-то предсказывали.
Лучшие алгоритмы, устойчивые к использованию памяти — Argon2 и scrypt — на самом деле менее устойчивы к ускорению, чем кэш-устойчивые алгоритмы, при целевом времени выполнения менее 1000 мс, что делает их отличными KDF, но не очень хорошими для аутентификации в реальном времени.
В идеале следовало бы использовать намеренно кэш-устойчивую функцию хеширования паролей, такую как pufferfish или bscrypt. Однако эти функции новее, менее изучены и имеют мало доступных библиотек. bcrypt, с другой стороны — хотя и непреднамеренно кэш-устойчивый — доступен практически для любого языка и фреймворка. Из алгоритмов, которые у нас есть в наличии, bcrypt обеспечивает наибольшую устойчивость к ускорению для аутентификации в реальном времени (целевое время выполнения < 1000 мс), поэтому очевидный ответ — использовать тот bcrypt, который у нас есть.
Однако у bcrypt есть некоторые заметные ограничения, на которые быстро указывают его очень громкие критики:
hmac-bcrypt решает обе эти проблемы и не только.