Сравнение паролей с crypt() в PHP

Мне нужно получить основы этой функции. В документации php.net для алгоритма blowfish указано, что:

Хеширование Blowfish с солью следующим образом: «$2a$», двухзначный параметр стоимости, «$» и 22 цифры с основанием 64 из алфавита «./0-9A-Za-z». Использование символов за пределами этого диапазона в соли приведет к тому, что crypt() вернет строку нулевой длины

Так что это по определению не должно работать:

echo crypt('rasmuslerdorf', '$2a$07$usesomadasdsadsadsadasdasdasdsadesillystringforsalt$');

Однако выплевывает:

$2a$07$usesomadasdsadsadsadaeMTUHlZEItvtV00u0.kb7qhDlC0Kou9e

Где кажется, что crypt() обрезает саму соль до длины 22. Может кто-нибудь объяснить это?

Другой аспект этой функции, который я не могу понять, это когда они используют crypt() для сравнения паролей. http://php.net/manual/en/function.crypt.php (см. упр. №1). Означает ли это, что если я использую одну и ту же соль для шифрования всех своих паролей, я должен сначала ее зашифровать? то есть:

$salt = "usesomadasdsadsadsadae";
$salt_crypt = crypt($salt);

if (crypt($user_input, $salt) == $password) {
   // FAIL WONT WORK
}

if (crypt($user_input, $salt_crypt) == $password) {
   // I HAVE TO DO THIS?
}    

Спасибо за ваше время


person soren.qvist    schedule 28.06.2010    source источник
comment
Я бессовестно натыкаюсь на это. Мой оставшийся вопрос — это мой ответ ZZ Coder, посмотрите внизу этого поста.   -  person soren.qvist    schedule 30.06.2010


Ответы (6)


Следующий пример кода может ответить на ваши вопросы.

Чтобы сгенерировать хешированный пароль с помощью Blowfish, вам сначала нужно сгенерировать соль, которая начинается с $2a$, за которой следует количество итераций и 22 символа строки Base64.

$salt = '$2a$07$usesomadasdsadsadsadasdasdasdsadesillystringfors';
$digest = crypt('rasmuslerdorf', $salt);

Сохраните весь $digest в базе данных, в нем есть и соль, и дайджест.

При сравнении пароля просто сделайте это,

  if (crypt($user_input, $digest) == $digest)

Вы повторно используете дайджест как соль. crypt знает, какой длины соль из идентификатора алгоритма.

person ZZ Coder    schedule 28.06.2010
comment
Да вот так и надо делать. И этот $salt должен генерироваться случайным образом каждый раз, когда вы устанавливаете пароль. - person caf; 29.06.2010
comment
Ааа, понятно, я думал, что должен хранить в БД только хэш. Но хранение соли вместе с хэшем в базе данных отменяет назначение соли, не так ли? - person soren.qvist; 29.06.2010
comment
@soren.qvist: Не совсем, нет. Даже если злоумышленник знает соль, ему все равно придется пересчитывать хэш для каждого возможного пароля с этой солью; недостаточно вычислить его один раз, а затем просто найти хэш в результатах. Еще лучше, если соль неизвестна, потому что тогда ему придется перебирать все пароли для каждой возможной соли, но даже если соль для каждого пользователя хранится вместе с каждым паролем, все равно потребуется повторное вычисление соли для этого пользователя. См. также en.wikipedia.org/wiki/Salt_%28cryptography%29. - person Michael Madsen; 30.06.2010
comment
@Майкл Мэдсен, спасибо за ответ, не могли бы вы порекомендовать случайно сгенерированную соль для каждой записи пароля в БД? - person soren.qvist; 30.06.2010
comment
@soren.qvist: Вам определенно следует использовать разные соли для каждой записи. Случайно это или нет - я не уверен, что есть большая разница. Даже если соль предсказуема, вам все равно нужно запустить хэш для каждой соли. То же самое для создания новой соли при смене пароля; вам все еще нужно рассчитать все для этой соли. Это не позволяет кому-то нацеливаться на этого конкретного пользователя, предварительно вычисляя хэши, а затем повторно используя их, но это все. Тем не менее, было бы вредно иметь случайную соль при каждом изменении, так что вы можете сделать это. - person Michael Madsen; 30.06.2010
comment
Проверьте gensalt_blowfish() из Wordpress: cvsweb. openwall.com/cgi/cvsweb.cgi/projects/phpass/ - person ZZ Coder; 30.06.2010

Новая соль для каждого пароля

$password = 'p@ssw0rd';

$salt = uniqid('', true);
$algo = '6'; // CRYPT_SHA512
$rounds = '5042';
$cryptSalt = '$'.$algo.'$rounds='.$rounds.'$'.$salt;

