Argon2Password.php 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. <?php
  2. use Wikimedia\Assert\Assert;
  3. /**
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License along
  15. * with this program; if not, write to the Free Software Foundation, Inc.,
  16. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  17. * http://www.gnu.org/copyleft/gpl.html
  18. *
  19. * @file
  20. */
  21. /**
  22. * Implements Argon2, a modern key derivation algorithm designed to resist GPU cracking and
  23. * side-channel attacks.
  24. *
  25. * @see https://en.wikipedia.org/wiki/Argon2
  26. */
  27. class Argon2Password extends Password {
  28. /**
  29. * @var null[] Array with known password_hash() option names as keys
  30. */
  31. private static $knownOptions = [
  32. 'memory_cost' => null,
  33. 'time_cost' => null,
  34. 'threads' => null,
  35. ];
  36. /**
  37. * @inheritDoc
  38. */
  39. protected function isSupported() {
  40. // It is actually possible to have a PHP build with Argon2i but not Argon2id
  41. return defined( 'PASSWORD_ARGON2I' ) || defined( 'PASSWORD_ARGON2ID' );
  42. }
  43. /**
  44. * @return mixed[] Array of 2nd and third parmeters to password_hash()
  45. */
  46. private function prepareParams() {
  47. switch ( $this->config['algo'] ) {
  48. case 'argon2i':
  49. $algo = PASSWORD_ARGON2I;
  50. break;
  51. case 'argon2id':
  52. $algo = PASSWORD_ARGON2ID;
  53. break;
  54. case 'auto':
  55. $algo = defined( 'PASSWORD_ARGON2ID' ) ? PASSWORD_ARGON2ID : PASSWORD_ARGON2I;
  56. break;
  57. default:
  58. throw new LogicException( "Unexpected algo: {$this->config['algo']}" );
  59. }
  60. $params = array_intersect_key( $this->config, self::$knownOptions );
  61. return [ $algo, $params ];
  62. }
  63. /**
  64. * @inheritDoc
  65. */
  66. public function crypt( $password ) {
  67. list( $algo, $params ) = $this->prepareParams();
  68. $this->hash = password_hash( $password, $algo, $params );
  69. }
  70. /**
  71. * @inheritDoc
  72. */
  73. public function equals( $other ) {
  74. wfDeprecated( __METHOD__, '1.33' );
  75. if ( is_string( $other ) ) {
  76. return $this->verify( $other );
  77. }
  78. // Argon2 key derivation is not deterministic, can't pass objects to equals()
  79. return false;
  80. }
  81. /**
  82. * @inheritDoc
  83. */
  84. public function verify( $password ) {
  85. Assert::parameterType( 'string', $password, '$password' );
  86. return password_verify( $password, $this->hash );
  87. }
  88. /**
  89. * @inheritDoc
  90. */
  91. public function toString() {
  92. $res = ":argon2:{$this->hash}";
  93. $this->assertIsSafeSize( $res );
  94. return $res;
  95. }
  96. /**
  97. * @inheritDoc
  98. */
  99. public function needsUpdate() {
  100. list( $algo, $params ) = $this->prepareParams();
  101. return password_needs_rehash( $this->hash, $algo, $params );
  102. }
  103. }