Как хэш длинные пароли (> 72 символа) с blowfish

На прошлой неделе я прочитал много статей о хэшировании паролей, и Blowfish кажется (одним из) лучшим алгоритмом хэширования прямо сейчас, но это не тема этого вопроса!

Предел 72 символов

Blowfish рассматривает только первые 72 символа введенного пароля:

<?php
$password = "Wow. This is a super secret and super, super long password. Let add some special ch4r4ct3rs a#d everything is fine :)";
$hash = password_hash($password, PASSWORD_BCRYPT);
var_dump($password);

$input = substr($password, 0, 72);
var_dump($input);

var_dump(password_verify($input, $hash));
?>

Вывод:

string(119) "Wow. This is a super secret and super, super long password. Let add some special ch4r4ct3rs a#d everything is fine :)"
string(72) "Wow. This is a super secret and super, super long password. Let add so"
bool(true)

Как вы можете видеть только первые 72 символа. Twitter использует blowfish aka bcrypt для хранения своих паролей (https://shouldichangemypassword.com/twitter-hacked.php) и угадайте, что: измените свой пароль Twitter на длинный пароль с более чем 72 символов, и вы можете войти в свою учетную запись, введя только первые 72 символа.

Blowfish and Pepper

Есть много разных мнений о "переполненных" паролях. Некоторые люди говорят, что это не нужно, потому что вы должны предположить, что секретная перечная строка также известна/опубликована, поэтому она не улучшает хэш. У меня есть отдельный сервер базы данных, поэтому вполне возможно, что просочилась только база данных, а не постоянный перец.

В этом случае (перец не просочился) вы делаете атаку на основе словаря более сложной (исправьте меня, если это неверно). Если ваша перечная струна также просочилась: не так уж плохо - у вас все еще есть соль, и она защищена как хэш без перца.

Таким образом, я думаю, что надуть пароль - это, по крайней мере, плохой выбор.

Предложение

Мое предложение получить хеш Blowfish для пароля с более чем 72 символами (и перцем):

<?php
$pepper = "foIwUVmkKGrGucNJMOkxkvcQ79iPNzP5OKlbIdGPCMTjJcDYnR";

// Generate Hash
$password = "Wow. This is a super secret and super, super long password. Let add some special ch4r4ct3rs a#d everything is fine :)";
$password_peppered = hash_hmac('sha256', $password, $pepper);
$hash = password_hash($password_peppered, PASSWORD_BCRYPT);

// Check
$input = substr($password, 0, 72);
$input_peppered = hash_hmac('sha256', $input, $pepper);

var_dump(password_verify($input_peppered, $hash));
?>

Это основано на этом вопросе: password_verify return false.

Вопрос

Чем безопаснее? Сначала получить хэш SHA-256 (который возвращает 64 символа) или рассмотреть только первые 72 символа пароля?

Pros

  • Пользователь не может войти, введя только первые 72 символа
  • Вы можете добавить перец без превышения лимита символов
  • Выход hash_hmac, вероятно, будет иметь больше энтропии, чем сам пароль
  • Пароль хешируется двумя различными функциями

Против

  • Только 64 символа используются для создания хэша blowfish


Изменить 1: Этот вопрос касается только интеграции PHP blowfish/bcrypt. Спасибо за комментарии!

Ответ 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);
}

И это полезно, потому что вы используете все алгоритмы способами, которые хорошо поняты и хорошо изучены (относительно по крайней мере). Помните:

Любой, от самого невежественного любителя до лучшего криптографа, может создать алгоритм, который он сам не сможет сломать.

Ответ 2

Погружающиеся пароли - это, безусловно, хорошая вещь, но почему бы не понять почему.

Сначала мы должны ответить на вопрос, когда именно перец помогает. Перец защищает пароли только до тех пор, пока он остается в секрете, поэтому, если злоумышленник имеет доступ к самому серверу, это бесполезно. Более легкая атака, хотя и является SQL-инъекцией, которая позволяет читать доступ к базе данных (нашим хэш-значениям), я подготовил демо SQL-инъекции, чтобы показать, насколько легко это может быть (щелкните следующую стрелку, чтобы получить подготовленный ввод).

Тогда что действительно помогает перцу? Пока перец остается в секрете, он защищает слабые пароли от атаки словаря. Пароль 1234 стал бы чем-то вроде 1234-p*deDIUZeRweretWy+.O. Этот пароль не только намного длиннее, он также содержит специальные символы и никогда не будет частью какого-либо словаря.

Теперь мы можем оценить, какие пароли будут использовать наши пользователи, возможно, больше пользователей будут вводить слабые пароли, так как есть пользователи с паролями между 64-72 символами (на самом деле это будет очень редко).

Еще один момент - это диапазон для принудительного принуждения. Хэш-функция sha256 будет возвращать 256-битные выходные или 1,2E77-комбинации, что слишком много для принудительного вытеснения, даже для графического процессора (если я правильно вычислил, это потребует 2E61 года на графическом процессоре в 2013 году). Таким образом, мы не получаем настоящего недостатка, применяя перец. Поскольку значения хэша не являются систематическими, вы не можете ускорить грубое форсирование с помощью общих паттернов.

P.S. Насколько я знаю, ограничение 72 символов специфично для алгоритма самого BCrypt. Лучший ответ, который я нашел, this.

P.P.S Я думаю, что ваш пример ошибочен, вы не можете сгенерировать хэш с полной длиной пароля и проверить его с усеченным. Вы, вероятно, хотели применить перец таким же образом для генерации хэша и для проверки хеша.

Ответ 3

Bcrypt использует алгоритм, основанный на дорогостоящем алгоритме настройки ключа Blowfish.

Рекомендуемый префикс пароля 56 байтов (включая пустой байт завершения) для bcrypt относится к пределу 448 бит ключа Blowfish. Любые байты, превышающие этот предел, не полностью смешиваются с полученным хешем. Таким образом, абсолютный предел в байтах с байтом в 72 байта является менее актуальным, если вы рассматриваете фактическое влияние на полученный хэш этими байтами.

Если вы считаете, что ваши пользователи обычно выбирают пароли длиной более 55 байт, помните, что вы всегда можете увеличить раунды растяжения паролей, чтобы повысить безопасность в случае нарушения таблицы паролей (хотя это должно быть много по сравнению с добавлением дополнительных символов). Если права доступа пользователей настолько важны, что пользователям обычно требуется массовый длинный пароль, то срок действия пароля также должен быть коротким, например, 2 недели. Это означает, что пароль гораздо менее вероятен, чтобы оставаться в силе, в то время как хакер инвестирует свои ресурсы в разгром рабочего фактора, связанного с тестированием каждого пробного пароля, чтобы увидеть, приведет ли он к соответствующему хешу.

Конечно, в случае нарушения таблицы паролей мы должны разрешать хакерам, в лучшем случае, десять попыток угадать пароль пользователя 55 байтов, прежде чем блокировать учетную запись пользователя;)

Если вы решите предварительно ввести пароль, длина которого превышает 55 байт, тогда вы должны использовать SHA-384, так как он имеет наибольший выход без перехода за пределы.