Subversion Repositories cheapmusic

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
25 - 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
namespace {
10
    if (!defined('PASSWORD_BCRYPT')) {
11
        /**
12
         * PHPUnit Process isolation caches constants, but not function declarations.
13
         * So we need to check if the constants are defined separately from
14
         * the functions to enable supporting process isolation in userland
15
         * code.
16
         */
17
        define('PASSWORD_BCRYPT', 1);
18
        define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);
19
        define('PASSWORD_BCRYPT_DEFAULT_COST', 10);
20
    }
21
    if (!function_exists('password_hash')) {
22
        /**
23
         * Hash the password using the specified algorithm
24
         *
25
         * @param string $password The password to hash
26
         * @param int    $algo     The algorithm to use (Defined by PASSWORD_* constants)
27
         * @param array  $options  The options for the algorithm to use
28
         *
29
         * @return string|false The hashed password, or false on error.
30
         */
31
        function password_hash($password, $algo, array $options = array()) {
32
            if (!function_exists('crypt')) {
33
                trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
34
                return null;
35
            }
36
            if (is_null($password) || is_int($password)) {
37
                $password = (string) $password;
38
            }
39
            if (!is_string($password)) {
40
                trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
41
                return null;
42
            }
43
            if (!is_int($algo)) {
44
                trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
45
                return null;
46
            }
47
            $resultLength = 0;
48
            switch ($algo) {
49
                case PASSWORD_BCRYPT:
50
                    $cost = PASSWORD_BCRYPT_DEFAULT_COST;
51
                    if (isset($options['cost'])) {
52
                        $cost = (int) $options['cost'];
53
                        if ($cost < 4 || $cost > 31) {
54
                            trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
55
                            return null;
56
                        }
57
                    }
58
                    // The length of salt to generate
59
                    $raw_salt_len = 16;
60
                    // The length required in the final serialization
61
                    $required_salt_len = 22;
62
                    $hash_format = sprintf("$2y$%02d$", $cost);
63
                    // The expected length of the final crypt() output
64
                    $resultLength = 60;
65
                    break;
66
                default:
67
                    trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
68
                    return null;
69
            }
70
            $salt_req_encoding = false;
71
            if (isset($options['salt'])) {
72
                switch (gettype($options['salt'])) {
73
                    case 'NULL':
74
                    case 'boolean':
75
                    case 'integer':
76
                    case 'double':
77
                    case 'string':
78
                        $salt = (string) $options['salt'];
79
                        break;
80
                    case 'object':
81
                        if (method_exists($options['salt'], '__tostring')) {
82
                            $salt = (string) $options['salt'];
83
                            break;
84
                        }
85
                    case 'array':
86
                    case 'resource':
87
                    default:
88
                        trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
89
                        return null;
90
                }
91
                if (PasswordCompat\binary\_strlen($salt) < $required_salt_len) {
92
                    trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", PasswordCompat\binary\_strlen($salt), $required_salt_len), E_USER_WARNING);
93
                    return null;
94
                } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
95
                    $salt_req_encoding = true;
96
                }
97
            } else {
98
                $buffer = '';
99
                $buffer_valid = false;
100
                if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
101
                    $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM);
102
                    if ($buffer) {
103
                        $buffer_valid = true;
104
                    }
105
                }
106
                if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
107
                    $strong = false;
108
                    $buffer = openssl_random_pseudo_bytes($raw_salt_len, $strong);
109
                    if ($buffer && $strong) {
110
                        $buffer_valid = true;
111
                    }
112
                }
113
                if (!$buffer_valid && @is_readable('/dev/urandom')) {
114
                    $file = fopen('/dev/urandom', 'r');
115
                    $read = 0;
116
                    $local_buffer = '';
117
                    while ($read < $raw_salt_len) {
118
                        $local_buffer .= fread($file, $raw_salt_len - $read);
119
                        $read = PasswordCompat\binary\_strlen($local_buffer);
120
                    }
121
                    fclose($file);
122
                    if ($read >= $raw_salt_len) {
123
                        $buffer_valid = true;
124
                    }
125
                    $buffer = str_pad($buffer, $raw_salt_len, "\0") ^ str_pad($local_buffer, $raw_salt_len, "\0");
126
                }
127
                if (!$buffer_valid || PasswordCompat\binary\_strlen($buffer) < $raw_salt_len) {
128
                    $buffer_length = PasswordCompat\binary\_strlen($buffer);
129
                    for ($i = 0; $i < $raw_salt_len; $i++) {
130
                        if ($i < $buffer_length) {
131
                            $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
132
                        } else {
133
                            $buffer .= chr(mt_rand(0, 255));
134
                        }
135
                    }
136
                }
137
                $salt = $buffer;
138
                $salt_req_encoding = true;
139
            }
140
            if ($salt_req_encoding) {
141
                // encode string with the Base64 variant used by crypt
142
                $base64_digits =
143
                    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
144
                $bcrypt64_digits =
145
                    './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
146
                $base64_string = base64_encode($salt);
147
                $salt = strtr(rtrim($base64_string, '='), $base64_digits, $bcrypt64_digits);
148
            }
