PHP 8.4: Стоимость Bcrypt по умолчанию изменена с 10 на 12
PHP предоставляет функции password_hash, password_verify и password_needs_rehash (с двумя дополнительными функциями для получения списка поддерживаемых алгоритмов и для получения информации из хэша) для безопасного хэширования паролей. Алгоритм хеширования пароля и его параметры настраиваются, а алгоритм и параметры хранятся в самом хеше пароля, поэтому пароль можно проверить, используя те же параметры, на основе которых был вычислен хеш.
Когда API хеширования паролей был представлен в PHP 5.5 (2013), он поддерживал Bcrypt в качестве одного из алгоритмов, на что указывала константа PASSWORD_BCRYPT.
password_hash('hunter2', PASSWORD_BCRYPT);
// "$2y$10$GUYJOA4aWc6HegA2x.85PeZ9yjP2HCKFjb44C3vz1jlpUNb4AgYz2"PHP 5.5 также поддерживал PASSWORD_DEFAULT, разработанный для изменения со временем. Алгоритм по умолчанию не менялся, и в будущем его изменение также не планируется.
Также можно изменить параметры алгоритма хеширования:
password_hash('hunter2', PASSWORD_BCRYPT, ['cost' => 10]);
// "$2y$10$dI8qa2bKk/RN2hemdHkOgu8oICMn7ivhzVyJmiJmNFzjJpYsLEAYK"Для PASSWORD_BCRYPT/PASSWORD_DEFAULT значение cost по умолчанию равно 10. Параметр cost — это количество используемых итераций, вводимое как степень 2. Например, значение cost, равное 10, означает 2**10 итераций.
В PHP 8.4 параметр cost алгоритма PASSWORD_BCRYPT/PASSWORD_DEFAULT был изменён с 10 на 12, чтобы сделать пароли более устойчивыми и сложными для вычисления, учитывая более мощное аппаратное обеспечение по сравнению с тем, когда было принято значение по умолчанию.
Это изменение по сути равнозначно:
-password_hash('hunter2', PASSWORD_BCRYPT, ['cost' => 10]);
+password_hash('hunter2', PASSWORD_BCRYPT, ['cost' => 12]);
Показатели стоимости вычислений
В RFC, предлагаемыми изменениями, приводились результаты тестирования на различном доступном в настоящее время оборудовании, в которых сравнивалось время, необходимое для вычисления хэша пароля с затратами 10, 11 и 12.
Правильный баланс этой стоимости заключается в том, что она должна быть достаточно быстрой для вычисления сервером, чтобы не мешать работе пользователя, но достаточно медленной, чтобы её было сложно вычислить методом перебора в случае, если злоумышленник получит хэши паролей.
Следующие таблица и график составлены на основе данных, приведённых в RFC, а также дополнительных бенчмарков, проведённых PHP.Watch. Для каждого процессора среднее время вычисления хэша при различных параметрах cost. Время указано в миллисекундах.
| 8 | 9 | 10 | 11 | 12 | 13 | 14 | |
|---|---|---|---|---|---|---|---|
| Intel i5-2430M 2.4GHz - 2011 | 21 | 41 | 80 | 161 | 328 | 657 | 1319 |
| Apple M1 Pro 2021 | 15 | 31 | 61 | 121 | 240 | 480 | 965 |
| Intel Xeon E-2246G 3.60 GHz - 2019 | 11 | 20 | 39 | 78 | 158 | 316 | 631 |
| Intel Xeon E32145 3.30 GHz - 2011 | 16 | 32 | 64 | 128 | 256 | 523 | 1047 |
| AMD EPYC 7002 2019 | 14 | 29 | 58 | 116 | 244 | 485 | 997 |
| AMD Ryzen 4800H 2.9 GHz - 2020 | 21 | 45 | 60 | 105 | 210 | 416 | 850 |
| Intel Xeon Skylake IBRS - 2020 | 13 | 27 | 54 | 108 | 216 | 432 | 873 |
Влияние на обратную совместимость
В PHP 8.4 параметр cost по умолчанию в PASSWORD_BCRYPT/PASSWORD_DEFAULT изменён на 12. В приложениях явно задающих стоимость всё останется без изменений.
Параметры для PASSWORD_ARGON2I или PASSWORD_ARGON2ID остаются неизменными, и в приложениях использующих их, всё останется без изменений.
Приложения, использующие параметры cost по умолчанию, получат несколько большее время вычисления пароля, но это ожидаемый результат — увеличение времени вычисления для злоумышленников, пытающихся перебрать хэши паролей.
Автоматическое повторное хеширование
Когда в PHP появился API хэширования паролей, было рекомендовано вызывать функцию password_needs_rehash после успешного вызова password_verify, чтобы проверить, нужно ли повторно хэшировать пароль.
Ниже приведён пример использования PASSWORD_BCRYPT, проверяющий необходимость повторного хэширования пароля. Поскольку параметр cost изменился в PHP 8.4, password_needs_rehash возвращает true при значениях по умолчанию. Это одноразовое повторное хэширование, если обновлённый хэш сохраняется в базе данных и используется в дальнейшем.
// Когда пользователь вошёл в систему:
if (password_verify($password, $hash)) {
// Пароль корректный.
// Проверка, нуждается ли пароль в повторном хешировании:
if (password_needs_rehash($hash, PASSWORD_BCRYPT)) {
// Если это так, создаём новый хэш и заменяем старый
$newHash = password_hash($password, PASSWORD_BCRYPT);
// Обновление записи user с $newHash
// ...
}
// Продолжение процесса логина.
}Как вернуть старый параметр стоимости
Если на серверах установлены очень старые или маломощные процессоры, или если приложению необходимо работать с предыдущим значением cost по умолчанию 10, можно вернуться к старому времени вычисления хэша, явно задав параметр cost.
-password_hash('hunter2', PASSWORD_BCRYPT);
+password_hash('hunter2', PASSWORD_BCRYPT, ['cost' => 10]);
-password_hash('hunter2', PASSWORD_DEFAULT);
+password_hash('hunter2', PASSWORD_BCRYPT, ['cost' => 10]);