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

91

На прошлой неделе я прочитал много статей о хешировании паролей, и на данный момент 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 символа. Twitter использует blowfish или bcrypt для хранения своих паролей ( https://shouldichangemypassword.com/twitter-hacked.php ) и угадайте, что: измените свой пароль Twitter на длинный пароль, содержащий более 72 символов, и вы можете войти в свою учетную запись, ввод только первых 72 символов.

Blowfish и перец

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

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

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

Предложение

Мое предложение получить хэш 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 символа пароля?

Плюсы

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

Минусы

  • Для построения хэша blowfish используются только 64 символа.


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

Фредерик Каммер
источник
3
Blowfish - не единственный, кто обрезает пароль, заставляя людей думать, что это безопаснее, чем есть на самом деле. Вот интересная история ограничения в 8 символов.
DOK
2
Является ли 72-символьное усечение фундаментальным для алгоритма Blowfish или просто реализация PHP? IIRC Blowfish также используется (по крайней мере, в некоторых) для шифрования паролей пользователей.
Дуглас Б. Стейпл
3
Проблема связана с Bcrypt, а не с Blowfish. Я могу воспроизвести эту проблему только с помощью Python и Bcrypt.
Blender
@Blender: Спасибо за ваш комментарий и вашу работу над ним. Мне не удалось найти в php разные функции для blowfish и bcrypt, хотя они одинаковы. Но разве для меня это не имеет значения на php? Я бы предпочел использовать стандартную функцию php.
Фредерик Каммер
1
Также см. Фреймворк хеширования паролей PHP Openwall (PHPass). Он портативен и защищен от ряда распространенных атак на пароли пользователей. Фреймворк (SolarDesigner) написал тот же парень, который написал John The Ripper и является судьей в конкурсе по хешированию паролей . Так что он кое-что знает об атаках на пароли.
jww

Ответы:

137

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

Энтропия на символ

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

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

Примечание: если вы решите это сделать, используйте библиотеку. Для PHP я настоятельно рекомендую Zend\Cryptпакет Zend Framework 2 . На самом деле это единственный вариант, который я бы рекомендовал на данный момент. Он прошел тщательную проверку и принимает все решения за вас (что очень хорошо) ...

Что-то типа:

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);
}

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

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

ircmaxell
источник
6
Большое спасибо за подробный ответ. Это мне очень помогает!
Фредерик Каммер
1
Мой комплимент за этот ответ. Одна небольшая придирка, это подавляющее большинство пользователей, которые используют очень слабые пароли, слова и производные, содержащиеся в словаре для взлома паролей, перец защитит их независимо от вопросов энтрофии. Чтобы избежать потери энтрофии, вы можете просто объединить пароль и перец. Однако ваше предложение о шифровании хеш-значения, вероятно, является лучшим решением для добавления секрета на стороне сервера.
martinstoeckli
2
@martinstoeckli: Моя проблема с концепцией перца не в его ценности. Дело в том, что применение «перца» выходит на неизведанную территорию с точки зрения криптографических алгоритмов. Это нехорошо. Вместо этого я считаю, что криптографические примитивы следует комбинировать таким образом, чтобы они были созданы для совместной работы. По сути, основная концепция перца звучит для меня в ушах, как некоторые люди, которые ничего не знали о криптографии, говорили: «Больше хешей - лучше! У нас есть соль, перец тоже хорош!» . Я просто предпочел бы иметь более простой, более проверенный и понятный impl
ircmaxell
@ircmaxell - Да, я знаю вашу точку зрения и согласен, если после этого хеш-значения будут зашифрованы. Если вы не сделаете этот дополнительный шаг, атака по словарю просто обнаружит слишком много слабых паролей, даже при наличии хорошего алгоритма хеширования.
martinstoeckli
1
@martinstoeckli: Я с этим не согласен. Хранение секретов - дело нетривиальное. Вместо этого, если вы используете bcrypt с хорошей стоимостью (12 сегодня), все пароли, кроме самого слабого, будут безопасными (словарь и простые пароли являются слабыми). Поэтому я предпочел бы рекомендовать человек сосредоточиться на обучение пользователя измерителных и заставить их использовать более сложные пароли в первой очереди ...
ircmaxell
5

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

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

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

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

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

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

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

Мартинстекли
источник
Что касается вашего PPS, я могу просто сказать: да, он может проверить усеченный пароль с хешем неусеченного и все равно получить true. Вот о чем весь этот вопрос. Посмотрите сами: viper-7.com/RLKFnB
Sliq 01
@Panique - Проблема не в вычислении хэша BCrypt, а в HMAC раньше. Для генерации хэша SHA OP использует полный пароль и использует результат в качестве входных данных для BCrypt. Для проверки он усекает пароль перед вычислением хэша SHA, а затем использует этот совершенно другой результат в качестве входных данных для BCrypt. HMAC принимает ввод любой длины.
martinstoeckli 01
2

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

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

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

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

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

Фил
источник
1
«Срок действия пароля также должен быть коротким, например, 2 недели» «очень длинного пароля», правда, зачем вообще сохранять пароль тогда, просто используйте сброс пароля каждый раз. Серьезно, это неправильное решение, переходить к двухфакторной аутентификации с помощью токена.
zaph
Спасибо @zaph. Можете ли вы указать мне на пример этого? Звучит интересно.
Фил
[ПРОЕКТ Специальной публикации NIST 800-63B Рекомендации по цифровой аутентификации] ( pages.nist.gov/800-63-3/sp800-63b.html ), 5.1.1.2. Запомненные верификаторы секретов : верификаторам НЕ СЛЕДУЕТ требовать произвольного изменения запомненных секретов (например, периодически) . Также см. Джим Фентон «Движение к лучшим требованиям к паролю ».
zaph
1
Дело в том, что чем чаще пользователю требуется изменить пароль, тем хуже становится выбор пароля, что снижает безопасность. У пользователя ограниченное количество хорошо запоминаемых паролей, и они заканчиваются, либо выбирая действительно плохие пароли, либо записывая их на
стикерах,