Passed
Pull Request — release-2.1 (#5074)
by John
05:59
created

password_hash()   F

Complexity

Conditions 41
Paths 2839

Size

Total Lines 133
Code Lines 97

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 41
eloc 97
c 0
b 0
f 0
nop 3
dl 0
loc 133
rs 0
nc 2839

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 https://opensource.org/licenses/mit-license.html MIT License
7
 * @copyright 2012 The Authors
8
 *
9
 * Simple Machines Forum (SMF)
10
 *
11
 * @package SMF
12
 * @author Simple Machines http://www.simplemachines.org
13
 * @copyright 2018 Simple Machines and individual contributors
14
 * @license http://www.simplemachines.org/about/smf/license.php BSD
15
 *
16
 * @version 2.1 Beta 4
17
 */
18
19
namespace {
20
21
	if (!defined('PASSWORD_DEFAULT')) {
22
23
		define('PASSWORD_BCRYPT', 1);
24
		define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);
25
26
		/**
27
		 * Hash the password using the specified algorithm
28
		 * Limits the maximum length of password to 72, if a longer
29
		 * string is supplied the first 72 characters are used
30
		 *
31
		 * @param string $password The password to hash
32
		 * @param int    $algo     The algorithm to use (Defined by PASSWORD_* constants)
33
		 * @param array  $options  The options for the algorithm to use
34
		 *
35
		 * @return string|false The hashed password, or false on error.
36
		 */
37
		function password_hash($password, $algo, array $options = array()) {
38
			if (!function_exists('crypt')) {
39
				trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
40
				return null;
41
			}
42
			if (!is_string($password)) {
0 ignored issues
show
introduced by
The condition is_string($password) is always true.
Loading history...
43
				trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
44
				return null;
45
			}
46
			if (!is_int($algo)) {
0 ignored issues
show
introduced by
The condition is_int($algo) is always true.
Loading history...
47
				trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
48
				return null;
49
			}
50
			if (PasswordCompat\binary\_strlen($password) > 72) {
51
				$password = PasswordCompat\binary\_substr($password, 0, 72);
52
			}
53
			switch ($algo) {
54
				case PASSWORD_BCRYPT:
55
					// Note that this is a C constant, but not exposed to PHP, so we don't define it here.
56
					$cost = 10;
57
					if (isset($options['cost'])) {
58
						$cost = $options['cost'];
59
						if ($cost < 4 || $cost > 31) {
60
							trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
61
							return null;
62
						}
63
					}
64
					// The length of salt to generate
65
					$raw_salt_len = 16;
66
					// The length required in the final serialization
67
					$required_salt_len = 22;
68
					$hash_format = sprintf("$2y$%02d$", $cost);
69
					// The expected length of the final crypt() output
70
					$resultLength = 60;
71
					break;
72
				default:
73
					trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
74
					return null;
75
			}
76
			$salt_requires_encoding = false;
77
			if (isset($options['salt'])) {
78
				switch (gettype($options['salt'])) {
79
					case 'NULL':
80
					case 'boolean':
81
					case 'integer':
82
					case 'double':
83
					case 'string':
84
						$salt = (string) $options['salt'];
85
						break;
86
					case 'object':
87
						if (method_exists($options['salt'], '__tostring')) {
88
							$salt = (string) $options['salt'];
89
							break;
90
						}
91
					case 'array':
92
					case 'resource':
93
					default:
94
						trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
95
						return null;
96
				}
97
				if (PasswordCompat\binary\_strlen($salt) < $required_salt_len) {
98
					trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", PasswordCompat\binary\_strlen($salt), $required_salt_len), E_USER_WARNING);
99
					return null;
100
				} elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
101
					$salt_requires_encoding = true;
102
				}
103
			} else {
104
				$buffer = '';
105
				$buffer_valid = false;
106
				if (function_exists('random_bytes')) {
107
					$buffer = random_bytes($raw_salt_len);
108
					if ($buffer) {
109
						$buffer_valid = true;
110
					}
111
				}
112
				if (!$buffer_valid && function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
113
					$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

113
					$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...
114
					if ($buffer) {
115
						$buffer_valid = true;
116
					}
117
				}
118
				if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
119
					$buffer = openssl_random_pseudo_bytes($raw_salt_len);
120
					if ($buffer) {
121
						$buffer_valid = true;
122
					}
123
				}
124
				if (!$buffer_valid && @is_readable('/dev/urandom')) {
125
					$f = fopen('/dev/urandom', 'r');
126
					$read = PasswordCompat\binary\_strlen($buffer);
127
					while ($read < $raw_salt_len) {
128
						$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

128
						$buffer .= fread(/** @scrutinizer ignore-type */ $f, $raw_salt_len - $read);
Loading history...
129
						$read = PasswordCompat\binary\_strlen($buffer);
130
					}
131
					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

131
					fclose(/** @scrutinizer ignore-type */ $f);
Loading history...
132
					if ($read >= $raw_salt_len) {
133
						$buffer_valid = true;
134
					}
135
				}
136
				if (!$buffer_valid || PasswordCompat\binary\_strlen($buffer) < $raw_salt_len) {
137
					$bl = PasswordCompat\binary\_strlen($buffer);
138
					for ($i = 0; $i < $raw_salt_len; $i++) {
139
						if ($i < $bl) {
140
							$buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
141
						} else {
142
							$buffer .= chr(mt_rand(0, 255));
143
						}
144
					}
145
				}
146
				$salt = $buffer;
147
				$salt_requires_encoding = true;
148
			}
149
			if ($salt_requires_encoding) {
150
				// encode string with the Base64 variant used by crypt
151
				$base64_digits =
152
					'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
153
				$bcrypt64_digits =
154
					'./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
155
156
				$base64_string = base64_encode($salt);
157
				$salt = strtr(rtrim($base64_string, '='), $base64_digits, $bcrypt64_digits);
158
			}
159
			$salt = PasswordCompat\binary\_substr($salt, 0, $required_salt_len);
160
161
			$hash = $hash_format . $salt;
162
163
			$ret = crypt($password, $hash);
164
165
			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...
166
				return false;
167
			}
