Ответ 1
Проблема здесь в основном связана с проблемой энтропии. Итак, начните искать:
Энтропия на символ
Число бит энтропии на каждый байт:
- Шестигранные символы
- Биты: 4
- Значения: 16
- Энтропия в 72 символах: 288 бит
- Алфавитно-цифровой
- Биты: 6
- Значения: 62
- Энтропия в 72 символах: 432 бит
- "Общие" символы
- Биты: 6.5
- Значения: 94
- Энтропия в 72 символах: 468 бит
- Полный байт
- Биты: 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: Я абсолютно НЕ сторонник использования цитаты из Библии. Фактически, полная противоположность.
Заключение
Ты не собираешься помогать людям, которые используют длинные пароли, сначала хешируя. Некоторые группы вы можете определенно помочь. Некоторым можно определенно повредить.
Но в конце концов, ничто из этого не является чрезмерно значительным. Числа, с которыми мы имеем дело, - это просто WAY слишком высоко. Разница в энтропии не будет большой.
Вам лучше оставить bcrypt так, как есть. Вы, скорее всего, испортите хеширование (в буквальном смысле вы уже это сделали, и вы не первый, или последний, чтобы сделать эту ошибку), чем произойдет нападение, которое вы пытаетесь предотвратить.
Сосредоточьтесь на защите остальной части сайта. И добавьте счетчик энтропии пароля в поле пароля при регистрации, чтобы указать силу пароля (и укажите, не перекрыт ли пароль, который пользователь может пожелать изменить)...
Что мои $0,02 по крайней мере (или, возможно, больше, чем $0,02)...
До тех пор, как использовать "секретный" перец:
В буквальном смысле нет исследований по подаче одной хэш-функции в bcrypt. Таким образом, неясно, в лучшем случае, если подача "переусерченного" хэша в bcrypt будет когда-либо вызывать неизвестные уязвимости (мы знаем, что hash1(hash2($value))
может выявить значительные уязвимости в отношении устойчивости к столкновению и атак с прообразом).
Учитывая, что вы уже собираетесь хранить секретный ключ ( "перец" ), почему бы не использовать его так, чтобы он хорошо изучался и понимался? Почему бы не зашифровать хэш до его хранения?
В принципе, после того, как вы сделаете хэш-пароль, подайте весь хэш-вывод в сильный алгоритм шифрования. Затем сохраните зашифрованный результат.
Теперь атака SQL-Injection не будет пропускать ничего полезного, потому что у них нет ключа шифрования. И если ключ просочился, злоумышленники не лучше, чем если бы вы использовали простой хеш (что доказуемо, что-то с пре-хэшем перечня не дает).
Примечание. Если вы решите это сделать, используйте библиотеку. Для 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);
}
И это полезно, потому что вы используете все алгоритмы способами, которые хорошо поняты и хорошо изучены (относительно по крайней мере). Помните:
Любой, от самого невежественного любителя до лучшего криптографа, может создать алгоритм, который он сам не сможет сломать.