Password Hashing and Verification ¶
Most applications use passwords to login users. These passwords should be hashed to securely store them. Symfony's PasswordHasher component provides all utilities to safely hash and verify passwords.
Make sure it is installed by running:
1 |
$ composer require symfony/password-hasher
|
Configuring a Password Hasher ¶
Before hashing passwords, you must configure a hasher using the
password_hashers
option. You must configure the hashing algorithm and
optionally some algorithm options:
-
YAML
YAML
-
XML
XML
-
PHP
PHP
-
Standalone Use
スタンドアロン使用
1 2 3 4 5 6 7 8 9 10 11 12 |
# config/packages/security.yaml
security:
# ...
password_hashers:
# auto hasher with default options for the User class (and children)
App\Entity\User: 'auto'
# auto hasher with custom options for all PasswordAuthenticatedUserInterface instances
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: 'auto'
cost: 15
|
In this example, the "auto" algorithm is used. This hasher automatically selects the most secure algorithm available on your system. Combined with password migration, this allows you to always secure passwords in the safest way possible (even when new algorithms are introduced in future PHP releases).
Further in this article, you can find a full reference of all supported algorithms.
Tip
Hashing passwords is resource intensive and takes time in order to generate secure password hashes. In general, this makes your password hashing more secure.
In tests however, secure hashes are not important, so you can change
the password hasher configuration in test
environment to run tests
faster:
-
YAML
YAML
-
XML
XML
-
PHP
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# config/packages/test/security.yaml
security:
# ...
password_hashers:
# Use your user class name here
App\Entity\User:
algorithm: plaintext # disable hashing (only do this in tests!)
# or use the lowest possible values
App\Entity\User:
algorithm: auto # This should be the same value as in config/packages/security.yaml
cost: 4 # Lowest possible value for bcrypt
time_cost: 3 # Lowest possible value for argon
memory_cost: 10 # Lowest possible value for argon
|
Hashing the Password ¶
After configuring the correct algorithm, you can use the
UserPasswordHasherInterface
to hash and verify the passwords:
-
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)
{
// ... e.g. get the user data from a registration form
$user = new User(...);
$plaintextPassword = ...;
// hash the password (based on the security.yaml config for the $user class)
$hashedPassword = $passwordHasher->hashPassword(
$user,
$plaintextPassword
);
$user->setPassword($hashedPassword);
// ...
}
public function delete(UserPasswordHasherInterface $passwordHasher, UserInterface $user)
{
// ... e.g. get the password from a "confirm deletion" dialog
$plaintextPassword = ...;
if (!$passwordHasher->isPasswordValid($user, $plaintextPassword)) {
throw new AccessDeniedHttpException();
}
}
}
|
Reset Password ¶
Using MakerBundle and SymfonyCastsResetPasswordBundle, you can create a secure out of the box solution to handle forgotten passwords. First, install the SymfonyCastsResetPasswordBundle:
1 |
$ composer require symfonycasts/reset-password-bundle
|
Then, use the make:reset-password
command. This asks you a few
questions about your app and generates all the files you need! After,
you'll see a success message and a list of any other steps you need to do.
1 |
$ php bin/console make:reset-password
|
You can customize the reset password bundle's behavior by updating the
reset_password.yaml
file. For more information on the configuration,
check out the SymfonyCastsResetPasswordBundle guide.
Password Migration ¶
In order to protect passwords, it is recommended to store them using the latest
hash algorithms. This means that if a better hash algorithm is supported on your
system, the user's password should be rehashed using the newer algorithm and
stored. That's possible with the migrate_from
option:
- Configure a new Hasher Using "migrate_from"「migrate_from」を使用して新しい Hasher を構成する
- Upgrade the Passwordパスワードのアップグレード
- Optionally, Trigger Password Migration From a Custom Hasherオプションで、カスタム ハッシャーからパスワード移行をトリガーする
Configure a new Hasher Using "migrate_from" ¶
When a better hashing algorithm becomes available, you should keep the existing
hasher(s), rename it, and then define the new one. Set the migrate_from
option
on the new hasher to point to the old, legacy hasher(s):
-
YAML
YAML
-
XML
XML
-
PHP
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:
# a hasher used in the past for some users
legacy:
algorithm: sha256
encode_as_base64: false
iterations: 1
App\Entity\User:
# the new hasher, along with its options
algorithm: sodium
migrate_from:
- bcrypt # uses the "bcrypt" hasher with the default options
- legacy # uses the "legacy" hasher configured above
|
With this setup:
- New users will be hashed with the new algorithm;新しいユーザーは新しいアルゴリズムでハッシュされます。
- Whenever a user logs in whose password is still stored using the old algorithm,
Symfony will verify the password with the old algorithm and then rehash
and update the password using the new algorithm.パスワードがまだ古いアルゴリズムを使用して保存されているユーザーがログインするたびに、Symfony は古いアルゴリズムでパスワードを検証し、新しいアルゴリズムを使用してパスワードを更新し直します。
Tip
The auto, native, bcrypt and argon hashers automatically enable
password migration using the following list of migrate_from
algorithms:
- PBKDF2 (which uses hash_pbkdf2);PBKDF2 (hash_pbkdf2 を使用);
- Message digest (which uses hash)メッセージ ダイジェスト (ハッシュを使用)
Both use the hash_algorithm
setting as the algorithm. It is recommended to
use migrate_from
instead of hash_algorithm
, unless the auto
hasher is used.
Upgrade the Password ¶
Upon successful login, the Security system checks whether a better algorithm
is available to hash the user's password. If it is, it'll hash the correct
password using the new hash. When using a custom authenticator, you must
use the PasswordCredentials
in the security passport.
You can enable the upgrade behavior by implementing how this newly hashed password should be stored:
- When using Doctrine's entity user providerDoctrine のエンティティ ユーザー プロバイダーを使用する場合
- When using a custom user providerカスタム ユーザー プロバイダーを使用する場合
After this, you're done and passwords are always hashed as securely as possible!
Note
When using the PasswordHasher component outside a Symfony application,
you must manually use the PasswordHasherInterface::needsRehash()
method to check if a rehash is needed and PasswordHasherInterface::hash()
method to rehash the plaintext password using the new algorithm.
Upgrade the Password when using Doctrine ¶
When using the entity user provider, implement
PasswordUpgraderInterface in
the UserRepository
(see the Doctrine docs for information on how to
create this class if it's not already created). This interface implements
storing the newly created password hash:
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
{
// set the new hashed password on the User object
$user->setPassword($newHashedPassword);
// execute the queries on the database
$this->getEntityManager()->flush();
}
}
|
Upgrade the Password when using a Custom User Provider ¶
If you're using a custom user provider, implement the PasswordUpgraderInterface in the user provider:
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
{
// set the new hashed password on the User object
$user->setPassword($newHashedPassword);
// ... store the new password
}
}
|
Trigger Password Migration From a Custom Hasher ¶
If you're using a custom password hasher, you can trigger the password
migration by returning true
in the needsRehash()
method:
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
{
// check whether the current password is hashed using an outdated hasher
$hashIsOutdated = ...;
return $hashIsOutdated;
}
}
|
Dynamic Password Hashers ¶
Usually, the same password hasher is used for all users by configuring it to apply to all instances of a specific class. Another option is to use a "named" hasher and then select which hasher you want to use dynamically.
By default (as shown at the start of the article), the auto
algorithm
is used for App\Entity\User
.
This may be secure enough for a regular user, but what if you want your
admins to have a stronger algorithm, for example auto
with a higher
cost. This can be done with named hashers:
-
YAML
YAML
-
XML
XML
-
PHP
PHP
-
Standalone Use
スタンドアロン使用
1 2 3 4 5 6 7 |
# config/packages/security.yaml
security:
# ...
password_hashers:
harsh:
algorithm: auto
cost: 15
|
This creates a hasher named harsh
. In order for a User
instance
to use it, the class must implement
PasswordHasherAwareInterface.
The interface requires one method - getPasswordHasherName()
- which should return
the name of the hasher to use:
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; // use the default hasher
}
}
|
Caution
When migrating passwords, you don't
need to implement PasswordHasherAwareInterface
to return the legacy
hasher name: Symfony will detect it from your migrate_from
configuration.
If you created your own password hasher implementing the PasswordHasherInterface, you must register a service for it in order to use it as a named hasher:
-
YAML
YAML
-
XML
XML
-
PHP
PHP
1 2 3 4 5 6 |
# config/packages/security.yaml
security:
# ...
password_hashers:
app_hasher:
id: 'App\Security\Hasher\MyCustomPasswordHasher'
|
This creates a hasher named app_hasher
from a service with the ID
App\Security\Hasher\MyCustomPasswordHasher
.
Supported Algorithms ¶
- auto自動
- bcryptbcrypt
- sodiumナトリウム
- PBKDF2PBKDF2
- Or create a custom password hasherまたは、カスタム パスワード ハッシャーを作成します
The "auto" Hasher ¶
It automatically selects the best available hasher (currently Bcrypt). If PHP or Symfony adds new password hashers in the future, it might select a different hasher.
Because of this, the length of the hashed passwords may change in the future, so
make sure to allocate enough space for them to be persisted (varchar(255)
should be a good setting).
The Bcrypt Password Hasher ¶
It produces hashed passwords with the bcrypt password hashing function.
Hashed passwords are 60
characters long, so make sure to
allocate enough space for them to be persisted. Also, passwords include the
cryptographic salt inside them (it's generated automatically for each new
password) so you don't have to deal with it.
Its only configuration option is cost
, which is an integer in the range of
4-31
(by default, 13
). Each single increment of the cost doubles the
time it takes to hash a password. It's designed this way so the password
strength can be adapted to the future improvements in computation power.
You can change the cost at any time — even if you already have some passwords hashed using a different cost. New passwords will be hashed using the new cost, while the already hashed ones will be validated using a cost that was used back when they were hashed.
Tip
A simple technique to make tests much faster when using BCrypt is to set
the cost to 4
, which is the minimum value allowed, in the test
environment configuration.
The Sodium Password Hasher ¶
It uses the Argon2 key derivation function. Argon2 support was introduced in PHP 7.2 by bundling the libsodium extension.
The hashed passwords are 96
characters long, but due to the hashing
requirements saved in the resulting hash this may change in the future, so make
sure to allocate enough space for them to be persisted. Also, passwords include
the cryptographic salt inside them (it's generated automatically for each new
password) so you don't have to deal with it.
The PBKDF2 Hasher ¶
Using the PBKDF2 hasher is no longer recommended since PHP added support for Sodium and BCrypt. Legacy application still using it are encouraged to upgrade to those newer hashing algorithms.
Creating a custom Password Hasher ¶
If you need to create your own, it needs to follow these rules:
- The class must implement PasswordHasherInterface
(you can also implement LegacyPasswordHasherInterface if your hash algorithm uses a separate salt);クラスは PasswordHasherInterface を実装する必要があります (ハッシュ アルゴリズムが別のソルトを使用する場合は、LegacyPasswordHasherInterface も実装できます)。
The implementations of hash() and verify() must validate that the password length is no longer than 4096 characters. This is for security reasons (see CVE-2013-5750).
hash() および verify() の実装では、パスワードの長さが 4096 文字を超えていないことを検証する必要があります。これはセキュリティ上の理由によるものです (CVE-2013-5750 を参照)。You can use the isPasswordTooLong() method for this check.
このチェックには 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();
}
// ... hash the plain password in a secure way
return $hashedPassword;
}
public function verify(string $hashedPassword, string $plainPassword): bool
{
if ('' === $plainPassword || $this->isPasswordTooLong($plainPassword)) {
return false;
}
// ... validate if the password equals the user's password in a secure way
return $passwordIsValid;
}
}
|
Now, define a password hasher using the id
setting:
-
YAML
YAML
-
XML
XML
-
PHP
PHP
1 2 3 4 5 6 7 |
# config/packages/security.yaml
security:
# ...
password_hashers:
app_hasher:
# the service ID of your custom hasher (the FQCN using the default services.yaml)
id: 'App\Security\Hasher\MyCustomPasswordHasher'
|