Понимание соли bcrypt как используется PHP password_hash
У меня есть некоторые проблемы, чтобы понять, как bcrypt использует соль. Я знаю, для чего хороша соль, но я не понимаю, как именно используется значение соли.
Проблема 1: Какова правильная длина соли?
Все источники, которые я нашел, говорят, что соль имеет длину 22 и что она хранится вместе с алгоритмом, затратами и фактическим значением хеш-функции в строке результата.
Тем не менее, все реализации, которые я нашел, используют соль длиной 32. Например, FOSUserBundle
использован Symfony
использовал следующий код для создания соли:
$this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36)
Так как sha1
хэш длиной 32 символа, сгенерированная соль также имеет длину 32. Является ли это просто отложенной реализацией, пропускающей код для обрезки строки до длины 22, потому что это делается bcrypt
сам? Или 32 символа необходимы по какой-то причине?
Проблема 2: действительно ли длина соли 22 действительно правильна?
В следующем примере кажется, что только первые 21 символ соли сохраняются в строке результата. Передав эти 21 символ в виде соли password_hash
приведет к ошибке, но заполнение 0 будет работать:
$s = 'password';
$salt = 'salt5678901234567890123456789012';
$salt_prefix = 'salt567890123456789010'; // first 21 chars of salt + 0
$h1 = password_hash($s, PASSWORD_BCRYPT, array('salt' => $salt));
$h2 = password_hash($s, PASSWORD_BCRYPT, array('salt' => $salt_prefix));
echo $h1 . PHP_EOL;
echo $h2 . PHP_EOL;
//Result
$2y$10$salt56789012345678901uTWNlUnhu5K/xBrtKYTo7oDy8zMr/csu
$2y$10$salt56789012345678901uTWNlUnhu5K/xBrtKYTo7oDy8zMr/csu
Таким образом, нужно передать соль с минимум 22 символами алгоритму, но 22-й символ кажется бесполезным. Это верно? Какой смысл 22-го символа, если он вообще не используется?
Проблема 3: Почему бы не указать соль вручную?
В функции PHP password_hash
использование ручного хэша не рекомендуется. Вместо этого рекомендуется password_hash
автоматически, так как будет безопаснее.
Я понимаю, что использование "слабой" соли или одной и той же соли для всех паролей может привести к рискам из-за радужных таблиц. Но почему безопаснее использовать автоматически генерируемую соль?
Почему безопаснее использовать автоматически генерируемую соль вместо ручной соли, которая генерируется так:
$this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36)
Проблема 4: есть ли замена password_hash
что все еще позволяет использовать кастомную соль?
В связи с реализацией проекта, над которым я работаю, мне нужно контролировать соль, которая используется для генерации хэша пароля. Это может быть изменено в будущем, но правильно знать, что необходимо установить соль вручную. Поскольку эта функция устарела в password_hash
Мне нужна альтернатива для генерации хеша. Как это сделать?
РЕДАКТИРОВАТЬ:
Просто краткое объяснение, почему мне нужно контролировать соль: пароль используется не только для входа в веб-приложение напрямую, но и для подключения к приложению через REST API. Клиент запрашивает соль с сервера и использует ее (алгоритм и стоимость известны) для хеширования пароля, введенного пользователем на стороне клиента.
Хешированный пароль затем отправляется обратно на сервер для аутентификации. Цель состоит в том, чтобы не отправлять пароль в виде обычного текста. Чтобы иметь возможность генерировать тот же хеш на клиенте, что и на сервере, клиент должен знать, какую соль использует сервер.
Я знаю, что хешированный пароль не добавляет никакой реальной безопасности, так как для связи уже используется только HTTPS. Однако так в настоящее время работают клиенты: аутентификация предоставляется, если клиент отправляет обратно правильный хэш пароля.
Я не могу изменить сторону сервера, не сломав тысячи существующих клиентов. Клиенты могут быть обновлены когда-нибудь в будущем, но это будет долгий процесс.
Поскольку это сделано, мне нужно следовать старому процессу, что означает, что я должен быть в состоянии сказать клиентам соль.
Однако мне не нужно генерировать соль самостоятельно. Я полностью в порядке, если PHP знает самый безопасный способ, как это сделать. Но мне нужно как-то получить / извлечь соль, чтобы отправить ее клиентам.
Если бы я все правильно понял, я мог бы просто позволить password_hash
выполнить работу, а затем извлечь символы 7-29 из строки результата. Это правильно?
2 ответа
Проблема 1: Какова правильная длина соли?
Все источники, которые я нашел, говорят, что соль имеет длину 22 и что она хранится вместе с алгоритмом, затратами и фактическим значением хеш-функции в строке результата.
Если все источники говорят это, у вас не должно быть оснований сомневаться в этом...
Там нет универсального размера соли, это зависит от алгоритма и для bcrypt, это 22 ... хотя есть загвоздка. Необходимый размер на самом деле составляет 16 байтов, но на самом деле это кодировка Base64 (*).
Когда вы кодируете Base64 16 байтов данных, это приведет к получению строки ASCII длиной 24 символа, причем последние 2 символа не будут иметь значения - это станет 22, когда вы обрежете эти 2 ненужных символа.
Почему они не имеют значения? Ваш вопрос уже достаточно широк... прочитайте страницу Википедии для Base64.
* На самом деле есть несколько Base64 "диалектов", и тот, который используется bcrypt, не совсем такой же, как PHP base64_encode()
,
Тем не менее, все реализации, которые я нашел, используют соль длиной 32. Например,
FOSUserBundle
использованSymfony
использовал следующий код для создания соли:
$this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36)
Так как
sha1
хэш длиной 32 символа, сгенерированная соль также имеет длину 32. Это просто отложенная реализация, пропускающая код для обрезки строки до длины 22, потому что это делается bcrypt самостоятельно? Или 32 символа необходимы по какой-то причине?
Эта строка будет содержать строку из 31 символа, а не 32, но это на самом деле не имеет значения. Если вы предоставите более длинную строку, будет использована только необходимая ее часть - эти последние символы будут игнорироваться.
Вы можете проверить это самостоятельно:
php > var_dump(password_hash('foo', PASSWORD_DEFAULT, ['salt' => str_repeat('a', 22).'b']));
string(60) "$2y$10$aaaaaaaaaaaaaaaaaaaaaO8Q0BjhyjLkn5wwHyGGWhEnrex6ji3Qm"
php > var_dump(password_hash('foo', PASSWORD_DEFAULT, ['salt' => str_repeat('a', 22).'c']));
string(60) "$2y$10$aaaaaaaaaaaaaaaaaaaaaO8Q0BjhyjLkn5wwHyGGWhEnrex6ji3Qm"
php > var_dump(password_hash('foo', PASSWORD_DEFAULT, ['salt' => str_repeat('a', 22).'d']));
string(60) "$2y$10$aaaaaaaaaaaaaaaaaaaaaO8Q0BjhyjLkn5wwHyGGWhEnrex6ji3Qm"
(если использовать дополнительные символы, результирующие хэши будут отличаться)
Я не знаком с этим FOSUserBundle, но да - похоже, он просто делает что-то ленивое и неправильное.
Проблема 2: действительно ли длина соли 22 действительно правильна?
В следующем примере кажется, что только первые 21 символ соли сохраняются в строке результата. Передача этих 21 символа как соли в password_hash приведет к ошибке, но заполнение 0 будет работать:
$ s = 'пароль'; $salt = 'salt5678901234567890123456789012'; $salt_prefix = 'salt567890123456789010'; // первые 21 символ соли + 0 $h1 = password_hash($s, PASSWORD_BCRYPT, массив ('salt' => $salt)); $h2 = password_hash($s, PASSWORD_BCRYPT, массив ('salt' => $salt_prefix)); эхо $h1 . PHP_EOL; эхо $h2 . PHP_EOL; //Результат $2y$10$salt56789012345678901uTWNlUnhu5K/xBrtKYTo7oDy8zMr/ CSU $2y$10$salt56789012345678901uTWNlUnhu5K/xBrtKYTo7oDy8zMr/ CSUТаким образом, нужно передать соль с минимум 22 символами алгоритму, но 22-й символ кажется бесполезным. Это верно? Какой смысл 22-го символа, если он вообще не используется?
Это на самом деле не имеет значения... добавьте, например, "А", и вы увидите другой результат.
Я не могу объяснить это, если честно, но это опять-таки вызвано тем, как работает Base64, и потому что в полученном хэше вы фактически видите нечто похожее на это (псевдокод):
base64_encode( base64_decode($salt) . $actualHashInBinary )
Таким образом, (предположительно) соль, закодированная в Base64, сначала декодируется в необработанный двоичный файл, используется для создания фактического хэша (снова в необработанном двоичном коде), эти два соединяются, а затем все это кодируется в Base64.
Поскольку входная соль фактически является 22 релевантной из полной длины 24 размера, у нас фактически есть неполный блок в конце, который завершается (заполняется?) К началу необработанного хэша...
Другое дело объединить 2 отдельных значения в кодировке Base64 и объединить необработанные значения перед их кодированием в Base64.
Проблема 3: Почему бы не указать соль вручную?
В PHP-функции password_hash использование ручного хеширования не рекомендуется. Вместо этого рекомендуется автоматически разрешать password_hash, так как это будет заставкой.
Я понимаю, что использование "слабой" соли или одной и той же соли для всех паролей может привести к рискам из-за радужных таблиц. Но почему безопаснее использовать автоматически генерируемую соль?
Проще говоря - соль должна быть криптографически безопасной (то есть непредсказуемой), и PHP уже знает, как это сделать, в то время как есть вероятность (в подавляющем большинстве случаев), что вы этого не делаете.
Если у вас нет реального аппаратного CSPRNG (который PHP еще не настроен для использования), лучшее, что вы можете сделать, - это оставить PHP для автоматического генерирования соли в любом случае. Тем не менее, здесь мы, вы, очевидно, хотите сделать обратное (по любой причине) и сделать его менее безопасным в процессе - многие люди делают это.
Вот почему солевой вариант устарел - чтобы защитить себя от себя.:)
Почему безопаснее использовать автоматически генерируемую соль вместо ручной соли, которая генерируется следующим образом:
$this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36)
Как я уже сказал, соль должна быть непредсказуемой. В этом конкретном примере - ни одна из используемых функций непредсказуема, даже mt_rand()
,
Да, mt_rand()
на самом деле не случайно, несмотря на то, что подразумевает его название.
Проблема 4: есть ли замена для password_hash, которая все еще позволяет использовать пользовательскую соль?
В связи с реализацией проекта, над которым я работаю, мне нужно контролировать соль, которая используется для генерации хэша пароля. Это может быть изменено в будущем, но правильно знать, что необходимо установить соль вручную. Поскольку эта функция устарела в
password_hash
Мне нужна альтернатива для генерации хеша. Как это сделать?
Вы не
У вашего проекта абсолютно нулевая причина диктовать, как password_hash()
соль генерируется. Я не знаю, почему вы считаете это необходимым, но на 100% это не так - это не имеет смысла.
Хотя, в конечном счете, именно поэтому амортизация вводится до того, как что-то удаляется. Теперь вы знаете, что опция соли будет удалена в будущем, и у вас будет достаточно времени для рефакторинга вашего приложения.
Используйте это с умом, не пытайтесь копировать устаревшую функциональность. Вы должны работать в противоположном направлении - спросите, как разделить их, не нарушая приложение.
Вы можете использовать крипту с иглобрюхом. Он по-прежнему принимает пользовательскую соль в 2023 году. Не рекомендуется использовать ту же соль для пароля, но для идентификаторов, например адресов электронной почты, это лучше, чем ничего или алгоритм контрольной суммы.