Хешування та верифікація паролів
Дата оновлення перекладу 2024-06-03
Хешування та верифікація паролів
Більшість додатків використовують паролі для допуску користувачів у систему. Ці паролі повинні бути хешовані, щоб зберігатися безпечно. Компонент 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
як Uuid
або Ulid замість int
.
Ви можете налаштувати поведінку пакету скидання праоля, оновивши файл 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;
// сконфігурувати різні хешувальники, використовуючи фабрику
$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'