Хешування та верифікація паролів

Дата оновлення перекладу 2022-11-27

Хешування та верифікація паролів

Більшість додатків використовують паролі для допуску користувачів у систему. Ці паролі повинні бути хешовані, щоб зберігатися безпечно. Компонент Symfony PasswordHasher надає всі інструменти, щоб безпечно хешувати та верифікувати паролі.

Переконайтеся в тому, що він встановлений, виконавши:

1
$ composer require symfony/password-hasher

Конфігурація хешувальника паролів

Перед хешуваннямм паролів, ви повинні сконфігурувати хешувальник, використовуючи опцію password_hashers. Ви повинні сконфігурувати алгоритм хешування і, за бажанням, деякі опції алгоритму:

  • YAML
  • XML
  • PHP
  • Standalone Use
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, щоб тести виконувалися швидше:

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
8
9
10
11
12
# config/packages/test/security.yaml
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, щоб хешувати та верифікувати паролі:

  • Framework Use
  • Standalone Use
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
36
// 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)
    {
        // ... наприклад, отримати дані користувача з форми реєстрації
        $user = new User(...);
        $plaintextPassword = ...;

        // хешувати парль (засновуючись на конфігурації security.yaml для класу $user)
        $hashedPassword = $passwordHasher->hashPassword(
            $user,
            $plaintextPassword
        );
        $user->setPassword($hashedPassword);

        // ...
    }

    public function delete(UserPasswordHasherInterface $passwordHasher, UserInterface $user)
    {
        // ... наприклад, отримати пароль з діалогу "підтвердити видалення"
        $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

Ви можете налаштувати поведінку пакету скидання праоля, оновивши файл reset_password.yaml. Щоб дізнатися більше про конфігуррацію, прочитайте посібник SymfonyCastsResetPasswordBundle.

Міграція паролів

Для того, щоб захистити паролі, рекомендовано зберігати їх, використовуючи останні алгоритми хешування. Це означає, що якщо у вашій системі підтримується покращений алгоритм хешування, пароль користувача повинен бути хешований знову, використовуючи новіший алгоритм, та збережно. Це можливо з опцією migrate_from:

  1. Сконфігуруйте новий хешувальник, використовуючи "migrate_from"
  2. Оновіть пароль
  3. За бажанням, Запустіть міграцію паролів з користувацького хешувальника

Сконфігуруйте новий хешувальник, використовуючи "migrate_from"

Коли стає доступним покращений алгоритм хешування, вам потрібно залишити існуючий(і) хешувальник(и), переіменувати його, а потім визначити новий. Встановіть опцію
migrate_from у новому хешувальнику, щоб вказати йому на старий(і) успадкований(і) хешувальник(и):

  • YAML
  • XML
  • PHP
  • Standalone Use
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:

  1. PBKDF2 (який використовує hash_pbkdf2);
  2. Дайджест повідомлень (який використовує hash)

Обидва використовують налаштування hash_algorithm в якості алгоритму. Рекомендовано використовувати migrate_from замість hash_algorithm, хіба що не використовується хешувальник auto.

Оновіть пароль

Після успішного входу у систему, система Безпеки перевіряє, чи доступний покращений алгоритм для хешування пароля користувача. Якщо доступний, вона хешує правильний пароль, використовуючи новий хеш. При використанні користувацького аутентифікатора, ви повинні використовувати PasswordCredentials у паспорті безпеки .

Ви можете включити поведінку оновлення пароля, реалізувавши, як цей новохешований пароль повинен бути збережений:

Після цього, ви закінчили, і паролі завжди будуть хешовані максимально безпечно!

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
// src/Repository/UserRepository.php
namespace App\Repository;

// ...
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;

class UserRepository extends EntityRepository implements PasswordUpgraderInterface
{
    // ...

    public function upgradePassword(UserInterface $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\Hasher\UserPasswordHasherInterface;

class CustomPasswordHasher implements UserPasswordHasherInterface
{
    // ...

    public function needsRehash(string $hashed): bool
    {
        // перевірити, чи хешовано поточний пароль з використанням застарілого хешувальника
        $hashIsOutdated = ...;

        return $hashIsOutdated;
    }
}

Іменовані хешувальники паролів

Зазвичай, один і той же хешувальник паролів використовується для всіх користувачів, шляхом його конфігурації із застосуванням до усіх екземплярів конкретного класу. Інша опція - використовувати "іменований" хешувальник, а потім обирати, який хешувальник ви хочете використати, динамічно.

За замовчуванням (як показано на початку цієї статті), для App\Entity\User використовується алгоритм auto.

Це може бути достатньо безпечно для звичайного користувача, але що, якщо ви хочете, щоб у ваших адмінів був алгоритм потужніше, наприклад, auto з великою вартістю. Це може бути зроблено за допомогою іменованих хешувальників:

  • YAML
  • XML
  • PHP
  • Standalone Use
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; // використати хешувальник за замовчуванням
    }
}

Якщо ви створили власний хешувальник паролів, що реалізує PasswordHasherInterface, ви повинні зареєструвати для нього сервіс, щоб використовувати його як іменований хешувальник:

  • YAML
  • XML
  • PHP
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.

Підтримувані алгоритми

  • auto
  • bcrypt
  • sodium
  • PBKDF2
  • Message Digest
  • Native
  • Plaintext

Хешувальник "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. Успадковані додатки, які все ще використовують його, рекомендовано оновоити до новіших алгоритмів хешування.

Створення користувацького хешувальника паролів

Якщо вам потрібно створити свій власний, він має дотримуватись цих правил:

  1. Клас має реалізовувати PasswordHasherInterface (ви також можете реалізувати LegacyPasswordHasherInterface, якщо ваш алгоритм хешу використовує окрему сіль);
  2. Реалізації 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:

  • YAML
  • XML
  • PHP
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'