PhpassHashedPassword.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. <?php
  2. namespace Drupal\Core\Password;
  3. use Drupal\Component\Utility\Crypt;
  4. /**
  5. * Secure password hashing functions based on the Portable PHP password
  6. * hashing framework.
  7. *
  8. * @see http://www.openwall.com/phpass/
  9. */
  10. class PhpassHashedPassword implements PasswordInterface {
  11. /**
  12. * The minimum allowed log2 number of iterations for password stretching.
  13. */
  14. const MIN_HASH_COUNT = 7;
  15. /**
  16. * The maximum allowed log2 number of iterations for password stretching.
  17. */
  18. const MAX_HASH_COUNT = 30;
  19. /**
  20. * The expected (and maximum) number of characters in a hashed password.
  21. */
  22. const HASH_LENGTH = 55;
  23. /**
  24. * Returns a string for mapping an int to the corresponding base 64 character.
  25. *
  26. * @var string
  27. */
  28. public static $ITOA64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  29. /**
  30. * Specifies the number of times the hashing function will be applied when
  31. * generating new password hashes. The number of times is calculated by
  32. * raising 2 to the power of the given value.
  33. */
  34. protected $countLog2;
  35. /**
  36. * Constructs a new password hashing instance.
  37. *
  38. * @param int $countLog2
  39. * Password stretching iteration count. Specifies the number of times the
  40. * hashing function will be applied when generating new password hashes.
  41. * The number of times is calculated by raising 2 to the power of the given
  42. * value.
  43. */
  44. public function __construct($countLog2) {
  45. // Ensure that $countLog2 is within set bounds.
  46. $this->countLog2 = $this->enforceLog2Boundaries($countLog2);
  47. }
  48. /**
  49. * Encodes bytes into printable base 64 using the *nix standard from crypt().
  50. *
  51. * @param string $input
  52. * The string containing bytes to encode.
  53. * @param int $count
  54. * The number of characters (bytes) to encode.
  55. *
  56. * @return string
  57. * Encoded string.
  58. */
  59. protected function base64Encode($input, $count) {
  60. $output = '';
  61. $i = 0;
  62. do {
  63. $value = ord($input[$i++]);
  64. $output .= static::$ITOA64[$value & 0x3f];
  65. if ($i < $count) {
  66. $value |= ord($input[$i]) << 8;
  67. }
  68. $output .= static::$ITOA64[($value >> 6) & 0x3f];
  69. if ($i++ >= $count) {
  70. break;
  71. }
  72. if ($i < $count) {
  73. $value |= ord($input[$i]) << 16;
  74. }
  75. $output .= static::$ITOA64[($value >> 12) & 0x3f];
  76. if ($i++ >= $count) {
  77. break;
  78. }
  79. $output .= static::$ITOA64[($value >> 18) & 0x3f];
  80. } while ($i < $count);
  81. return $output;
  82. }
  83. /**
  84. * Generates a random base 64-encoded salt prefixed with hash settings.
  85. *
  86. * Proper use of salts may defeat a number of attacks, including:
  87. * - The ability to try candidate passwords against multiple hashes at once.
  88. * - The ability to use pre-hashed lists of candidate passwords.
  89. * - The ability to determine whether two users have the same (or different)
  90. * password without actually having to guess one of the passwords.
  91. *
  92. * @return string
  93. * A 12 character string containing the iteration count and a random salt.
  94. */
  95. protected function generateSalt() {
  96. $output = '$S$';
  97. // We encode the final log2 iteration count in base 64.
  98. $output .= static::$ITOA64[$this->countLog2];
  99. // 6 bytes is the standard salt for a portable phpass hash.
  100. $output .= $this->base64Encode(Crypt::randomBytes(6), 6);
  101. return $output;
  102. }
  103. /**
  104. * Ensures that $count_log2 is within set bounds.
  105. *
  106. * @param int $count_log2
  107. * Integer that determines the number of iterations used in the hashing
  108. * process. A larger value is more secure, but takes more time to complete.
  109. *
  110. * @return int
  111. * Integer within set bounds that is closest to $count_log2.
  112. */
  113. protected function enforceLog2Boundaries($count_log2) {
  114. if ($count_log2 < static::MIN_HASH_COUNT) {
  115. return static::MIN_HASH_COUNT;
  116. }
  117. elseif ($count_log2 > static::MAX_HASH_COUNT) {
  118. return static::MAX_HASH_COUNT;
  119. }
  120. return (int) $count_log2;
  121. }
  122. /**
  123. * Hash a password using a secure stretched hash.
  124. *
  125. * By using a salt and repeated hashing the password is "stretched". Its
  126. * security is increased because it becomes much more computationally costly
  127. * for an attacker to try to break the hash by brute-force computation of the
  128. * hashes of a large number of plain-text words or strings to find a match.
  129. *
  130. * @param string $algo
  131. * The string name of a hashing algorithm usable by hash(), like 'sha256'.
  132. * @param string $password
  133. * Plain-text password up to 512 bytes (128 to 512 UTF-8 characters) to
  134. * hash.
  135. * @param string $setting
  136. * An existing hash or the output of $this->generateSalt(). Must be at least
  137. * 12 characters (the settings and salt).
  138. *
  139. * @return string
  140. * A string containing the hashed password (and salt) or FALSE on failure.
  141. * The return string will be truncated at HASH_LENGTH characters max.
  142. */
  143. protected function crypt($algo, $password, $setting) {
  144. // Prevent DoS attacks by refusing to hash large passwords.
  145. if (strlen($password) > PasswordInterface::PASSWORD_MAX_LENGTH) {
  146. return FALSE;
  147. }
  148. // The first 12 characters of an existing hash are its setting string.
  149. $setting = substr($setting, 0, 12);
  150. if ($setting[0] != '$' || $setting[2] != '$') {
  151. return FALSE;
  152. }
  153. $count_log2 = $this->getCountLog2($setting);
  154. // Stored hashes may have been crypted with any iteration count. However we
  155. // do not allow applying the algorithm for unreasonable low and high values
  156. // respectively.
  157. if ($count_log2 != $this->enforceLog2Boundaries($count_log2)) {
  158. return FALSE;
  159. }
  160. $salt = substr($setting, 4, 8);
  161. // Hashes must have an 8 character salt.
  162. if (strlen($salt) != 8) {
  163. return FALSE;
  164. }
  165. // Convert the base 2 logarithm into an integer.
  166. $count = 1 << $count_log2;
  167. // We rely on the hash() function being available in PHP 5.2+.
  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 hashEquals() instead of === to mitigate timing attacks.
  229. return $computed_hash && Crypt::hashEquals($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. }