bcrypt и случайно сгенерированные соли

Итак, я экспериментировал с bcrypt. У меня есть класс (показанный ниже, который я получил от http://www.firedartstudios.com/articles/read/php-security-how-to-safely-store-your-passwords), в котором есть 3 функции. 1-й - для генерации случайной соли, 2-й - для генерации хэша с использованием 1-й сгенерированной соли, а последний - для проверки предоставленного пароля путем сравнения его с хешированным паролем.

<?php
/* Bcrypt Example */
class bcrypt {
    private $rounds;
    public function __construct($rounds = 12) {
        if(CRYPT_BLOWFISH != 1) {
            throw new Exception("Bcrypt is not supported on this server, please see the following to learn more: http://php.net/crypt");
        }
        $this->rounds = $rounds;
    }

    /* Gen Salt */
    public function genSalt() {
        /* openssl_random_pseudo_bytes(16) Fallback */
        $seed = '';
        for($i = 0; $i < 16; $i++) {
            $seed .= chr(mt_rand(0, 255));
        }
        /* GenSalt */
        $salt = substr(strtr(base64_encode($seed), '+', '.'), 0, 22);
        /* Return */
        return $salt;
    }

    /* Gen Hash */
    public function genHash($password) {
        /* Explain '$2y$' . $this->rounds . '$' */
            /* 2a selects bcrypt algorithm */
            /* $this->rounds is the workload factor */
        /* GenHash */
        $hash = crypt($password, '$2y$' . $this->rounds . '$' . $this->genSalt());
        /* Return */
        return $hash;
    }

    /* Verify Password */
    public function verify($password, $existingHash) {
        /* Hash new password with old hash */
        $hash = crypt($password, $existingHash);

        /* Do Hashs match? */
        if($hash === $existingHash) {
            return true;
        } else {
            return false;
        }
    }
}
/* Next the Usage */
/* Start Instance */
$bcrypt = new bcrypt(12);

/* Two create a Hash you do */
echo 'Bcrypt Password: ' . $bcrypt->genHash('password');

/* Two verify a hash you do */
$HashFromDB = $bcrypt->genHash('password'); /* This is an example you would draw the hash from your db */
echo 'Verify Password: ' . $bcrypt->verify('password', $HashFromDB);
?>

Теперь, если я сгенерирую хеш, например, с «паролем», я получу хешированный пароль, который взял случайно сгенерированную соль. Затем, если я снова ввожу «пароль» и использую функцию проверки, я получаю истину, что означает совпадение паролей. Если я введу неправильный пароль, я получу неверный. У меня вопрос, как это возможно? А как насчет случайно сгенерированной соли? Почему это не имеет никакого эффекта?


person Sameer Zahid    schedule 24.05.2013    source источник
comment
PHP v5.5 (который скоро выйдет) будет иметь набор password_xxx() функций, которые устранят необходимость в любом из этого кода. Вы также можете загрузить библиотеку обратной совместимости, которая реализует функции паролей PHP 5.5 в 5.3 или 5.4. Возможно, вы захотите попробовать; это сэкономит вам много работы. См. github.com/ircmaxell/password_compat.   -  person Spudley    schedule 24.05.2013


Ответы (1)


Внимательно посмотрите на ценности, с которыми вы имеете дело. Сгенерированная случайная соль будет, скажем,:

abcdefg...

То, что подается в crypt, выглядит так:

crypt($password, '$2y$10$abcdefg...')
                   |  |    |
                   |  |    +- the salt
                   |  +- the cost parameter
                   +- the algorithm type

Результат выглядит так:

$2y$10$abcdefg...123456789...
 |  |    |        |
 |  |    |        +- the password hash
 |  |    +- the salt
 |  +- the cost parameter
 +- the algorithm type

Другими словами, первая часть результирующего хэша такая же, как и исходный ввод в функцию crypt; он содержит тип и параметры алгоритма, случайную соль и результат хеширования.

Input:  $password + $2y$10$abcdefg...
Output:             $2y$10$abcdefg...123456789...
                    ^^^^^^^^^^^^^^^^^
                   first part identical

При подтверждении пароля вам снова понадобится та же оригинальная соль. Только с той же солью один и тот же хеш-пароль будет совпадать с одним и тем же хешем. И он все еще присутствует в хэше в формате, который можно передать в crypt, чтобы повторить ту же операцию, что и при сгенерировании хэша. Вот почему вам нужно передать и пароль, и хеш в функцию проверки:

crypt($passwordToCheck, '$2y$10$abcdefg...123456789...')

crypt берет первое определенное количество символов, до abcdefg... включительно, а остальные отбрасывает (поэтому соль должна быть фиксированным количеством символов). Следовательно, это та же операция, что и раньше:

crypt($passwordToCheck, '$2y$10$abcdefg...')

И будет генерировать один и тот же хеш, тогда и только тогда, когда $passwordToCheck совпадает.

person deceze♦    schedule 24.05.2013
comment
Я понимаю, что вам снова понадобится та же соль, и в руководстве, которому я следовал, предлагается сохранить эту сгенерированную соль в таблице вместе с именем пользователя и паролем. НО, я попробовал это без использования оригинальной сгенерированной соли, и она все еще работает. Я пробовал это несколько раз, и он работает каждый раз. - person Sameer Zahid; 24.05.2013
comment
Да, потому что соль является частью сгенерированного хэша в виде открытого текста. Вам не нужно хранить его отдельно, это часть хэша. Первая часть хеша точно такая же, как параметр, необходимый для crypt, поэтому вы просто передаете его обратно в crypt. - person deceze♦; 24.05.2013
comment
Так вы говорите, что bcrypt проверяет только ту часть хэша, которая была создана из исходной строки пароля, и игнорирует Salt при проверке? - person Sameer Zahid; 24.05.2013
comment
Нет, наоборот. Прочтите мое обновление. Он считывает части алгоритма, стоимости и соли, как и раньше, и игнорирует добавленный хеш. - person deceze♦; 24.05.2013
comment
Итак, суть в том, что мне просто нужно передать простой простой введенный пароль от пользователя в функцию проверки, не нужно добавлять к нему соль или хранить сгенерированную соль где-нибудь в таблице, когда я хеширую исходный пароль, скажем при регистрации пользователя? - person Sameer Zahid; 24.05.2013
comment
Когда пользователь регистрируется, вы генерируете соль и используете ее при хешировании пароля. Результатом является строка, содержащая всю информацию, необходимую для воспроизведения того же результата; вы просто сохраняете эту строку в базе данных. Вы больше не беспокоитесь о соли, потому что соль находится в этой строке. При подтверждении пароля вы просто берете эту строку из базы данных и снова используете ее в операции хеширования с вновь введенным паролем. Вы никогда больше не создадите новую соль для этого конкретного пароля. - person deceze♦; 24.05.2013
comment
Таким образом, да, я полагаю, что то, что вы сказали, правильно, хотя и выражено в обратном направлении. - person deceze♦; 24.05.2013
comment
Большое вам спасибо, это было для меня загадкой, пока я не получил от вас это замечательное объяснение. Теперь я понимаю это полностью. Спасибо еще раз. - person Sameer Zahid; 24.05.2013
comment
Еще один быстрый вопрос: если злоумышленник получает доступ к базе данных, он уже знает, что только последняя часть пароля, а оставшаяся слева - соль, так в чем тогда смысл использования Salts? - person Sameer Zahid; 24.05.2013
comment
На это уже есть много ответов: security.stackexchange.com/questions / 17421 / how-to-store-salt, security.stackexchange.com/questions/33505/ - person deceze♦; 24.05.2013