SodiumPasswordEncoder.php 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Security\Core\Encoder;
  11. use Symfony\Component\Security\Core\Exception\BadCredentialsException;
  12. use Symfony\Component\Security\Core\Exception\LogicException;
  13. /**
  14. * Hashes passwords using libsodium.
  15. *
  16. * @author Robin Chalas <robin.chalas@gmail.com>
  17. * @author Zan Baldwin <hello@zanbaldwin.com>
  18. * @author Dominik Müller <dominik.mueller@jkweb.ch>
  19. */
  20. final class SodiumPasswordEncoder implements PasswordEncoderInterface, SelfSaltingEncoderInterface
  21. {
  22. private const MAX_PASSWORD_LENGTH = 4096;
  23. private $opsLimit;
  24. private $memLimit;
  25. public function __construct(int $opsLimit = null, int $memLimit = null)
  26. {
  27. if (!self::isSupported()) {
  28. throw new LogicException('Libsodium is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.');
  29. }
  30. $this->opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4);
  31. $this->memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 1024);
  32. if (3 > $this->opsLimit) {
  33. throw new \InvalidArgumentException('$opsLimit must be 3 or greater.');
  34. }
  35. if (10 * 1024 > $this->memLimit) {
  36. throw new \InvalidArgumentException('$memLimit must be 10k or greater.');
  37. }
  38. }
  39. public static function isSupported(): bool
  40. {
  41. return version_compare(\extension_loaded('sodium') ? \SODIUM_LIBRARY_VERSION : phpversion('libsodium'), '1.0.14', '>=');
  42. }
  43. /**
  44. * {@inheritdoc}
  45. */
  46. public function encodePassword(string $raw, ?string $salt): string
  47. {
  48. if (\strlen($raw) > self::MAX_PASSWORD_LENGTH) {
  49. throw new BadCredentialsException('Invalid password.');
  50. }
  51. if (\function_exists('sodium_crypto_pwhash_str')) {
  52. return sodium_crypto_pwhash_str($raw, $this->opsLimit, $this->memLimit);
  53. }
  54. if (\extension_loaded('libsodium')) {
  55. return \Sodium\crypto_pwhash_str($raw, $this->opsLimit, $this->memLimit);
  56. }
  57. throw new LogicException('Libsodium is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.');
  58. }
  59. /**
  60. * {@inheritdoc}
  61. */
  62. public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool
  63. {
  64. if ('' === $raw) {
  65. return false;
  66. }
  67. if (\strlen($raw) > self::MAX_PASSWORD_LENGTH) {
  68. return false;
  69. }
  70. if (0 !== strpos($encoded, '$argon')) {
  71. // Accept validating non-argon passwords for seamless migrations
  72. return (72 >= \strlen($raw) || 0 !== strpos($encoded, '$2')) && password_verify($raw, $encoded);
  73. }
  74. if (\function_exists('sodium_crypto_pwhash_str_verify')) {
  75. return sodium_crypto_pwhash_str_verify($encoded, $raw);
  76. }
  77. if (\extension_loaded('libsodium')) {
  78. return \Sodium\crypto_pwhash_str_verify($encoded, $raw);
  79. }
  80. return false;
  81. }
  82. /**
  83. * {@inheritdoc}
  84. */
  85. public function needsRehash(string $encoded): bool
  86. {
  87. if (\function_exists('sodium_crypto_pwhash_str_needs_rehash')) {
  88. return sodium_crypto_pwhash_str_needs_rehash($encoded, $this->opsLimit, $this->memLimit);
  89. }
  90. if (\extension_loaded('libsodium')) {
  91. return \Sodium\crypto_pwhash_str_needs_rehash($encoded, $this->opsLimit, $this->memLimit);
  92. }
  93. throw new LogicException('Libsodium is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.');
  94. }
  95. }