149
            $salt = PasswordCompat\binary\_substr($salt, 0, $required_salt_len);
150
            $hash = $hash_format . $salt;
151
            $ret = crypt($password, $hash);
152
            if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != $resultLength) {
153
                return false;
154
            }
155
            return $ret;
156
        }
157
        /**
158
         * Get information about the password hash. Returns an array of the information
159
         * that was used to generate the password hash.
160
         *
161
         * array(
162
         *    'algo' => 1,
163
         *    'algoName' => 'bcrypt',
164
         *    'options' => array(
165
         *        'cost' => PASSWORD_BCRYPT_DEFAULT_COST,
166
         *    ),
167
         * )
168
         *
169
         * @param string $hash The password hash to extract info from
170
         *
171
         * @return array The array of information about the hash.
172
         */
173
        function password_get_info($hash) {
174
            $return = array(
175
                'algo' => 0,
176
                'algoName' => 'unknown',
177
                'options' => array(),
178
            );
179
            if (PasswordCompat\binary\_substr($hash, 0, 4) == '$2y$' && PasswordCompat\binary\_strlen($hash) == 60) {
180
                $return['algo'] = PASSWORD_BCRYPT;
181
                $return['algoName'] = 'bcrypt';
182
                list($cost) = sscanf($hash, "$2y$%d$");
183
                $return['options']['cost'] = $cost;
184
            }
185
            return $return;
186
        }
187
        /**
188
         * Determine if the password hash needs to be rehashed according to the options provided
189
         *
190
         * If the answer is true, after validating the password using password_verify, rehash it.
191
         *
192
         * @param string $hash    The hash to test
193
         * @param int    $algo    The algorithm used for new password hashes
194
         * @param array  $options The options array passed to password_hash
195
         *
196
         * @return boolean True if the password needs to be rehashed.
197
         */
198
        function password_needs_rehash($hash, $algo, array $options = array()) {
199
            $info = password_get_info($hash);
200
            if ($info['algo'] !== (int) $algo) {
201
                return true;
202
            }
203
            switch ($algo) {
204
                case PASSWORD_BCRYPT:
205
                    $cost = isset($options['cost']) ? (int) $options['cost'] : PASSWORD_BCRYPT_DEFAULT_COST;
206
                    if ($cost !== $info['options']['cost']) {
207
                        return true;
208
                    }
209
                    break;
210
            }
211
            return false;
212
        }
213
        /**
214
         * Verify a password against a hash using a timing attack resistant approach
215
         *
216
         * @param string $password The password to verify
217
         * @param string $hash     The hash to verify against
218
         *
219
         * @return boolean If the password matches the hash
220
         */
221
        function password_verify($password, $hash) {
222
            if (!function_exists('crypt')) {
223
                trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
224
                return false;
225
            }
226
            $ret = crypt($password, $hash);
227
            if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != PasswordCompat\binary\_strlen($hash) || PasswordCompat\binary\_strlen($ret) <= 13) {
228
                return false;
229
            }
230
            $status = 0;
231
            for ($i = 0; $i < PasswordCompat\binary\_strlen($ret); $i++) {
232
                $status |= (ord($ret[$i]) ^ ord($hash[$i]));
233
            }
234
            return $status === 0;
235
        }
236
    }
237
}
238
namespace PasswordCompat\binary {
239
    if (!function_exists('PasswordCompat\\binary\\_strlen')) {
240
        /**
241
         * Count the number of bytes in a string
242
         *
243
         * We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension.
244
         * In this case, strlen() will count the number of *characters* based on the internal encoding. A
245
         * sequence of bytes might be regarded as a single multibyte character.
246
         *
247
         * @param string $binary_string The input string
248
         *
249
         * @internal
250
         * @return int The number of bytes
251
         */
252
        function _strlen($binary_string) {
253
            if (function_exists('mb_strlen')) {
254
                return mb_strlen($binary_string, '8bit');
255
            }
256
            return strlen($binary_string);
257
        }
258
        /**
259
         * Get a substring based on byte limits
260
         *
261
         * @see _strlen()
262
         *
263
         * @param string $binary_string The input string
264
         * @param int    $start
265
         * @param int    $length
266
         *
267
         * @internal
268
         * @return string The substring
269
         */
270
        function _substr($binary_string, $start, $length) {
271
            if (function_exists('mb_substr')) {
272
                return mb_substr($binary_string, $start, $length, '8bit');
273
            }
274
            return substr($binary_string, $start, $length);
275
        }
276
        /**
277
         * Check if current PHP version is compatible with the library
278
         *
279
         * @return boolean the check result
280
         */
281
        function check() {
282
            static $pass = NULL;
283
            if (is_null($pass)) {
284
                if (function_exists('crypt')) {
285
                    $hash = '$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG';
286
                    $test = crypt("password", $hash);
287
                    $pass = $test == $hash;
288
                } else {
289
                    $pass = false;
290
                }
291
            }
292
            return $pass;
293
        }
294
    }
295
}