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

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

Ограничение в 72 символа

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

<?php
$password = "Wow. This is a super secret and super, super long password. Let's 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's add some special ch4r4ct3rs a#d everything is fine :)"
string(72) "Wow. This is a super secret and super, super long password. Let's add so"
bool(true)

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

Морская рыба и перец

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

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

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

Предложение

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

<?php
$pepper = "foIwUVmkKGrGucNJMOkxkvcQ79iPNzP5OKlbIdGPCMTjJcDYnR";

// Generate Hash
$password = "Wow. This is a super secret and super, super long password. Let's 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 вернуть false,

Вопрос

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

Pros

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

Cons

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


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

3 ответа

Решение

Проблема здесь в основном проблема энтропии. Итак, давайте начнем искать там:

Энтропия на персонажа

Количество битов энтропии на байт:

  • Шестнадцатеричные персонажи
    • Биты: 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, но продолжая с остальной частью стиха (так, как это, вероятно, будет использоваться пароль). Таким образом, ваш "хэш" не добавил много.

КСТАТИ: Я абсолютно не защищаю использование цитаты из Библии. На самом деле, с точностью до наоборот.

Заключение

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

Но в конце концов, ничего из этого не является слишком значительным. Числа, с которыми мы имеем дело, просто слишком высоки. Разница в энтропии не будет большой.

Вам лучше оставить bcrypt как есть. Вы, скорее всего, испортите хеширование (буквально, вы уже сделали это, и вы не первый или последний, кто совершит эту ошибку), чем атака, которую вы пытаетесь предотвратить, произойдет.

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

Это, по крайней мере, мои 0,02 доллара (или, возможно, более 0,02 доллара)...

Что касается использования "секретного" перца:

Буквально нет исследований по добавлению одной хэш-функции в bcrypt. Таким образом, в лучшем случае неясно, приведет ли когда-либо "зашитый" хеш к bcrypt, когда-нибудь возникнут неизвестные уязвимости (мы знаем, что делать hash1(hash2($value)) может выявить значительные уязвимости вокруг сопротивления столкновению и атак прообразу).

Учитывая, что вы уже рассматриваете возможность хранения секретного ключа ("перца"), почему бы не использовать его таким образом, который хорошо изучен и понят? Почему бы не зашифровать хеш до его хранения?

По сути, после того, как вы хешируете пароль, передайте весь вывод хеша в алгоритм надежного шифрования. Затем сохраните зашифрованный результат.

Теперь атака SQL-Injection не даст ничего полезного, потому что у них нет ключа шифрования. И если ключ пропущен, злоумышленники не в лучшем положении, чем если бы вы использовали простой хеш (что доказуемо, что-то с перечеркнутым "предварительным хешем" не дает).

Примечание: если вы решите сделать это, используйте библиотеку. Для PHP я настоятельно рекомендую Zend Framework 2's 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);
}

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

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

Переносить пароли, безусловно, хорошо, но давайте посмотрим, почему.

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

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

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам