1 <?php
2 3 4 5 6 7 8
9
10 if (!defined('PASSWORD_BCRYPT')) {
11
12 define('PASSWORD_BCRYPT', 1);
13 define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);
14
15 16 17 18 19 20 21 22 23
24 function password_hash($password, $algo, array $options = array()) {
25 if (!function_exists('crypt')) {
26 trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
27 return null;
28 }
29 if (!is_string($password)) {
30 trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
31 return null;
32 }
33 if (!is_int($algo)) {
34 trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
35 return null;
36 }
37 switch ($algo) {
38 case PASSWORD_BCRYPT:
39
40 $cost = 10;
41 if (isset($options['cost'])) {
42 $cost = $options['cost'];
43 if ($cost < 4 || $cost > 31) {
44 trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
45 return null;
46 }
47 }
48
49 $raw_salt_len = 16;
50
51 $required_salt_len = 22;
52 $hash_format = sprintf("$2y$%02d$", $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 $buffer_valid = false;
87 if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
88 $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM);
89 if ($buffer) {
90 $buffer_valid = true;
91 }
92 }
93 if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
94 $buffer = openssl_random_pseudo_bytes($raw_salt_len);
95 if ($buffer) {
96 $buffer_valid = true;
97 }
98 }
99 if (!$buffer_valid && is_readable('/dev/urandom')) {
100 $f = fopen('/dev/urandom', 'r');
101 $read = strlen($buffer);
102 while ($read < $raw_salt_len) {
103 $buffer .= fread($f, $raw_salt_len - $read);
104 $read = strlen($buffer);
105 }
106 fclose($f);
107 if ($read >= $raw_salt_len) {
108 $buffer_valid = true;
109 }
110 }
111 if (!$buffer_valid || strlen($buffer) < $raw_salt_len) {
112 $bl = strlen($buffer);
113 for ($i = 0; $i < $raw_salt_len; $i++) {
114 if ($i < $bl) {
115 $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
116 } else {
117 $buffer .= chr(mt_rand(0, 255));
118 }
119 }
120 }
121 $salt = str_replace('+', '.', base64_encode($buffer));
122 }
123 $salt = substr($salt, 0, $required_salt_len);
124
125 $hash = $hash_format . $salt;
126
127 $ret = crypt($password, $hash);
128
129 if (!is_string($ret) || strlen($ret) <= 13) {
130 return false;
131 }
132
133 return $ret;
134 }
135
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
152 function password_get_info($hash) {
153 $return = array(
154 'algo' => 0,
155 'algoName' => 'unknown',
156 'options' => array(),
157 );
158 if (substr($hash, 0, 4) == '$2y$' && strlen($hash) == 60) {
159 $return['algo'] = PASSWORD_BCRYPT;
160 $return['algoName'] = 'bcrypt';
161 list($cost) = sscanf($hash, "$2y$%d$");
162 $return['options']['cost'] = $cost;
163 }
164 return $return;
165 }
166
167 168 169 170 171 172 173 174 175 176 177
178 function password_needs_rehash($hash, $algo, array $options = array()) {
179 $info = password_get_info($hash);
180 if ($info['algo'] != $algo) {
181 return true;
182 }
183 switch ($algo) {
184 case PASSWORD_BCRYPT:
185 $cost = isset($options['cost']) ? $options['cost'] : 10;
186 if ($cost != $info['options']['cost']) {
187 return true;
188 }
189 break;
190 }
191 return false;
192 }
193
194 195 196 197 198 199 200 201
202 function password_verify($password, $hash) {
203 if (!function_exists('crypt')) {
204 trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
205 return false;
206 }
207 $ret = crypt($password, $hash);
208 if (!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) {
209 return false;
210 }
211
212 $status = 0;
213 for ($i = 0; $i < strlen($ret); $i++) {
214 $status |= (ord($ret[$i]) ^ ord($hash[$i]));
215 }
216
217 return $status === 0;
218 }
219 }
220
221
222
223