$hashedPassword = crypt($password, $cryptSalt);
// Store complete $hashedPassword in DB

echo "<hr>$password<hr>$algo<hr>$rounds<hr>$cryptSalt<hr>$hashedPassword";

Аутентификация

if (crypt($passwordFromPost, $hashedPasswordInDb) == $hashedPasswordInDb) {
    // Authenticated
person Brock Hensley    schedule 27.03.2013

Цитата из мануала

CRYPT_BLOWFISH - хеширование Blowfish с солью следующим образом: "$2a$", двухзначный параметр стоимости, "$" и 22 цифры из 64-х символов алфавита.

Примечание: 22 цифры с основанием 64.

person Mark Baker    schedule 28.06.2010
comment
Английский не мой родной язык, так что, возможно, я совершенно неправильно понял это :) Я не использую 22 символа в своем примере, но в любом случае это нормально для PHP? - person soren.qvist; 28.06.2010
comment
PHP не возражает против ›22 символов в соли; он возражает против ‹22. Однако используются только первые 22 символа. идентичный - person Mark Baker; 28.06.2010

BCrypt использует 128 бит для соли, то есть 22 байта Base64, причем используются только два бита последнего байта.

Хэш вычисляется с использованием соли и пароля. Когда вы передаете зашифрованный пароль, алгоритм считывает силу, соль (игнорируя все остальное) и пароль, который вы дали, и вычисляет хэш, добавляя его. Если у вас есть PostgreSQL и pg_crypto, SELECT gen_salt('bf'); покажет вам, что из $salt читается.

Вот пример кода для генерации соли из моей реализации .NET test-vector- gen.php, альтернативно:

$salt = sprintf('$2a$%02d$%s', [strength goes here],
    strtr(str_replace(
    '=', '', base64_encode(openssl_random_pseudo_bytes(16))
    ),
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
    './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'));

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

person Zer    schedule 22.01.2011

Первый вопрос:

Так что это по определению не должно работать:

echo crypt('rasmuslerdorf', '$2a$07$usesomadasdsadsadsadasdasdasdsadesillystringforsalt$');

Где кажется, что crypt() обрезает саму соль до длины 22. Может кто-нибудь объяснить это?

Нет проблем с наличием слишком большого количества символов... фраза Использование символов за пределами этого диапазона в соли приведет к тому, что crypt() вернет строку нулевой длины, относится к вне диапазона base 64, а не диапазон 22 символов. Попробуйте поместить недопустимый символ в строку соли, и вы обнаружите, что вы получите пустой вывод (или, если вы введете ‹ 22 символов, это приведет к недопустимым пустым байтам).

Второй вопрос:

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

person G__    schedule 28.06.2010
comment
Итак, я храню свой зашифрованный пароль в базе данных, что-то вроде «MTUHlZEItvtV00u0.kb7qhDlC0Kou9e», а затем использую его как соль? Но я использовал другую соль для создания этого шифрования, не так ли? - person soren.qvist; 29.06.2010
comment
Правильно, вы использовали другую соль (или соль, сгенерированную по умолчанию) для создания первого шифрования. Получается, что из-за некоторых (я думаю, тщательно разработанных) особенностей алгоритмов шифрования вы можете затем использовать эту зашифрованную строку в качестве соли для другого шифрования, и вы получаете эффект использования той же самой соли. - person G__; 29.06.2010

Этот вопрос связан с моим ответом на ответ ZZ Coder. В основном мой вопрос касается хранения результата crypt() в базе данных. Должен ли я хранить весь вывод в базе данных, чтобы моя база данных выглядела так:

--------------------------------------------------------------------------------
| ID | Username |                          Password                            |
--------------------------------------------------------------------------------
| 32 | testuser | $2a$07$usesomadasdsadsadsadaeMTUHlZEItvtV00u0.kb7qhDlC0Kou9e |
--------------------------------------------------------------------------------

Если да, то разве это не противоречит цели использования соли? Если кто-то получит доступ к базе данных, он сможет ясно увидеть соль, используемую для шифрования?

Бонусный вопрос: безопасно ли использовать одну и ту же соль для каждого пароля?

person soren.qvist    schedule 29.06.2010
comment
Во-первых, вы хешируете, а не шифруете. Во-вторых, используя разные соли для каждого пользователя, вы защищаете себя от радужных таблиц. Тот факт, что соли хранятся в базе данных, не сильно помогает злоумышленнику — вы не можете угадать пароль по соли (для хороших алгоритмов хеширования). - person Greg; 30.06.2010
comment
Итак, да, я должен хранить пароль таким образом? Я думал, что хэш был результатом шифрования, но, возможно, я ошибся в терминологии. - person soren.qvist; 30.06.2010