Проблема здесь в основном в проблеме энтропии. Итак, начнем искать там:
Энтропия на символ
Количество бит энтропии на байт:
- Hex Characters
- Bits: 4
- Значения: 16
- Энтропия в 72 символах: 288 бит
- Alpha-Numeric
- Bits: 6
- Значения: 62
- Энтропия в 72 символах: 432 бита
- "Common" Symbols
- Bits: 6.5
- Значения: 94
- Энтропия в 72 символах: 468 бит
- Full Bytes
- Bits: 8
- Значения: 255
- Энтропия в 72 символах: 576 бит
Итак, то, как мы будем действовать, зависит от того, каких персонажей мы ожидаем.
Первая проблема
Первая проблема с вашим кодом заключается в том, что на шаге хеширования "перец" выводятся шестнадцатеричные символы (поскольку четвертый параметр hash_hmac()
не установлен).
Следовательно, перемешивая перец, вы фактически сокращаете максимальную энтропию, доступную для пароля, в 2 раза (с 576 до 288 возможных бит).
Вторая проблема
Однако sha256
в первую очередь обеспечивает только 256
бит энтропии. Таким образом, вы фактически сокращаете возможные 576 бит до 256 бит. Ваш шаг хеширования * немедленно * по самому определению теряет как минимум 50% возможной энтропии пароля.
Вы можете частично решить эту проблему, переключившись на SHA512
, где вы уменьшите доступную энтропию только примерно на 12%. Но это все же немаловажная разница. Эти 12% сокращают количество перестановок в 1.8e19
раз. Это большое число ... И это коэффициент, который уменьшает его на ...
Основная проблема
Основная проблема заключается в том, что существует три типа паролей длиной более 72 символов. Воздействие, которое оказывает на них эта система стилей, будет самым разным:
Примечание: с этого момента я предполагаю, что мы сравниваем систему перца, которая использует SHA512
с необработанным выводом (не шестнадцатеричным).
Случайные пароли с высокой энтропией
Это ваши пользователи, использующие генераторы паролей, которые генерируют сколько угодно больших ключей для паролей. Они случайны (генерируются, а не выбираются человеком) и имеют высокую энтропию для каждого символа. Эти типы используют старшие байты (символы> 127) и некоторые управляющие символы.
Для этой группы ваша функция хеширования значительно уменьшит доступную энтропию до bcrypt
.
Позвольте мне сказать это еще раз. Для пользователей, которые используют длинные пароли с высокой энтропией, ваше решение значительно снижает надежность их паролей на ощутимую величину. (62 бита энтропии потеряны для пароля из 72 символов и больше для более длинных паролей)
Случайные пароли со средней энтропией
В этой группе используются пароли, содержащие общие символы, но без старших байтов или управляющих символов. Это ваши вводимые пароли.
Для этой группы вы собираетесь немного разблокировать больше энтропии (не создавать ее, но позволить большей энтропии соответствовать паролю bcrypt). Когда я говорю «слегка», я имею в виду «слегка». Безубыточность происходит, когда вы максимально используете 512 бит, которые имеет SHA512. Таким образом, пик составляет 78 символов.
Позвольте мне сказать это еще раз. Для этого класса паролей вы можете сохранить только 6 дополнительных символов, прежде чем у вас закончится энтропия.
Неслучайные пароли с низкой энтропией
Это группа, которая использует буквенно-цифровые символы, которые, вероятно, не генерируются случайным образом. Что-то вроде цитаты из Библии или чего-то подобного. Эти фразы имеют примерно 2,3 бита энтропии на символ.
Для этой группы вы можете значительно разблокировать больше энтропии (не создавать ее, но позволить большему количеству вписаться во ввод пароля bcrypt) с помощью хеширования. Безубыточность составляет около 223 символов, прежде чем у вас закончится энтропия.
Скажем еще раз. Для этого класса паролей предварительное хеширование определенно значительно повышает безопасность.
Назад в реальный мир
Подобные вычисления энтропии на самом деле не имеют большого значения в реальном мире. Важно угадать энтропию. Это напрямую влияет на то, что могут сделать злоумышленники. Это то, что вы хотите максимизировать.
Хотя есть небольшое исследование, посвященное угадыванию энтропии, есть некоторые моменты, на которые я хотел бы указать.
Шансы случайно угадать 72 правильных символа подряд чрезвычайно низки. У вас больше шансов выиграть лотерею Powerball 21 раз, чем попасть в эту коллизию ... Вот какое большое число мы говорим.
Но статистически мы не можем на это наткнуться. В случае фраз вероятность совпадения первых 72 символов намного выше, чем для случайного пароля. Но он все еще тривиально низкий (вы с большей вероятностью выиграете лотерею Powerball 5 раз, исходя из 2,3 бита на символ).
Практически
Практически это не имеет значения. Шансы на то, что кто-то правильно угадает первые 72 символа, а последние имеют существенное значение, настолько низки, что не стоит беспокоиться. Почему?
Ну, допустим, вы берете фразу. Если человек может правильно понять первые 72 символа, то ему либо действительно повезло (маловероятно), либо это обычная фраза. Если это обычная фраза, единственная переменная - как долго ее делать.
Возьмем пример. Возьмем цитату из Библии (просто потому, что это общий источник длинного текста, а не по какой-либо другой причине):
Не желай дома ближнего твоего. Не желай жены ближнего твоего, его слуги или служанки, его вола или осла или всего, что принадлежит твоему ближнему.
Это 180 символов. 73-й символ - это g
во втором neighbor's
. Если вы так много догадались, вы, вероятно, не остановитесь на nei
, а продолжите читать остальную часть стиха (поскольку именно так, вероятно, будет использоваться пароль). Таким образом, ваш "хеш" ничего не добавил.
BTW: Я АБСОЛЮТНО НЕ защищаю использование цитаты из Библии. На самом деле с точностью до наоборот.
Вывод
На самом деле вы не очень сильно поможете людям, использующим длинные пароли, путем хеширования. Некоторым группам вы определенно можете помочь. Некоторым вы определенно можете навредить.
Но, в конце концов, все это не имеет особого значения. Цифры, с которыми мы имеем дело, просто ПУТЬ завышены. Разница в энтропии не будет большой.
Вам лучше оставить bcrypt как есть. У вас больше шансов испортить хеширование (буквально, вы уже сделали это, и вы не первый и не последний, кто совершит эту ошибку), чем произойдет атака, которую вы пытаетесь предотвратить.
Сосредоточьтесь на обеспечении безопасности остальной части сайта. И добавьте измеритель энтропии пароля в поле пароля при регистрации, чтобы указать надежность пароля (и указать, является ли пароль слишком длинным, чтобы пользователь мог захотеть его изменить) ...
По крайней мере, это мои 0,02 доллара (или, возможно, больше, чем 0,02 доллара) ...
Что касается использования "секретного" перца:
Буквально нет исследований по загрузке одной хеш-функции в bcrypt. Таким образом, в лучшем случае неясно, приведет ли когда-либо подача «приправленного» хеша в bcrypt к неизвестным уязвимостям (мы знаем, что выполнение hash1(hash2($value))
может выявить значительные уязвимости, связанные с сопротивлением коллизиям и атаками с использованием прообраза).
Учитывая, что вы уже подумываете о хранении секретного ключа («перца»), почему бы не использовать его хорошо изученным и понятным способом? Почему бы не зашифровать хэш перед его сохранением?
По сути, после того, как вы хэшируете пароль, передайте весь хеш-вывод в надежный алгоритм шифрования. Затем сохраните зашифрованный результат.
Теперь атака SQL-инъекции не даст утечки ничего полезного, потому что у них нет ключа шифрования. И если ключ просочился, злоумышленникам ничуть не лучше, чем если бы вы использовали простой хеш (что доказуемо, чего-то с перцем "pre-hash" не дает).
Примечание: если вы решите это сделать, используйте библиотеку. Для PHP я настоятельно рекомендую Zend Framework 2 Zend\Crypt
. На самом деле это единственный вариант, который я бы порекомендовал на данный момент. Он прошел тщательную проверку и принимает все решения за вас (что очень хорошо) ...
Что-то типа:
use Zend\Crypt\BlockCipher;
public function createHash($password) {
$hash = password_hash($password, PASSWORD_BCRYPT, ["cost"=>$this->cost]);
$blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
$blockCipher->setKey($this->key);
return $blockCipher->encrypt($hash);
}
public function verifyHash($password, $hash) {
$blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
$blockCipher->setKey($this->key);
$hash = $blockCipher->decrypt($hash);
return password_verify($password, $hash);
}
И это полезно, потому что вы используете все алгоритмы хорошо понятными и хорошо изученными способами (по крайней мере, относительно). Помните:
Любой, от самого невежественного любителя до лучшего криптографа, может создать алгоритм, который сам не сможет сломать.
person
ircmaxell
schedule
16.05.2013