password_hash()   F
last analyzed

Complexity

Conditions 40
Paths 1113

Size

Total Lines 130
Code Lines 97

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 40
eloc 97
nc 1113
nop 3
dl 0
loc 130
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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