PageRenderTime 65ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/application/extras/password_compat.php

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