168
169
			return $ret;
170
		}
171
172
		/**
173
		 * Get information about the password hash. Returns an array of the information
174
		 * that was used to generate the password hash.
175
		 *
176
		 * array(
177
		 *    'algo' => 1,
178
		 *    'algoName' => 'bcrypt',
179
		 *    'options' => array(
180
		 *        'cost' => 10,
181
		 *    ),
182
		 * )
183
		 *
184
		 * @param string $hash The password hash to extract info from
185
		 *
186
		 * @return array The array of information about the hash.
187
		 */
188
		function password_get_info($hash) {
189
			$return = array(
190
				'algo' => 0,
191
				'algoName' => 'unknown',
192
				'options' => array(),
193
			);
194
			if (PasswordCompat\binary\_substr($hash, 0, 4) == '$2y$' && PasswordCompat\binary\_strlen($hash) == 60) {
195
				$return['algo'] = PASSWORD_BCRYPT;
196
				$return['algoName'] = 'bcrypt';
197
				list($cost) = sscanf($hash, "$2y$%d$");
198
				$return['options']['cost'] = $cost;
199
			}
200
			return $return;
201
		}
202
203
		/**
204
		 * Determine if the password hash needs to be rehashed according to the options provided
205
		 *
206
		 * If the answer is true, after validating the password using password_verify, rehash it.
207
		 *
208
		 * @param string $hash    The hash to test
209
		 * @param int    $algo    The algorithm used for new password hashes
210
		 * @param array  $options The options array passed to password_hash
211
		 *
212
		 * @return boolean True if the password needs to be rehashed.
213
		 */
214
		function password_needs_rehash($hash, $algo, array $options = array()) {
215
			$info = password_get_info($hash);
216
			if ($info['algo'] != $algo) {
217
				return true;
218
			}
219
			switch ($algo) {
220
				case PASSWORD_BCRYPT:
221
					$cost = isset($options['cost']) ? $options['cost'] : 10;
222
					if ($cost != $info['options']['cost']) {
223
						return true;
224
					}
225
					break;
226
			}
227
			return false;
228
		}
229
230
		/**
231
		 * Verify a password against a hash using a timing attack resistant approach
232
		 *
233
		 * @param string $password The password to verify
234
		 * @param string $hash     The hash to verify against
235
		 *
236
		 * @return boolean If the password matches the hash
237
		 */
238
		function password_verify($password, $hash) {
239
			if (!function_exists('crypt')) {
240
				trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
241
				return false;
242
			}
243
			if (PasswordCompat\binary\_strlen($password) > 72) {
244
				$password = PasswordCompat\binary\_substr($password, 0, 72);
245
			}
246
			$ret = crypt($password, $hash);
247
			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...
248
				return false;
249
			}
250
251
			$status = 0;
252
			for ($i = 0; $i < PasswordCompat\binary\_strlen($ret); $i++) {
253
				$status |= (ord($ret[$i]) ^ ord($hash[$i]));
254
			}
255
256
			return $status === 0;
257
		}
258
	}
259
}
260
261
namespace PasswordCompat\binary {
262
	/**
263
	 * Count the number of bytes in a string
264
	 *
265
	 * We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension.
266
	 * In this case, strlen() will count the number of *characters* based on the internal encoding. A
267
	 * sequence of bytes might be regarded as a single multibyte character.
268
	 *
269
	 * @param string $binary_string The input string
270
	 *
271
	 * @internal
272
	 * @return int The number of bytes
273
	 */
274
	function _strlen($binary_string) {
275
		if (function_exists('mb_strlen')) {
276
			return mb_strlen($binary_string, '8bit');
277
		}
278
		return strlen($binary_string);
279
	}
280
281
	/**
282
	 * Get a substring based on byte limits
283
	 *
284
	 * @see _strlen()
285
	 *
286
	 * @param string $binary_string The input string
287
	 * @param int    $start
288
	 * @param int    $length
289
	 *
290
	 * @internal
291
	 * @return string The substring
292
	 */
293
	function _substr($binary_string, $start, $length) {
294
		if (function_exists('mb_substr')) {
295
			return mb_substr($binary_string, $start, $length, '8bit');
296
		}
297
		return substr($binary_string, $start, $length);
298
	}
299
}
300
301
?>