Как правильно хранить хеш пароля PBKDF2

Я провел некоторое исследование правильных способов хеширования / шифрования пароля и сохранения его в базе данных. Я знал о соли и хешировании, поэтому огляделся, и PBKDF2 показался мне хорошим выбором. Итак, я нашел этот веб-сайт, который дал хорошее руководство по нему, а также адаптацию PBKDF2. для PHP (это то, что я использую на своем веб-сайте).

Итак, я настроил свой веб-сайт для использования этих функций для генерации / создания паролей, но, как вы можете видеть в следующем коде:

function create_hash($password) {
// format: algorithm:iterations:salt:hash
$salt = base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTES, MCRYPT_DEV_URANDOM));
return PBKDF2_HASH_ALGORITHM . ":" . PBKDF2_ITERATIONS . ":" .  $salt . ":" .
    base64_encode(pbkdf2(
        PBKDF2_HASH_ALGORITHM,
        $password,
        $salt,
        PBKDF2_ITERATIONS,
        PBKDF2_HASH_BYTES,
        true
    )); }

Соль генерируется в функции create_hash и сохраняется в результирующем хэше, который в конечном итоге выглядит как sha256: 1000: salt: hashed_password. Это то, что мне нужно было сохранить в моей базе данных, и поскольку соль была включена в полученный хэш, мне не нужно было добавлять ее в мою базу данных. Однако после создания нескольких тестовых пользователей с этим я задался вопросом, действительно ли хорошо иметь настройки PBKDF2 внутри моего хешированного пароля в базе данных. Они, как мой новичок, видят, что это хакер, после взлома моей базы данных, он увидит эту кучу sha256: 1000: salt: password вещей и выяснит, что будет означать каждая часть, что очень поможет ему в его попытках, нет ?

Поэтому я немного изменил его, чтобы иметь внешнюю соль, которую я генерирую и храню в своей базе данных, и включил соль в пароль, прежде чем запускать его через PBKDF2. Затем я делаю то же самое, чтобы сравнить данный пароль с тем, что у меня есть в моей базе данных для входа в систему, и он работает. Меня беспокоит только то, что со 128-битной солью результирующий хэш пароля составляет всего 50 символов, что мне не кажется правильным.

Вот мой текущий код:

define("PBKDF2_HASH_ALGORITHM", "sha256");
define("PBKDF2_ITERATIONS", 10000);
define("PBKDF2_SALT_BYTES", 128);
define("PBKDF2_HASH_BYTES", 24);

define("HASH_SECTIONS", 4);
define("HASH_ALGORITHM_INDEX", 0);
define("HASH_ITERATION_INDEX", 1);
define("HASH_SALT_INDEX", 2);
define("HASH_PBKDF2_INDEX", 3);

function create_hash($password, $salt)
{
    // format: salthash
    return  
        base64_encode(pbkdf2(
            PBKDF2_HASH_ALGORITHM,
            $password,
            $salt,
            PBKDF2_ITERATIONS,
            PBKDF2_HASH_BYTES,
            true
        ));
}

function validate_password($password, $salt, $good_hash)
{
    $pbkdf2 = base64_decode($good_hash);
    return slow_equals(
        $pbkdf2,
        pbkdf2(
            PBKDF2_HASH_ALGORITHM,
            $password,
            $salt,
            PBKDF2_ITERATIONS,
            PBKDF2_HASH_BYTES,
            true
        )
    );
}

// Compares two strings $a and $b in length-constant time.
function slow_equals($a, $b)
{
    $diff = strlen($a) ^ strlen($b);
    for($i = 0; $i < strlen($a) && $i < strlen($b); $i++)
    {
        $diff |= ord($a[$i]) ^ ord($b[$i]);
    }
    return $diff === 0; 
}

function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
{
    $algorithm = strtolower($algorithm);
    if(!in_array($algorithm, hash_algos(), true))
        die('PBKDF2 ERROR: Invalid hash algorithm.');
    if($count <= 0 || $key_length <= 0)
        die('PBKDF2 ERROR: Invalid parameters.');

    $hash_length = strlen(hash($algorithm, "", true));
    $block_count = ceil($key_length / $hash_length);

    $output = "";
    for($i = 1; $i <= $block_count; $i++) {
        // $i encoded as 4 bytes, big endian.
        $last = $salt . pack("N", $i);
        // first iteration
        $last = $xorsum = hash_hmac($algorithm, $last, $password, true);
        // perform the other $count - 1 iterations
        for ($j = 1; $j < $count; $j++) {
            $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
        }
        $output .= $xorsum;
    }

    if($raw_output)
        return substr($output, 0, $key_length);
    else
        return bin2hex(substr($output, 0, $key_length));
}

