PhpassHashedPassword.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. <?php
  2. namespace Drupal\Core\Password;
  3. /**
  4. * Secure password hashing functions based on the Portable PHP password
  5. * hashing framework.
  6. *
  7. * @see http://www.openwall.com/phpass/
  8. */
  9. class PhpassHashedPassword implements PasswordInterface {
  10. /**
  11. * The minimum allowed log2 number of iterations for password stretching.
  12. */
  13. const MIN_HASH_COUNT = 7;
  14. /**
  15. * The maximum allowed log2 number of iterations for password stretching.
  16. */
  17. const MAX_HASH_COUNT = 30;
  18. /**
  19. * The expected (and maximum) number of characters in a hashed password.
  20. */
  21. const HASH_LENGTH = 55;
  22. /**
  23. * Returns a string for mapping an int to the corresponding base 64 character.
  24. *
  25. * @var string
  26. */
  27. public static $ITOA64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  28. /**
  29. * Specifies the number of times the hashing function will be applied when
  30. * generating new password hashes. The number of times is calculated by
  31. * raising 2 to the power of the given value.
  32. *
  33. * @var int
  34. */
  35. protected $countLog2;
  36. /**
  37. * Constructs a new password hashing instance.
  38. *
  39. * @param int $countLog2
  40. * Password stretching iteration count. Specifies the number of times the
  41. * hashing function will be applied when generating new password hashes.
  42. * The number of times is calculated by raising 2 to the power of the given
  43. * value.
  44. */
  45. public function __construct($countLog2) {
  46. // Ensure that $countLog2 is within set bounds.
  47. $this->countLog2 = $this->enforceLog2Boundaries($countLog2);
  48. }
  49. /**
  50. * Encodes bytes into printable base 64 using the *nix standard from crypt().
  51. *
  52. * @param string $input
  53. * The string containing bytes to encode.
  54. * @param int $count
  55. * The number of characters (bytes) to encode.
  56. *
  57. * @return string
  58. * Encoded string.
  59. */
  60. protected function base64Encode($input, $count) {
  61. $output = '';
  62. $i = 0;
  63. do {
  64. $value = ord($input[$i++]);
  65. $output .= static::$ITOA64[$value & 0x3f];
  66. if ($i < $count) {
  67. $value |= ord($input[$i]) << 8;
  68. }
  69. $output .= static::$ITOA64[($value >> 6) & 0x3f];
  70. if ($i++ >= $count) {
  71. break;
  72. }
  73. if ($i < $count) {
  74. $value |= ord($input[$i]) << 16;
  75. }
  76. $output .= static::$ITOA64[($value >> 12) & 0x3f];
  77. if ($i++ >= $count) {
  78. break;
  79. }
  80. $output .= static::$ITOA64[($value >> 18) & 0x3f];
  81. } while ($i < $count);
  82. return $output;
  83. }
  84. /**
  85. * Generates a random base 64-encoded salt prefixed with hash settings.
  86. *
  87. * Proper use of salts may defeat a number of attacks, including:
  88. * - The ability to try candidate passwords against multiple hashes at once.
  89. * - The ability to use pre-hashed lists of candidate passwords.
  90. * - The ability to determine whether two users have the same (or different)
  91. * password without actually having to guess one of the passwords.
  92. *
  93. * @return string
  94. * A 12 character string containing the iteration count and a random salt.
  95. */
  96. protected function generateSalt() {
  97. $output = '$S$';
  98. // We encode the final log2 iteration count in base 64.
  99. $output .= static::$ITOA64[$this->countLog2];
  100. // 6 bytes is the standard salt for a portable phpass hash.
  101. $output .= $this->base64Encode(random_bytes(6), 6);
  102. return $output;
  103. }
  104. /**
  105. * Ensures that $count_log2 is within set bounds.
  106. *
  107. * @param int $count_log2
  108. * Integer that determines the number of iterations used in the hashing
  109. * process. A larger value is more secure, but takes more time to complete.
  110. *
  111. * @return int
  112. * Integer within set bounds that is closest to $count_log2.
  113. */
  114. protected function enforceLog2Boundaries($count_log2) {
  115. if ($count_log2 < static::MIN_HASH_COUNT) {
  116. return static::MIN_HASH_COUNT;
  117. }
  118. elseif ($count_log2 > static::MAX_HASH_COUNT) {
  119. return static::MAX_HASH_COUNT;
  120. }
  121. return (int) $count_log2;
  122. }
  123. /**
  124. * Hash a password using a secure stretched hash.
  125. *
  126. * By using a salt and repeated hashing the password is "stretched". Its
  127. * security is increased because it becomes much more computationally costly
  128. * for an attacker to try to break the hash by brute-force computation of the
  129. * hashes of a large number of plain-text words or strings to find a match.
  130. *
  131. * @param string $algo
  132. * The string name of a hashing algorithm usable by hash(), like 'sha256'.
  133. * @param string $password
  134. * Plain-text password up to 512 bytes (128 to 512 UTF-8 characters) to
  135. * hash.
  136. * @param string $setting
  137. * An existing hash or the output of $this->generateSalt(). Must be at least
  138. * 12 characters (the settings and salt).
  139. *
  140. * @return string
  141. * A string containing the hashed password (and salt) or FALSE on failure.
  142. * The return string will be truncated at HASH_LENGTH characters max.
  143. */
  144. protected function crypt($algo, $password, $setting) {
  145. // Prevent DoS attacks by refusing to hash large passwords.
  146. if (strlen($password) > PasswordInterface::PASSWORD_MAX_LENGTH) {
  147. return FALSE;
  148. }
  149. // The first 12 characters of an existing hash are its setting string.
  150. $setting = substr($setting, 0, 12);
  151. if ($setting[0] != '$' || $setting[2] != '$') {
  152. return FALSE;
  153. }
  154. $count_log2 = $this->getCountLog2($setting);
  155. // Stored hashes may have been crypted with any iteration count. However we
  156. // do not allow applying the algorithm for unreasonable low and high values
  157. // respectively.
  158. if ($count_log2 != $this->enforceLog2Boundaries($count_log2)) {
  159. return FALSE;
  160. }
  161. $salt = substr($setting, 4, 8);
  162. // Hashes must have an 8 character salt.
  163. if (strlen($salt) != 8) {
  164. return FALSE;
  165. }
  166. // Convert the base 2 logarithm into an integer.
  167. $count = 1 << $count_log2;
  168. $hash = hash($algo, $salt . $password, TRUE);
  169. do {
  170. $hash = hash($algo, $hash . $password, TRUE);
  171. } while (--$count);
  172. $len = strlen($hash);
  173. $output = $setting . $this->base64Encode($hash, $len);
  174. // $this->base64Encode() of a 16 byte MD5 will always be 22 characters.
  175. // $this->base64Encode() of a 64 byte sha512 will always be 86 characters.
  176. $expected = 12 + ceil((8 * $len) / 6);
  177. return (strlen($output) == $expected) ? substr($output, 0, static::HASH_LENGTH) : FALSE;
  178. }
  179. /**
  180. * Parses the log2 iteration count from a stored hash or setting string.
  181. *
  182. * @param string $setting
  183. * An existing hash or the output of $this->generateSalt(). Must be at least
  184. * 12 characters (the settings and salt).
  185. *
  186. * @return int
  187. * The log2 iteration count.
  188. */
  189. public function getCountLog2($setting) {
  190. return strpos(static::$ITOA64, $setting[3]);
  191. }
  192. /**
  193. * {@inheritdoc}
  194. */
  195. public function hash($password) {
  196. return $this->crypt('sha512', $password, $this->generateSalt());
  197. }
  198. /**
  199. * {@inheritdoc}
  200. */
  201. public function check($password, $hash) {
  202. if (substr($hash, 0, 2) == 'U$') {
  203. // This may be an updated password from user_update_7000(). Such hashes
  204. // have 'U' added as the first character and need an extra md5() (see the
  205. // Drupal 7 documentation).
  206. $stored_hash = substr($hash, 1);
  207. $password = md5($password);
  208. }
  209. else {
  210. $stored_hash = $hash;
  211. }
  212. $type = substr($stored_hash, 0, 3);
  213. switch ($type) {
  214. case '$S$':
  215. // A normal Drupal 7 password using sha512.
  216. $computed_hash = $this->crypt('sha512', $password, $stored_hash);
  217. break;
  218. case '$H$':
  219. // phpBB3 uses "$H$" for the same thing as "$P$".
  220. case '$P$':
  221. // A phpass password generated using md5. This is an
  222. // imported password or from an earlier Drupal version.
  223. $computed_hash = $this->crypt('md5', $password, $stored_hash);
  224. break;
  225. default:
  226. return FALSE;
  227. }
  228. // Compare using hash_equals() instead of === to mitigate timing attacks.
  229. return $computed_hash && hash_equals($stored_hash, $computed_hash);
  230. }
  231. /**
  232. * {@inheritdoc}
  233. */
  234. public function needsRehash($hash) {
  235. // Check whether this was an updated password.
  236. if ((substr($hash, 0, 3) != '$S$') || (strlen($hash) != static::HASH_LENGTH)) {
  237. return TRUE;
  238. }
  239. // Ensure that $count_log2 is within set bounds.
  240. $count_log2 = $this->enforceLog2Boundaries($this->countLog2);
  241. // Check whether the iteration count used differs from the standard number.
  242. return ($this->getCountLog2($hash) !== $count_log2);
  243. }
  244. }