Password.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. <?php
  2. /**
  3. * Implements the Password class for the MediaWiki software.
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. */
  22. use Wikimedia\Assert\Assert;
  23. /**
  24. * Represents a password hash for use in authentication
  25. *
  26. * Note: All password types are transparently prefixed with :<TYPE>:, where <TYPE>
  27. * is the registered type of the hash. This prefix is stripped in the constructor
  28. * and is added back in the toString() function.
  29. *
  30. * When inheriting this class, there are a couple of expectations
  31. * to be fulfilled:
  32. * * If Password::toString() is called on an object, and the result is passed back in
  33. * to PasswordFactory::newFromCiphertext(), the result will be identical to the original.
  34. * With these two points in mind, when creating a new Password sub-class, there are some functions
  35. * you have to override (because they are abstract) and others that you may want to override.
  36. *
  37. * The abstract functions that must be overridden are:
  38. * * Password::crypt(), which takes a plaintext password and hashes it into a string hash suitable
  39. * for being passed to the constructor of that class, and then stores that hash (and whatever
  40. * other data) into the internal state of the object.
  41. * The functions that can optionally be overridden are:
  42. * * Password::parseHash(), which can be useful to override if you need to extract values from or
  43. * otherwise parse a password hash when it's passed to the constructor.
  44. * * Password::needsUpdate(), which can be useful if a specific password hash has different
  45. * logic for when the hash needs to be updated.
  46. * * Password::toString(), which can be useful if the hash was changed in the constructor and
  47. * needs to be re-assembled before being returned as a string. This function is expected to add
  48. * the type back on to the hash, so make sure to do that if you override the function.
  49. * * Password::verify() - This function checks if $this->hash was generated with the given
  50. * password. The default is to just hash the password and do a timing-safe string comparison with
  51. * $this->hash.
  52. *
  53. * After creating a new password hash type, it can be registered using the static
  54. * Password::register() method. The default type is set using the Password::setDefaultType() type.
  55. * Types must be registered before they can be set as the default.
  56. *
  57. * @since 1.24
  58. */
  59. abstract class Password {
  60. /**
  61. * @var PasswordFactory Factory that created the object
  62. */
  63. protected $factory;
  64. /**
  65. * String representation of the hash without the type
  66. * @var string
  67. */
  68. protected $hash;
  69. /**
  70. * Array of configuration variables injected from the constructor
  71. * @var array
  72. */
  73. protected $config;
  74. /**
  75. * Hash must fit in user_password, which is a tinyblob
  76. */
  77. const MAX_HASH_SIZE = 255;
  78. /**
  79. * Construct the Password object using a string hash
  80. *
  81. * It is strongly recommended not to call this function directly unless you
  82. * have a reason to. Use the PasswordFactory class instead.
  83. *
  84. * @throws MWException If $config does not contain required parameters
  85. *
  86. * @param PasswordFactory $factory Factory object that created the password
  87. * @param array $config Array of engine configuration options for hashing
  88. * @param string|null $hash The raw hash, including the type
  89. */
  90. final public function __construct( PasswordFactory $factory, array $config, $hash = null ) {
  91. if ( !$this->isSupported() ) {
  92. throw new Exception( 'PHP support not found for ' . get_class( $this ) );
  93. }
  94. if ( !isset( $config['type'] ) ) {
  95. throw new Exception( 'Password configuration must contain a type name.' );
  96. }
  97. $this->config = $config;
  98. $this->factory = $factory;
  99. if ( $hash !== null && strlen( $hash ) >= 3 ) {
  100. // Strip the type from the hash for parsing
  101. $hash = substr( $hash, strpos( $hash, ':', 1 ) + 1 );
  102. }
  103. $this->hash = $hash;
  104. $this->parseHash( $hash );
  105. }
  106. /**
  107. * Get the type name of the password
  108. *
  109. * @return string Password type
  110. */
  111. final public function getType() {
  112. return $this->config['type'];
  113. }
  114. /**
  115. * Whether current password type is supported on this system.
  116. *
  117. * @return bool
  118. */
  119. protected function isSupported() {
  120. return true;
  121. }
  122. /**
  123. * Perform any parsing necessary on the hash to see if the hash is valid
  124. * and/or to perform logic for seeing if the hash needs updating.
  125. *
  126. * @param string $hash The hash, with the :<TYPE>: prefix stripped
  127. * @throws PasswordError If there is an error in parsing the hash
  128. */
  129. protected function parseHash( $hash ) {
  130. }
  131. /**
  132. * Determine if the hash needs to be updated
  133. *
  134. * @return bool True if needs update, false otherwise
  135. */
  136. abstract public function needsUpdate();
  137. /**
  138. * Compare one Password object to this object
  139. *
  140. * By default, do a timing-safe string comparison on the result of
  141. * Password::toString() for each object. This can be overridden to do
  142. * custom comparison, but it is not recommended unless necessary.
  143. *
  144. * @deprecated since 1.33, use verify()
  145. * @codeCoverageIgnore
  146. *
  147. * @param Password|string $other The other password
  148. * @return bool True if equal, false otherwise
  149. */
  150. public function equals( $other ) {
  151. wfDeprecated( __METHOD__, '1.33' );
  152. if ( is_string( $other ) ) {
  153. return $this->verify( $other );
  154. }
  155. return hash_equals( $this->toString(), $other->toString() );
  156. }
  157. /**
  158. * Checks whether the given password matches the hash stored in this object.
  159. *
  160. * @param string $password Password to check
  161. * @return bool
  162. */
  163. public function verify( $password ) {
  164. Assert::parameterType( 'string', $password, '$password' );
  165. // No need to use the factory because we're definitely making
  166. // an object of the same type.
  167. $obj = clone $this;
  168. $obj->crypt( $password );
  169. return hash_equals( $this->toString(), $obj->toString() );
  170. }
  171. /**
  172. * Convert this hash to a string that can be stored in the database
  173. *
  174. * The resulting string should be considered the seralized representation
  175. * of this hash, i.e., if the return value were recycled back into
  176. * PasswordFactory::newFromCiphertext, the returned object would be equivalent to
  177. * this; also, if two objects return the same value from this function, they
  178. * are considered equivalent.
  179. *
  180. * @return string
  181. * @throws PasswordError if password cannot be serialized to fit a tinyblob.
  182. */
  183. public function toString() {
  184. $result = ':' . $this->config['type'] . ':' . $this->hash;
  185. $this->assertIsSafeSize( $result );
  186. return $result;
  187. }
  188. /**
  189. * Assert that hash will fit in a tinyblob field.
  190. *
  191. * This prevents MW from inserting it into the DB
  192. * and having MySQL silently truncating it, locking
  193. * the user out of their account.
  194. *
  195. * @param string $hash The hash in question.
  196. * @throws PasswordError If hash does not fit in DB.
  197. */
  198. final protected function assertIsSafeSize( $hash ) {
  199. if ( strlen( $hash ) > self::MAX_HASH_SIZE ) {
  200. throw new PasswordError( "Password hash is too big" );
  201. }
  202. }
  203. /**
  204. * Hash a password and store the result in this object
  205. *
  206. * The result of the password hash should be put into the internal
  207. * state of the hash object.
  208. *
  209. * @param string $password Password to hash
  210. * @throws PasswordError If an internal error occurs in hashing
  211. */
  212. abstract public function crypt( $password );
  213. }