PageRenderTime 37ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/password_compat/lib/password.php

https://bitbucket.org/kudutest1/moodlegit
PHP | 220 lines | 155 code | 13 blank | 52 comment | 48 complexity | 7bb0c38f303b2b704b31c6415838d7ed MD5 | raw file
  1. <?php
  2. /**
  3. * A Compatibility library with PHP 5.5's simplified password hashing API.
  4. *
  5. * @author Anthony Ferrara <ircmaxell@php.net>
  6. * @license http://www.opensource.org/licenses/mit-license.html MIT License
  7. * @copyright 2012 The Authors
  8. */
  9. if (!defined('PASSWORD_BCRYPT')) {
  10. define('PASSWORD_BCRYPT', 1);
  11. define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);
  12. /**
  13. * Hash the password using the specified algorithm
  14. *
  15. * @param string $password The password to hash
  16. * @param int $algo The algorithm to use (Defined by PASSWORD_* constants)
  17. * @param array $options The options for the algorithm to use
  18. *
  19. * @return string|false The hashed password, or false on error.
  20. */
  21. function password_hash($password, $algo, array $options = array()) {
  22. if (!function_exists('crypt')) {
  23. trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
  24. return null;
  25. }
  26. if (!is_string($password)) {
  27. trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
  28. return null;
  29. }
  30. if (!is_int($algo)) {
  31. trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
  32. return null;
  33. }
  34. switch ($algo) {
  35. case PASSWORD_BCRYPT:
  36. // Note that this is a C constant, but not exposed to PHP, so we don't define it here.
  37. $cost = 10;
  38. if (isset($options['cost'])) {
  39. $cost = $options['cost'];
  40. if ($cost < 4 || $cost > 31) {
  41. trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
  42. return null;
  43. }
  44. }
  45. $required_salt_len = 22;
  46. $hash_format = sprintf("$2y$%02d$", $cost);
  47. break;
  48. default:
  49. trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
  50. return null;
  51. }
  52. if (isset($options['salt'])) {
  53. switch (gettype($options['salt'])) {
  54. case 'NULL':
  55. case 'boolean':
  56. case 'integer':
  57. case 'double':
  58. case 'string':
  59. $salt = (string) $options['salt'];
  60. break;
  61. case 'object':
  62. if (method_exists($options['salt'], '__tostring')) {
  63. $salt = (string) $options['salt'];
  64. break;
  65. }
  66. case 'array':
  67. case 'resource':
  68. default:
  69. trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
  70. return null;
  71. }
  72. if (strlen($salt) < $required_salt_len) {
  73. trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING);
  74. return null;
  75. } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
  76. $salt = str_replace('+', '.', base64_encode($salt));
  77. }
  78. } else {
  79. $buffer = '';
  80. $raw_length = (int) ($required_salt_len * 3 / 4 + 1);
  81. $buffer_valid = false;
  82. if (function_exists('mcrypt_create_iv')) {
  83. $buffer = mcrypt_create_iv($raw_length, MCRYPT_DEV_URANDOM);
  84. if ($buffer) {
  85. $buffer_valid = true;
  86. }
  87. }
  88. if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
  89. $buffer = openssl_random_pseudo_bytes($raw_length);
  90. if ($buffer) {
  91. $buffer_valid = true;
  92. }
  93. }
  94. if (!$buffer_valid && file_exists('/dev/urandom')) {
  95. $f = @fopen('/dev/urandom', 'r');
  96. if ($f) {
  97. $read = strlen($buffer);
  98. while ($read < $raw_length) {
  99. $buffer .= fread($f, $raw_length - $read);
  100. $read = strlen($buffer);
  101. }
  102. fclose($f);
  103. if ($read >= $raw_length) {
  104. $buffer_valid = true;
  105. }
  106. }
  107. }
  108. if (!$buffer_valid || strlen($buffer) < $raw_length) {
  109. $bl = strlen($buffer);
  110. for ($i = 0; $i < $raw_length; $i++) {
  111. if ($i < $bl) {
  112. $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
  113. } else {
  114. $buffer .= chr(mt_rand(0, 255));
  115. }
  116. }
  117. }
  118. $salt = str_replace('+', '.', base64_encode($buffer));
  119. }
  120. $salt = substr($salt, 0, $required_salt_len);
  121. $hash = $hash_format . $salt;
  122. $ret = crypt($password, $hash);
  123. if (!is_string($ret) || strlen($ret) <= 13) {
  124. return false;
  125. }
  126. return $ret;
  127. }
  128. /**
  129. * Get information about the password hash. Returns an array of the information
  130. * that was used to generate the password hash.
  131. *
  132. * array(
  133. * 'algo' => 1,
  134. * 'algoName' => 'bcrypt',
  135. * 'options' => array(
  136. * 'cost' => 10,
  137. * ),
  138. * )
  139. *
  140. * @param string $hash The password hash to extract info from
  141. *
  142. * @return array The array of information about the hash.
  143. */
  144. function password_get_info($hash) {
  145. $return = array(
  146. 'algo' => 0,
  147. 'algoName' => 'unknown',
  148. 'options' => array(),
  149. );
  150. if (substr($hash, 0, 4) == '$2y$' && strlen($hash) == 60) {
  151. $return['algo'] = PASSWORD_BCRYPT;
  152. $return['algoName'] = 'bcrypt';
  153. list($cost) = sscanf($hash, "$2y$%d$");
  154. $return['options']['cost'] = $cost;
  155. }
  156. return $return;
  157. }
  158. /**
  159. * Determine if the password hash needs to be rehashed according to the options provided
  160. *
  161. * If the answer is true, after validating the password using password_verify, rehash it.
  162. *
  163. * @param string $hash The hash to test
  164. * @param int $algo The algorithm used for new password hashes
  165. * @param array $options The options array passed to password_hash
  166. *
  167. * @return boolean True if the password needs to be rehashed.
  168. */
  169. function password_needs_rehash($hash, $algo, array $options = array()) {
  170. $info = password_get_info($hash);
  171. if ($info['algo'] != $algo) {
  172. return true;
  173. }
  174. switch ($algo) {
  175. case PASSWORD_BCRYPT:
  176. $cost = isset($options['cost']) ? $options['cost'] : 10;
  177. if ($cost != $info['options']['cost']) {
  178. return true;
  179. }
  180. break;
  181. }
  182. return false;
  183. }
  184. /**
  185. * Verify a password against a hash using a timing attack resistant approach
  186. *
  187. * @param string $password The password to verify
  188. * @param string $hash The hash to verify against
  189. *
  190. * @return boolean If the password matches the hash
  191. */
  192. function password_verify($password, $hash) {
  193. if (!function_exists('crypt')) {
  194. trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
  195. return false;
  196. }
  197. $ret = crypt($password, $hash);
  198. if (!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) {
  199. return false;
  200. }
  201. $status = 0;
  202. for ($i = 0; $i < strlen($ret); $i++) {
  203. $status |= (ord($ret[$i]) ^ ord($hash[$i]));
  204. }
  205. return $status === 0;
  206. }
  207. }