Хеширование и верификация паролей

Дата обновления перевода 2022-02-04

Хеширование и верификация паролей

Большинство приложений используют пароли для допуска пользователей в систему. Эти пароли должны быть хешированы, чтобы храниться безопасно. Компонент Symfony PasswordHasher предоставляет все инструменты, чтобы безопасно хешировать и верифицировать пароли.

Убедитесь в том, что он установлен, выполнив:

1
$ composer require symfony/password-hasher

5.3

Компонент PasswordHasher был представлен в версии 5.3. До этой версии, функционал хеширования паролей предоставлялся компонентом Безопасность.

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

Перед хешированием паролей, вы должны сконфигурировать хешировщик, используя опцию 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

5.3

Опция password_hashers была представлена в Symfony 5.3. В предыдущих версиях она называлась encoders.

В данном примере используется алгоритм "авто". Этот хешировщик автоматически выбирает наиболее безопасный алгоритм, доступный в вашей системе. Вместе с миграцией паролей, это позволяет вам всегда максимально обезопашивать ваши пароли (даже при условии появления новых алгоритмов в последующих релизах 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.

Поддерживаемые алгоритмы

  • 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. Наследуемые приложения, все еще использующие его, рекомендуется обновить до более новых алгоритмов хеширования.