Имеет ли смысл мое беспокойство по поводу такого формата сохранения паролей? Или в любом случае это будет одно и то же, поскольку, имея мою соль в моей базе данных, после того, как она взломана, хакер все еще может пройти через грубую силу?

Спасибо и извините за длинный вопрос.


person Alex    schedule 31.07.2012    source источник
comment
См. Эти ответы, чтобы понять, насколько бесполезно скрывать соль здесь, используя bcrypt (поскольку в вашей ссылке реализация не указана) здесь, а также рекомендации по использованию хэша для защиты паролей здесь. Наконец, никогда не недооценивайте своего противника!   -  person Romain    schedule 31.07.2012
comment
@Romain Это тот ответ, который я искал. Сделали пока, но не стесняйтесь отвечать сами.   -  person Alex    schedule 06.11.2014


Ответы (3)


Меня беспокоит только то, что со 128-битной солью результирующий хэш пароля составляет всего 50 символов, что мне не кажется правильным.

Размер полученного хэша совершенно не зависит от размера соли, пароля и количества итераций. Выходные данные современного безопасного алгоритма хеширования (например, sha256) всегда имеют одинаковую длину независимо от ввода. Вход нулевой длины имеет ту же выходную длину, что и вход 25 ТБ.

Или в любом случае это будет одно и то же, поскольку, имея мою соль в моей базе данных, после того, как она взломана, хакер все еще может пройти через грубую силу?

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

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

person Slartibartfast    schedule 01.08.2012

Отвечая на свой вопрос с комментарием от @Romain.

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

Кроме того (так как это немного устарело), ​​bcrypt действительно «лучше», чем pbkdf2, но scrypt даже лучше!

person Community    schedule 06.11.2014

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

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

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

person jmoreno    schedule 01.08.2012
comment
Вы не должны использовать соль для каждого приложения без соли для конкретного пароля, поскольку злоумышленник может искать повторяющиеся хэши и использовать эту информацию для атаки на наиболее уязвимые пароли (и найти две или более учетных записи пользователей на одинаковое количество работы). - person Maarten Bodewes; 06.08.2012
comment
@owlstead: да, я имел в виду добавление к соли для пароля. Отредактирую, чтобы прояснить это. Спасибо. - person jmoreno; 06.08.2012
comment
этот ответ сбивает с толку. нет ничего плохого в использовании SHA256 при использовании PBKDF2. конструкция PBKDF2 добавляет замедление (это эквивалент bcrypt; часть 1000 означает, что SHA256 был вызван 1000 раз PBKDF2). - person andrew cooke; 10.11.2013
comment
PBKDF2 не эквивалент bcrypt - у них есть сходство, но запуск SHA256 1000 раз не то же самое, что запуск bcrypt 1000 раз. - person jmoreno; 10.11.2013
comment
@jmoreno: если вы сравните PBKDF2 с bcrypt, то примите во внимание, что эти 1000x SHA256-вызовов (или любой другой алгоритм хеширования / количество итераций, которое вы берете) происходят во время одного единственного вызова PBKDF2. - person Robert; 29.11.2013
comment
@Robert: Да, вы можете сделать так, чтобы они занимали одинаковое количество времени в данной системе, но не за одинаковое количество итераций. SHA256 менее ресурсоемок, чем BCRYPT, и его можно значительно ускорить с помощью графического процессора. Поскольку вы не контролируете машину злоумышленников, лучше использовать функцию хеширования, которая затрудняет им использование оборудования для ее ускорения. BCRYPT разработан, чтобы быть медленным (доступ к памяти), SHA256 разработан, чтобы быть быстрым (простой битовый сдвиг и xors). Для паролей лучше использовать BCRYPT (хотя теперь, возможно, лучше использовать scrypt). - person jmoreno; 29.11.2013