Completed
Push — master ( 280e63...1ffdb5 )
by Sam
03:06
created

password.php ➔ password_hash()   F

Complexity

Conditions 36
Paths 289

Size

Total Lines 111
Code Lines 82

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 36
eloc 82
c 2
b 1
f 0
nc 289
nop 3
dl 0
loc 111
rs 3.3842

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
if (!defined('PASSWORD_DEFAULT')) {
11
12
    define('PASSWORD_BCRYPT', 1);
13
    define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);
14
15
    /**
16
     * Hash the password using the specified algorithm
17
     *
18
     * @param string $password The password to hash
19
     * @param int    $algo     The algorithm to use (Defined by PASSWORD_* constants)
20
     * @param array  $options  The options for the algorithm to use
21
     *
22
     * @return string|false The hashed password, or false on error.
23
     */
24
    function password_hash($password, $algo, array $options = array()) {
25
        if (!function_exists('crypt')) {
26
            trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
27
            return null;
28
        }
29
        if (!is_string($password)) {
30
            trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
31
            return null;
32
        }
33
        if (!is_int($algo)) {
34
            trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
35
            return null;
36
        }
37
        switch ($algo) {
38
            case PASSWORD_BCRYPT:
39
                // Note that this is a C constant, but not exposed to PHP, so we don't define it here.
40
                $cost = 10;
41
                if (isset($options['cost'])) {
42
                    $cost = $options['cost'];
43
                    if ($cost < 4 || $cost > 31) {
44
                        trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
45
                        return null;
46
                    }
47
                }
48
                // The length of salt to generate
49
                $raw_salt_len = 16;
50
                // The length required in the final serialization
51
                $required_salt_len = 22;
52
                $hash_format = sprintf("$2y$%02d$", $cost);
53
                break;
54
            default:
55
                trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
56
                return null;
57
        }
58
        if (isset($options['salt'])) {
59
            switch (gettype($options['salt'])) {
60
                case 'NULL':
61
                case 'boolean':
62
                case 'integer':
63
                case 'double':
64
                case 'string':
65
                    $salt = (string) $options['salt'];
66
                    break;
67
                case 'object':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
68
                    if (method_exists($options['salt'], '__tostring')) {
69
                        $salt = (string) $options['salt'];
70
                        break;
71
                    }
72
                case 'array':
73
                case 'resource':
74
                default:
75
                    trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
76
                    return null;
77
            }
78
            if (strlen($salt) < $required_salt_len) {
79
                trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING);
80
                return null;
81
            } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
82
                $salt = str_replace('+', '.', base64_encode($salt));
83
            }
84
        } else {
85
            $buffer = '';
86
            $buffer_valid = false;
87
            if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
88
                $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM);
89
                if ($buffer) {
90
                    $buffer_valid = true;
91
                }
92
            }
93
            if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
94
                $buffer = openssl_random_pseudo_bytes($raw_salt_len);
95
                if ($buffer) {
96
                    $buffer_valid = true;
97
                }
98
            }
99
            if (!$buffer_valid && is_readable('/dev/urandom')) {
100
                $f = fopen('/dev/urandom', 'r');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $f. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
101
                $read = strlen($buffer);
102
                while ($read < $raw_salt_len) {
103
                    $buffer .= fread($f, $raw_salt_len - $read);
104
                    $read = strlen($buffer);
105
                }
106
                fclose($f);
107
                if ($read >= $raw_salt_len) {
108
                    $buffer_valid = true;
109
                }
110
            }
111
            if (!$buffer_valid || strlen($buffer) < $raw_salt_len) {
112
                $bl = strlen($buffer);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $bl. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
113
                for ($i = 0; $i < $raw_salt_len; $i++) {
114
                    if ($i < $bl) {
115
                        $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
116
                    } else {
117
                        $buffer .= chr(mt_rand(0, 255));
118
                    }
119
                }
120
            }
121
            $salt = str_replace('+', '.', base64_encode($buffer));
122
        }
123
        $salt = substr($salt, 0, $required_salt_len);
124
125
        $hash = $hash_format . $salt;
126
127
        $ret = crypt($password, $hash);
128
129
        if (!is_string($ret) || strlen($ret) <= 13) {
130
            return false;
131
        }
132
133
        return $ret;
134
    }
135
136
    /**
137
     * Get information about the password hash. Returns an array of the information
138
     * that was used to generate the password hash.
139
     *
140
     * array(
141
     *    'algo' => 1,
142
     *    'algoName' => 'bcrypt',
143
     *    'options' => array(
144
     *        'cost' => 10,
145
     *    ),
146
     * )
147
     *
148
     * @param string $hash The password hash to extract info from
149
     *
150
     * @return array The array of information about the hash.
151
     */
152
    function password_get_info($hash) {
153
        $return = array(
154
            'algo' => 0,
155
            'algoName' => 'unknown',
156
            'options' => array(),
157
        );
158
        if (substr($hash, 0, 4) == '$2y$' && strlen($hash) == 60) {
159
            $return['algo'] = PASSWORD_BCRYPT;
160
            $return['algoName'] = 'bcrypt';
161
            list($cost) = sscanf($hash, "$2y$%d$");
162
            $return['options']['cost'] = $cost;
163
        }
164
        return $return;
165
    }
166
167
    /**
168
     * Determine if the password hash needs to be rehashed according to the options provided
169
     *
170
     * If the answer is true, after validating the password using password_verify, rehash it.
171
     *
172
     * @param string $hash    The hash to test
173
     * @param int    $algo    The algorithm used for new password hashes
174
     * @param array  $options The options array passed to password_hash
175
     *
176
     * @return boolean True if the password needs to be rehashed.
177
     */
178
    function password_needs_rehash($hash, $algo, array $options = array()) {
179
        $info = password_get_info($hash);
180
        if ($info['algo'] != $algo) {
181
            return true;
182
        }
183
        switch ($algo) {
184
            case PASSWORD_BCRYPT:
185
                $cost = isset($options['cost']) ? $options['cost'] : 10;
186
                if ($cost != $info['options']['cost']) {
187
                    return true;
188
                }
189
                break;
190
        }
191
        return false;
192
    }
193
194
    /**
195
     * Verify a password against a hash using a timing attack resistant approach
196
     *
197
     * @param string $password The password to verify
198
     * @param string $hash     The hash to verify against
199
     *
200
     * @return boolean If the password matches the hash
201
     */
202
    function password_verify($password, $hash) {
203
        if (!function_exists('crypt')) {
204
            trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
205
            return false;
206
        }
207
        $ret = crypt($password, $hash);
208
        if (!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) {
209
            return false;
210
        }
211
212
        $status = 0;
213
        for ($i = 0; $i < strlen($ret); $i++) {
214
            $status |= (ord($ret[$i]) ^ ord($hash[$i]));
215
        }
216
217
        return $status === 0;
218
    }
219
}
220
221
222
0 ignored issues
show
Coding Style introduced by
As per coding style, files should not end with a newline character.

This check marks files that end in a newline character, i.e. an empy line.

Loading history...
223