PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/app/Library/password.php

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