1: | <?php
|
2: | |
3: | |
4: | |
5: | |
6: | |
7: | |
8: |
|
9: |
|
10: | namespace {
|
11: |
|
12: | if (!defined('PASSWORD_BCRYPT')) {
|
13: | |
14: | |
15: | |
16: | |
17: | |
18: |
|
19: | define('PASSWORD_BCRYPT', 1);
|
20: | define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);
|
21: | define('PASSWORD_BCRYPT_DEFAULT_COST', 10);
|
22: | }
|
23: |
|
24: | if (!function_exists('password_hash')) {
|
25: |
|
26: | |
27: | |
28: | |
29: | |
30: | |
31: | |
32: | |
33: | |
34: |
|
35: | function password_hash($password, $algo, array $options = array()) {
|
36: | if (!function_exists('crypt')) {
|
37: | trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
|
38: | return null;
|
39: | }
|
40: | if (is_null($password) || is_int($password)) {
|
41: | $password = (string) $password;
|
42: | }
|
43: | if (!is_string($password)) {
|
44: | trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
|
45: | return null;
|
46: | }
|
47: | if (!is_int($algo)) {
|
48: | trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
|
49: | return null;
|
50: | }
|
51: | $resultLength = 0;
|
52: | switch ($algo) {
|
53: | case PASSWORD_BCRYPT:
|
54: | $cost = PASSWORD_BCRYPT_DEFAULT_COST;
|
55: | if (isset($options['cost'])) {
|
56: | $cost = $options['cost'];
|
57: | if ($cost < 4 || $cost > 31) {
|
58: | trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
|
59: | return null;
|
60: | }
|
61: | }
|
62: |
|
63: | $raw_salt_len = 16;
|
64: |
|
65: | $required_salt_len = 22;
|
66: | $hash_format = sprintf("$2y$%02d$", $cost);
|
67: |
|
68: | $resultLength = 60;
|
69: | break;
|
70: | default:
|
71: | trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
|
72: | return null;
|
73: | }
|
74: | $salt_requires_encoding = false;
|
75: | if (isset($options['salt'])) {
|
76: | switch (gettype($options['salt'])) {
|
77: | case 'NULL':
|
78: | case 'boolean':
|
79: | case 'integer':
|
80: | case 'double':
|
81: | case 'string':
|
82: | $salt = (string) $options['salt'];
|
83: | break;
|
84: | case 'object':
|
85: | if (method_exists($options['salt'], '__tostring')) {
|
86: | $salt = (string) $options['salt'];
|
87: | break;
|
88: | }
|
89: | case 'array':
|
90: | case 'resource':
|
91: | default:
|
92: | trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
|
93: | return null;
|
94: | }
|
95: | if (PasswordCompat\binary\_strlen($salt) < $required_salt_len) {
|
96: | trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", PasswordCompat\binary\_strlen($salt), $required_salt_len), E_USER_WARNING);
|
97: | return null;
|
98: | } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
|
99: | $salt_requires_encoding = true;
|
100: | }
|
101: | } else {
|
102: | $buffer = '';
|
103: | $buffer_valid = false;
|
104: | if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
|
105: | $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM);
|
106: | if ($buffer) {
|
107: | $buffer_valid = true;
|
108: | }
|
109: | }
|
110: | if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
|
111: | $buffer = openssl_random_pseudo_bytes($raw_salt_len);
|
112: | if ($buffer) {
|
113: | $buffer_valid = true;
|
114: | }
|
115: | }
|
116: | if (!$buffer_valid && @is_readable('/dev/urandom')) {
|
117: | $f = fopen('/dev/urandom', 'r');
|
118: | $read = PasswordCompat\binary\_strlen($buffer);
|
119: | while ($read < $raw_salt_len) {
|
120: | $buffer .= fread($f, $raw_salt_len - $read);
|
121: | $read = PasswordCompat\binary\_strlen($buffer);
|
122: | }
|
123: | fclose($f);
|
124: | if ($read >= $raw_salt_len) {
|
125: | $buffer_valid = true;
|
126: | }
|
127: | }
|
128: | if (!$buffer_valid || PasswordCompat\binary\_strlen($buffer) < $raw_salt_len) {
|
129: | $bl = PasswordCompat\binary\_strlen($buffer);
|
130: | for ($i = 0; $i < $raw_salt_len; $i++) {
|
131: | if ($i < $bl) {
|
132: | $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
|
133: | } else {
|
134: | $buffer .= chr(mt_rand(0, 255));
|
135: | }
|
136: | }
|
137: | }
|
138: | $salt = $buffer;
|
139: | $salt_requires_encoding = true;
|
140: | }
|
141: | if ($salt_requires_encoding) {
|
142: |
|
143: | $base64_digits =
|
144: | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
145: | $bcrypt64_digits =
|
146: | './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
147: |
|
148: | $base64_string = base64_encode($salt);
|
149: | $salt = strtr(rtrim($base64_string, '='), $base64_digits, $bcrypt64_digits);
|
150: | }
|
151: | $salt = PasswordCompat\binary\_substr($salt, 0, $required_salt_len);
|
152: |
|
153: | $hash = $hash_format . $salt;
|
154: |
|
155: | $ret = crypt($password, $hash);
|
156: |
|
157: | if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != $resultLength) {
|
158: | return false;
|
159: | }
|
160: |
|
161: | return $ret;
|
162: | }
|
163: |
|
164: | |
165: | |
166: | |
167: | |
168: | |
169: | |
170: | |
171: | |
172: | |
173: | |
174: | |
175: | |
176: | |
177: | |
178: | |
179: |
|
180: | function password_get_info($hash) {
|
181: | $return = array(
|
182: | 'algo' => 0,
|
183: | 'algoName' => 'unknown',
|
184: | 'options' => array(),
|
185: | );
|
186: | if (PasswordCompat\binary\_substr($hash, 0, 4) == '$2y$' && PasswordCompat\binary\_strlen($hash) == 60) {
|
187: | $return['algo'] = PASSWORD_BCRYPT;
|
188: | $return['algoName'] = 'bcrypt';
|
189: | list($cost) = sscanf($hash, "$2y$%d$");
|
190: | $return['options']['cost'] = $cost;
|
191: | }
|
192: | return $return;
|
193: | }
|
194: |
|
195: | |
196: | |
197: | |
198: | |
199: | |
200: | |
201: | |
202: | |
203: | |
204: | |
205: |
|
206: | function password_needs_rehash($hash, $algo, array $options = array()) {
|
207: | $info = password_get_info($hash);
|
208: | if ($info['algo'] != $algo) {
|
209: | return true;
|
210: | }
|
211: | switch ($algo) {
|
212: | case PASSWORD_BCRYPT:
|
213: | $cost = isset($options['cost']) ? $options['cost'] : PASSWORD_BCRYPT_DEFAULT_COST;
|
214: | if ($cost != $info['options']['cost']) {
|
215: | return true;
|
216: | }
|
217: | break;
|
218: | }
|
219: | return false;
|
220: | }
|
221: |
|
222: | |
223: | |
224: | |
225: | |
226: | |
227: | |
228: | |
229: |
|
230: | function password_verify($password, $hash) {
|
231: | if (!function_exists('crypt')) {
|
232: | trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
|
233: | return false;
|
234: | }
|
235: | $ret = crypt($password, $hash);
|
236: | if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != PasswordCompat\binary\_strlen($hash) || PasswordCompat\binary\_strlen($ret) <= 13) {
|
237: | return false;
|
238: | }
|
239: |
|
240: | $status = 0;
|
241: | for ($i = 0; $i < PasswordCompat\binary\_strlen($ret); $i++) {
|
242: | $status |= (ord($ret[$i]) ^ ord($hash[$i]));
|
243: | }
|
244: |
|
245: | return $status === 0;
|
246: | }
|
247: | }
|
248: |
|
249: | }
|
250: |
|
251: | namespace PasswordCompat\binary {
|
252: |
|
253: | if (!function_exists('PasswordCompat\\binary\\_strlen')) {
|
254: |
|
255: | |
256: | |
257: | |
258: | |
259: | |
260: | |
261: | |
262: | |
263: | |
264: | |
265: | |
266: |
|
267: | function _strlen($binary_string) {
|
268: | if (function_exists('mb_strlen')) {
|
269: | return mb_strlen($binary_string, '8bit');
|
270: | }
|
271: | return strlen($binary_string);
|
272: | }
|
273: |
|
274: | |
275: | |
276: | |
277: | |
278: | |
279: | |
280: | |
281: | |
282: | |
283: | |
284: | |
285: |
|
286: | function _substr($binary_string, $start, $length) {
|
287: | if (function_exists('mb_substr')) {
|
288: | return mb_substr($binary_string, $start, $length, '8bit');
|
289: | }
|
290: | return substr($binary_string, $start, $length);
|
291: | }
|
292: |
|
293: | |
294: | |
295: | |
296: | |
297: |
|
298: | function check() {
|
299: | static $pass = NULL;
|
300: |
|
301: | if (is_null($pass)) {
|
302: | if (function_exists('crypt')) {
|
303: | $hash = '$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG';
|
304: | $test = crypt("password", $hash);
|
305: | $pass = $test == $hash;
|
306: | } else {
|
307: | $pass = false;
|
308: | }
|
309: | }
|
310: | return $pass;
|
311: | }
|
312: |
|
313: | }
|
314: | } |