Хеширование и верификация паролей
Дата обновления перевода 2024-07-27
Хеширование и верификация паролей
Большинство приложений используют пароли для допуска пользователей в систему. Эти пароли должны быть хешированы, чтобы храниться безопасно. Компонент Symfony PasswordHasher предоставляет все инструменты, чтобы безопасно хешировать и верифицировать пароли.
Убедитесь в том, что он установлен, выполнив:
1
$ composer require symfony/password-hasher
Конфигурация хешировщика паролей
Перед хешированием паролей, вы должны сконфигурировать хешировщик,
используя опцию password_hashers
. Вы должны сконфигурировать
алгоритм хеширования и, по желанию, некоторые опции алгоритма:
1 2 3 4 5 6 7 8 9 10 11 12
# config/packages/security.yaml
security:
# ...
password_hashers:
# авто-хешировщик с опциями по умолчанию для класса User (и его дочерей)
App\Entity\User: 'auto'
# авто-хешировщик с пользовательскими опциями для всех экземпляров PasswordAuthenticatedUserInterface
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: 'auto'
cost: 15
В данном примере используется алгоритм "авто". Этот хешировщик автоматически выбирает наиболее безопасный алгоритм, доступный в вашей системе. Вместе с миграцией паролей , это позволяет вам всегда максимально обезопашивать ваши пароли (даже при условии появления новых алгоритмов в последующих релизах PHP).
Далее в этой статье вы можете найти полный справочник всех поддерживаемых алгоритмов .
Tip
Хеширование паролей требует большого количества ресурсов и времени, чтобы генерировать безопасные хеши паролей. В общем, это делает ваше хеширование паролей более безопасным.
Однако в тестах безопасные хеши неважны, поэтому вы можете изменить
конфигурацию хешировщица паролей в окружении test
, чтобы тесты
выполнялись быстрее:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# config/packages/test/security.yaml
security:
# ...
password_hashers:
# Используйте ваше имя класса пользователя здесь
App\Entity\User:
algorithm: plaintext # отключить хеширование (делайте это только в тестах!)
# или использовать минимальные возможные значения
App\Entity\User:
algorithm: auto # Должно быть то же значение, что и в config/packages/security.yaml
cost: 4 # Минимальное возможное значение для bcrypt
time_cost: 3 # Минимальное возможное значение для argon
memory_cost: 10 # Минимальное возможное значение для argon
Хеширование пароля
После конфигурации правильного алгоритма, вы можете использовать
UserPasswordHasherInterface
, чтобы хешировать и верифицировать
пароли:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
// src/Controller/RegistrationController.php
namespace App\Controller;
// ...
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class UserController extends AbstractController
{
public function registration(UserPasswordHasherInterface $passwordHasher): Response
{
// ... например, получить данные пользователя из формы регистрации
$user = new User(...);
$plaintextPassword = ...;
// хешировать пароль (основываясь на конфигурации security.yaml для класса $user)
$hashedPassword = $passwordHasher->hashPassword(
$user,
$plaintextPassword
);
$user->setPassword($hashedPassword);
// ...
}
public function delete(UserPasswordHasherInterface $passwordHasher, UserInterface $user): void
{
// ... например, получить пароль из диалога "подтвердить удаление"
$plaintextPassword = ...;
if (!$passwordHasher->isPasswordValid($user, $plaintextPassword)) {
throw new AccessDeniedHttpException();
}
}
}
Сброс пароля
Используя MakerBundle и SymfonyCastsResetPasswordBundle, вы можете создать безопасное, готовое к использованию, решение для обработки забытых паролей. Для начала, установите SymfonyCastsResetPasswordBundle:
1
$ composer require symfonycasts/reset-password-bundle
Затем, используйте команду make:reset-password
. Это задаст вам пару
вопросов о вашем приложении, и сгенерирует все необходимые вам файлы!
После этого, вы увидите сообщение об успехе и список всех шагов, которые
вам нужно сделать.
1
$ php bin/console make:reset-password
Tip
Начиная с MakerBundle: v1.57.0 - Вы можете передать --with-uuid
или
--with-ulid
в make:reset-password
. Используя Компонент Symfony Uid,
сущности будут генерироваться с типом id
в виде :ref:df9f8179cf7766fbef42cdab0f1ae769942a64f6int``.
Вы можете настроить поведение пакета сброса пароля, обновив файл
reset_password.yaml
. Чтобы узнать больше о конфигурации, прочтите
руководство SymfonyCastsResetPasswordBundle.
Миграция паролей
Для того, чтобы защитить пароли, рекомендуется хранить их, используя последние
алгоритмы хеширования. Это означает, что если в вашей системе поддерживается
улучшенный алгоритм хеширования, пароль пользователя должен быть хеширован снова,
используя более новый алгоритм, и сохранен. Это возможно с опцией migrate_from
:
- Сконфигурируйте новый хешировщик, используя "migrate_from"
- Обновите пароль
- По желанию, Запустите миграцию паролей из пользовательского хешировщика
Сконфигурируйте новый хешировщик, используя "migrate_from"
Когда становится доступным улучшенный алгоритм хеширования, вам нужно оставить
существующий(ие) хешировщик(и), переименовать его, а затем определить новый.
Установите опцию migrate_from
в новом хешировщике, чтобы указать ему на
старый(е), наследумый(е) хешировщик(и):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# config/packages/security.yaml
security:
# ...
password_hashers:
# хешировщик, используемый ранее для некоторых пользователей
legacy:
algorithm: sha256
encode_as_base64: false
iterations: 1
App\Entity\User:
# новый хешировщик, вместе с его опциями
algorithm: sodium
migrate_from:
- bcrypt # использует хешировщик "bcrypt" с опциями по умолчанию
- legacy # использует хешировщик "legacy", сконфигурированный выше
С такой настройкой:
- Новые пользователи будут хешированы с использованием нового алгоритма;
- Когда в систему войдет пользователь, чей пароль все еще сохранен с помощью старого алгоритма, Symfony верифицирует пароль со старым алгоритмом, а затем обновит пароль, используя новый алгоритм.
Tip
Хешировщики auto, native, bcrypt и argon автоматически включают
миграцию паролей, используя следующий список алгоритмов migrate_from
:
- PBKDF2 (который использует hash_pbkdf2);
- Дайджест сообщений (который использует hash)
Оба используют настройку hash_algorithm
в качестве алгоритма. Рекомендуется
использовать migrate_from
вместо hash_algorithm
, если только не используется
хешировщик auto.
Обновите пароль
После удачного входа в систему, система Безопасности проверяет, доступен ли
улучшенный алгоритм для хеширования пароля пользователя. Если доступен, она
хеширует правильный пароль, используя новый хеш. При использовании пользовательского
аутентификатора, вы должны использовать PasswordCredentials
в
паспорте безопасности .
Вы можете включить поведение обновления пароля, реализовав, как этот новохешированный пароль должен быть сохранен:
- При использовании сущности поставщика пользователей Doctrine
- При использовании пользовательского поставщика пользователей
После этого, вы закончили, и пароли всегда будут хешированы максимально безопасно!
Note
При использовании компонента PasswordHasher вне приложения Symfony, вы
должны вручную использовать метод PasswordHasherInterface::needsRehash()
,
чтобы проверить необходимо ли повторное хеширование, и метод
PasswordHasherInterface::hash()
, чтобы повторно хешировать открытый
текстовый пароль, используя новый алгоритм.
Обновление пароля при использовании Doctrine
При использовании сущности поставщика пользователей,
реализуйте PasswordUpgraderInterface
в UserRepository
(см. документацию Doctrine, чтобы узнать информацию о том, как
создать этот класс, если он еще не создан). Этот интерфейс реализует сохранение
новосозданного хеша паролей:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// src/Repository/UserRepository.php
namespace App\Repository;
// ...
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
class UserRepository extends EntityRepository implements PasswordUpgraderInterface
{
// ...
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
{
// установить новый хешированный пароль в объекте User
$user->setPassword($newHashedPassword);
// выполнить запросы в базе данных
$this->getEntityManager()->flush();
}
}
Обновление пароля при использовании пользовательского поставщика пользователей
Если вы используете пользовательского поставщика пользователей , реализуйте PasswordUpgraderInterface в поставщике пользователей:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/Security/UserProvider.php
namespace App\Security;
// ...
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
class UserProvider implements UserProviderInterface, PasswordUpgraderInterface
{
// ...
public function upgradePassword(UserInterface $user, string $newHashedPassword): void
{
// установить новый хешированный пароль в объекте User
$user->setPassword($newHashedPassword);
// ... сохранить новый пароль
}
}
Запустите миграцию паролей из пользовательского хешировщика
Если вы используете пользовательский хешироващик паролей, вы можете запустить
миграцию паролей, вернув true
в методе needsRehash()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/Security/CustomPasswordHasher.php
namespace App\Security;
// ...
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
class CustomPasswordHasher implements PasswordHasherInterface
{
// ...
public function needsRehash(string $hashedPassword): bool
{
// проверить, хеширован ли текущий пароль с использованием устаревшего хешировщика
$hashIsOutdated = ...;
return $hashIsOutdated;
}
}
Динамический хешировщики паролей
Обычно, один и тот же хешировщик паролей используется для всех пользователей, путем его конфигурации с применением ко всем экземплярам конкретного класса. Другая опция - использовать "именованный" хешировщик, а затем выбирать, какой хешировщик вы хотите использовать, динамически.
По умолчанию (как показано в начале этой статьи), для App\Entity\User
используется алгоритм auto
.
Это может быть достаточно безопасно для обычного пользователя, но что,
если вы хотите, чтобы у ваших админов был алгоритм сильнее, к примеру,
auto
с большей стоимостью. Это может быть сделано с помощью именованных
хешировщиков:
1 2 3 4 5 6 7
# config/packages/security.yaml
security:
# ...
password_hashers:
harsh:
algorithm: auto
cost: 15
Это создает хешировщик с именем harsh
. Для того, чтобы экземпляр User
использовал его, касс должен реализовать
PasswordHasherAwareInterface.
Интерфейс требует одного метода - getPasswordHasherName()
- который должен
возвращать имя хешировщика для использования:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
// src/Entity/User.php
namespace App\Entity;
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherAwareInterface;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class User implements
UserInterface,
PasswordAuthenticatedUserInterface,
PasswordHasherAwareInterface
{
// ...
public function getPasswordHasherName(): ?string
{
if ($this->isAdmin()) {
return 'harsh';
}
return null; // использовать хешировщик по умолчанию
}
}
Caution
При миграции паролей , вам не нужно
реализовывать PasswordHasherAwareInterface
для возврата имени унаследованного
хешера: Symfony определит его из вашей конфигурации migrate_from
.
Если вы создали собственный хешировщик паролей, реализующий PasswordHasherInterface, вы должны зарегистрировать для него сервис, чтобы использовать его как именованный хешировщик:
1 2 3 4 5 6
# config/packages/security.yaml
security:
# ...
password_hashers:
app_hasher:
id: 'App\Security\Hasher\MyCustomPasswordHasher'
Это создаст хешировщик с именем app_hasher
из сервиса с ID
App\Security\Hasher\MyCustomPasswordHasher
.
Хеширование отдельностоящей строки
Хешировщик паролей можно использовать для хеширования строк независимо от пользователей. Используя PasswordHasherFactory, вы можете объявить несколько хэшировщиков, получить любой из них по имени и создавать хеши. Затем вы можете проверить, соответствует ли строка заданному хешу:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
// configure different hashers via the factory
$factory = new PasswordHasherFactory([
'common' => ['algorithm' => 'bcrypt'],
'sodium' => ['algorithm' => 'sodium'],
]);
// получить хешировщик, используя bcrypt
$hasher = $factory->getPasswordHasher('common');
$hash = $hasher->hash('plain');
// проверить, совпадает ли заданная строка с хешировщиком, вычисленным выше
$hasher->verify($hash, 'invalid'); // false
$hasher->verify($hash, 'plain'); // true
Поддерживаемые алгоритмы
- auto
- bcrypt
- sodium
- PBKDF2
- Или создайте пользовательский хешировщик паролей
Хешировщик "auto"
Автоматически выбирает лучший доступный хешировщик. Начиная с Symfony 5.3, он использует хешировщик Bcrypt. Если PHP или Symfony в будущем добавит новые хешировщики паролей, он может выбирать другой хешировщик.
Из-за этого, длина хешированных паролей может измениться в будущем, поэтому
убедитесь в том, что оставили достаточно места для их сохранения
(varchar(255)
должно быть хорошей настройкой).
Хешировщик паролей Bcrypt
Производит хешированные пароли с функцией хеширования паролей bcrypt.
Хешированные пароли имеет 60
символов, поэтому убедитесь в том, что
оставили достаточно места для их сохранения. Также, пароли включают в себя
криптографическую соль (которая генерируется автоматически для каждого
нового пароля), чтобы вам не нужно было с этим разбираться.
Единственная опция конфигурации - cost
, которая является целым числом в
диапазоне 4-31
(по умолчанию, 13
). Каждый шаг стоимости удваивает время,
необходимое для хеширования пароля. Это сделано для того, чтобы сила пароля могла
быть адаптирована к последующим улучшениям в мощности вычисления.
Вы можете изменить стоимость в любое время - даже если у вас уже есть пароли, хешированные с использованием другой стоимости. Новые пароли будут хешированы с использованием новой стоимости, в то время, как уже хешированные пароли будут валидированы, используя стоимость, которая была использована во время их хеширования.
Tip
Простая техника для значительного ускорения тестов при использовании BCrypt
заключаются в установке стоимости 4
, что является минимальным разрешенным
значением, в конфигурации окружения test
.
Хешировщик паролей Sodium
Использует функцию формирования ключа Argon2. Поддержка Argon2 была представлена в PHP 7.2 путем создания пакета расширения libsodium.
Хешированные пароли имеют длину 96
символов, но в связи с требованиями хеширования,
сохраненными в результирующем хеше, это может измениться в будущем, так что убедитесь в
том, что оставили достаточно места для их сохранения. Также, пароли включают в себя
криптографическую соль (которая генерируется автоматически для каждого нового пароля),
чтобы вам не нужно было с этим разбираться.
Хешировщик PBKDF2
Использование хешировщика PBKDF2 больше не рекомендуется с тех пор, как PHP добавил поддержку Sodium и BCrypt. Наследуемые приложения, все еще использующие его, рекомендуется обновить до более новых алгоритмов хеширования.
Создание пользовательского хешировщика паролей
Если вам нужно создать собственный, он должен следовать таким правилам:
- Класс должен реализовывать PasswordHasherInterface (вы также можете реализовать LegacyPasswordHasherInterface если ваш алгоритм хеша использует отдельную соль);
Реализации hash() и verify() должны валидировать, что длина пароля не более 4096 знаков. Это из соображений безопасности (см. CVE-2013-5750).
Вы можете использовать метод isPasswordTooLong() для такой проверки.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
// src/Security/Hasher/CustomVerySecureHasher.php
namespace App\Security\Hasher;
use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException;
use Symfony\Component\PasswordHasher\Hasher\CheckPasswordLengthTrait;
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
class CustomVerySecureHasher implements PasswordHasherInterface
{
use CheckPasswordLengthTrait;
public function hash(string $plainPassword): string
{
if ($this->isPasswordTooLong($plainPassword)) {
throw new InvalidPasswordException();
}
// ... хешировать чистый пароль безопасным образом
return $hashedPassword;
}
public function verify(string $hashedPassword, string $plainPassword): bool
{
if ('' === $plainPassword || $this->isPasswordTooLong($plainPassword)) {
return false;
}
// ... валидировать, равняется ли пароль паролю пользователя, безопасным образом
return $passwordIsValid;
}
}
Теперь, определите хешировщик пароля, используя настройку id
:
1 2 3 4 5 6 7
# config/packages/security.yaml
security:
# ...
password_hashers:
app_hasher:
# сервис-ID вашего пользовательского хешировщика (FQCN, используя services.yaml по умолчанию)
id: 'App\Security\Hasher\MyCustomPasswordHasher'