Subversion Repositories cheapmusic

Rev

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