PHP – всё про защиту пароля

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

PHP – md5 вычисляем хеш строки

Строки? А почему не пароля?
НЕ ИСПОЛЬЗУЙТЕ md5 для “шифрования” паролей, это очень ненадежное средство. Как это выглядит на практике:

echo md5('test123'); // cc03e747a6afbbcbf8be7668acfebee5
echo md5('test124'); // ad2af2578b4d55b5f78383024270f852

Если запустить данную команду, то функция вернёт MD5-хеш строки. У нас два разных пароля, у которых отличается только последний символ test123 и test124. Разница 1 символ, но ничего общего в хеше md5 нет.

Так почему же такой способ ненадежный? Он очень легко ломается, и если вы не можете перевести обратно из md5 в текст, то для злоумышленника это не займет много времени (что уж там говорить, если есть сайты, которые это делают в пару кликов).

Предположим, что md5 это хороший и надежный способ хранить пароли (напомню, это не так). Как бы тогда выглядел код?

$passwordHash = 'cc03e747a6afbbcbf8be7668acfebee5';
$password = $_REQUEST['password'];
if (md5($password) === $passwordHash) {
	echo 'done';
} else {
	echo 'error';
}

Где, $passwordHash – это зашифрованный пароль, который лежит в базе данных, а $password = $_REQUEST[‘password’] – это пароль, который ввел пользователь через форму авторизации.

PHP – Как надежно защитить пароль?

Для этого нам понадобится функция password_hash и password_verify. Как вообще должен храниться пароль:

При таком указании пароля он делится на части:
алгоритм – при помощи которого и был зашифрован пароль;
cost – задаёт необходимую алгоритмическую сложность, стандартно она 10, чем выше сложность, тем дольше будет генерироваться пароль, и соответственно, тем дольше будет загружаться страница;
salt – соль для хеширования, некая уникальная строка, которая увеличивает сложность дешифровки пароля. На данный момент, используется автоматическая соль, но об этом чуть ниже;
хэш – хэш пароля.

То есть, пароль шифруется при помощи определенного алгоритма, сложности и соли.

Как применять функцию password_hash в PHP?

Функция должна содержать 2 аргумента, сам пароль и алгоритм. PASSWORD_DEFAULT, PASSWORD_BCRYPT – наиболее чаще встречаются в качестве алгоритмов, особенно PASSWORD_BCRYPT. Такой хэш пароля, в отличие от md5, при перезагрузке страницы, всегда будет уникальным, и его длина, независимо от выбранной сложности, всегда 60 символов.
Выглядит это следующим образом:

echo password_hash('test123', PASSWORD_DEFAULT); // $2y$10$kPuLknA408BkgnaKVUh8EOQOm1dRUUkufeqvG6OoIvpGDopPGea1q
echo password_hash('test123', PASSWORD_BCRYPT); // $2y$10$jFmerATwXASg0WvV3W9pt.fK1UQmnnLrKao35r6/VJfzz7NPxszCq
echo mb_strlen(password_hash('test123', PASSWORD_BCRYPT)); // 60

Как же нам увеличить сложность алгоритма? Для этого нужно использовать третий аргумент функции, который не обязательный, это опции:

echo password_hash('test123', PASSWORD_BCRYPT, ['salt' => 'ttttttttttttttttttttttttt']);
// $2y$10$ttttttttttttttttttttteRXxHYSDzcdOi/S1JzEheCNMETevWiKC

echo password_hash('test123', PASSWORD_BCRYPT, ['cost' => 12]);
// $2y$12$wNh3cx81xQzXvOKCwPuerOOBOyyxgE3noJQsp3L3RYDMy43CTs4n2

Через массив мы указываем salt – и она уже будет являться частью строки. Если вы внимательно изучите сам процесс создания этого участка “из соли”, то обнаружите, что она может записаться не полностью, или может быть не достаточно сложной. В настоящее время не рекомендуется её задавать явно, а начиная с PHP 8 её просто игнорируют, и всё равно генерируют автоматически.

Внимание
Эта опция объявлена устаревшей. Рекомендуется использовать автоматически генерируемую соль. Начиная с PHP 8.0.0 явно заданная соль игнорируется.

Теперь что касается cost, и сложности хэша. Как и говорилось выше, чем больше сложность, тем больше потребуется ресурсов для генерации. Чтобы вычислить оптимальную сложность, можно воспользоваться функцией из документации:

/**
 * Данный код замерит скорость выполнения операции хеширования для вашего сервера
 * с разными значениями алгоритмической сложности для определения максимального
 * его значения, не приводящего к деградации производительности. Хорошее базовое
 * значение лежит в диапазоне 8-10, но если ваш сервер достаточно мощный, то можно
 * задать и больше. Данный скрипт ищет максимальное значение, при котором
 * хеширование уложится в 50 миллисекунд.
 */
$timeTarget = 0.05; // 50 миллисекунд.

$cost = 8;
do {
    $cost++;
    $start = microtime(true);
    password_hash("test", PASSWORD_BCRYPT, ["cost" => $cost]);
    $end = microtime(true);
} while (($end - $start) < $timeTarget);

echo "Оптимальная стоимость: " . $cost;

Как проверить хэш пароля в PHP?

Делается это при помощи специальной функции, password_verify:

$hash = '$2y$10$ttttttttttttttttttttteRXxHYSDzcdOi/S1JzEheCNMETevWiKC';
if (password_verify('test124', $hash)) {
	echo 'done';
} else {
	echo 'error';
}

Пара моментов на которые стоит обратить внимание:
1. Один и тот же пароль, зашифрованный при помощи функции password_hash, всегда будет иметь разный хеш, но при этом, password_verify – раскодирует его именно так как нужно.
2. От одной версии языка PHP к другой, с течением времени, с изменением общей ситуации в мире безопасности, да и вообще, в контексте разного “железа” на вашем сервере, нужно думать наперед, и писать код через password_needs_rehash.

Что такое password_needs_rehash?

password = 'rasmuslerdorf';
$hash = '$2y$10$YCFsG6elYca568hBi2pZ0.3LDL5wjgxct1N8w/oLR/jfHsiQwCqTS';

// Параметр стоимости может изменяться в связи со сменой оборудования
$options = array('cost' => 11);

// Проверка сохранённого хеша с помощью пароля
if (password_verify($password, $hash)) {
    // Проверяем, не нужно ли использовать более новый алгоритм
    // или другую алгоритмическую стоимость
    if (password_needs_rehash($hash, PASSWORD_DEFAULT, $options)) {
        // Если таки да, перехешируем и сохраняем новый хеш
        $newHash = password_hash($password, PASSWORD_DEFAULT, $options);
    }

    // Авторизуем пользователя
}

Опять же, замечательный пример из документации, который будет создавать новый хэш пароля, вам нужно будет перезаписать его в базе данных, в случае таких вот изменений обстоятельств, которые описаны в пункте 2. Изменили ‘cost’ => 13, пожалуйста, вот вам новый пароль).
Долго загружается страница, вернулись на стандартные 10, и снова всё автоматически работает, песня! 🙂 .

И всё таки, может ли владелец сайта расшифровать такой пароль? Ответ – если это md5 или base65, да, запросто, если это password_hash с автоматической солью – то удачи ему 🙂 . Такие пароли, в данный момент времени, считаются надежными.

На этом всё, надеюсь было интересно 🙂 .

автор: Dmitriy

З 2011 року займаюся веб-розробкою. Зараз я – PHP Full Stack Developer.
Обговорити ваш проект, а також дізнатися більше про мене ви можете на цьому сайті:
dev.forwww.com

Email: dmitriyribka@gmail.com

Залишити відповідь