Completed
Push — release-2.1 ( f30e04...3ac280 )
by Mert
07:11
created

Subs-Password.php ➔ _substr()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 2
eloc 4
nc 2
nop 3
dl 0
loc 6
rs 9.4285
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
 * Simple Machines Forum (SMF)
10
 *
11
 * @package SMF
12
 * @author Simple Machines http://www.simplemachines.org
13
 * @copyright 2016 Simple Machines and individual contributors
14
 * @license http://www.simplemachines.org/about/smf/license.php BSD
15
 *
16
 * @version 2.1 Beta 3
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)) {
43
				trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
44
				return null;
45
			}
46
			if (!is_int($algo)) {
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
			$resultLength = 0;
0 ignored issues
show
Unused Code introduced by
$resultLength is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...