Passed
Push — master ( b5dddf...91d417 )
by Richard
09:12
created

password_hash()   F

Complexity

Conditions 39
Paths 1113

Size

Total Lines 127
Code Lines 94

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 94
dl 0
loc 127
rs 0
c 0
b 0
f 0
cc 39
nc 1113
nop 3

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
 */
9
10
namespace {
11
12
    if (!defined('PASSWORD_BCRYPT')) {
13
        /**
14
         * PHPUnit Process isolation caches constants, but not function declarations.
15
         * So we need to check if the constants are defined separately from 
16
         * the functions to enable supporting process isolation in userland
17
         * code.
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
         * Hash the password using the specified algorithm
28
         *
29
         * @param string $password The password to hash
30
         * @param int    $algo     The algorithm to use (Defined by PASSWORD_* constants)
31
         * @param array  $options  The options for the algorithm to use
32
         *
33
         * @return string|false The hashed password, or false on error.
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)) {
0 ignored issues
show
introduced by
The condition is_int($password) is always false.
Loading history...
41
                $password = (string) $password;
42
            }
43
            if (!is_string($password)) {
0 ignored issues
show
introduced by
The condition is_string($password) is always true.
Loading history...
44
                trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
45
                return null;
46
            }
47
            if (!is_int($algo)) {
0 ignored issues
show
introduced by
The condition is_int($algo) is always true.
Loading history...
48
                trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
49
                return null;
50
            }
51
            $resultLength = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $resultLength is dead and can be removed.
Loading history...
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
                    // The length of salt to generate
63
                    $raw_salt_len = 16;
64
                    // The length required in the final serialization
65
                    $required_salt_len = 22;
66
                    $hash_format = sprintf("$2y$%02d$", $cost);
67
                    // The expected length of the final crypt() output
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);
0 ignored issues
show
Deprecated Code introduced by
The function mcrypt_create_iv() has been deprecated: 7.1 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

105
                    $buffer = /** @scrutinizer ignore-deprecated */ mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $f can also be of type false; however, parameter $handle of fread() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

120
                        $buffer .= fread(/** @scrutinizer ignore-type */ $f, $raw_salt_len - $read);
Loading history...
121
                        $read = PasswordCompat\binary\_strlen($buffer);
122
                    }
123
                    fclose($f);
0 ignored issues
show
Bug introduced by
It seems like $f can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

123
                    fclose(/** @scrutinizer ignore-type */ $f);
Loading history...
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
                // encode string with the Base64 variant used by crypt
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) {
0 ignored issues
show
introduced by
The condition is_string($ret) is always true.
Loading history...
158
                return false;
159
            }
160
161
            return $ret;
162
        }
163
164
        /**
165
         * Get information about the password hash. Returns an array of the information
166
         * that was used to generate the password hash.
167
         *
168
         * array(
169
         *    'algo' => 1,
170
         *    'algoName' => 'bcrypt',
171
         *    'options' => array(
172
         *        'cost' => PASSWORD_BCRYPT_DEFAULT_COST,
173
         *    ),
174
         * )
175
         *
176
         * @param string $hash The password hash to extract info from
177
         *
178
         * @return array The array of information about the hash.
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
         * Determine if the password hash needs to be rehashed according to the options provided
197
         *
198
         * If the answer is true, after validating the password using password_verify, rehash it.
199
         *
200
         * @param string $hash    The hash to test
201
         * @param int    $algo    The algorithm used for new password hashes
202
         * @param array  $options The options array passed to password_hash
203
         *
204
         * @return boolean True if the password needs to be rehashed.
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
         * Verify a password against a hash using a timing attack resistant approach
224
         *
225
         * @param string $password The password to verify
226
         * @param string $hash     The hash to verify against
227
         *
228
         * @return boolean If the password matches the hash
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) {
0 ignored issues
show
introduced by
The condition is_string($ret) is always true.
Loading history...
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
         * Count the number of bytes in a string
257
         *
258
         * We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension.
259
         * In this case, strlen() will count the number of *characters* based on the internal encoding. A
260
         * sequence of bytes might be regarded as a single multibyte character.
261
         *
262
         * @param string $binary_string The input string
263
         *
264
         * @internal
265
         * @return int The number of bytes
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
         * Get a substring based on byte limits
276
         *
277
         * @see _strlen()
278
         *
279
         * @param string $binary_string The input string
280
         * @param int    $start
281
         * @param int    $length
282
         *
283
         * @internal
284
         * @return string The substring
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
         * Check if current PHP version is compatible with the library
295
         *
296
         * @return boolean the check result
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
}