Completed
Pull Request — master (#2252)
by ྅༻ Ǭɀħ
02:27
created

PasswordHash::HashPassword()   D

Complexity

Conditions 10
Paths 29

Size

Total Lines 40
Code Lines 23

Duplication

Lines 15
Ratio 37.5 %

Importance

Changes 0
Metric Value
cc 10
eloc 23
nc 29
nop 1
dl 15
loc 40
rs 4.8196
c 0
b 0
f 0

How to fix   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
namespace Ozh\Phpass;
4
5
/**
6
 *
7
 * Portable PHP password hashing framework.
8
 *
9
 * Originally written by Solar Designer <solar at openwall.com> in 2004-2006
10
 *
11
 * Modernized by Hautelook at https://github.com/hautelook/phpass
12
 *
13
 * Slightly repacked by Ozh to extend compatibility from PHP 5.3 to 7.1 in a single file
14
 *
15
 * There's absolutely no warranty.
16
 *
17
 * The homepage URL for this framework is:
18
 *
19
 * http://www.openwall.com/phpass/
20
 *
21
 * Please be sure to update the Version line if you edit this file in any way.
22
 * It is suggested that you leave the main version number intact, but indicate
23
 * your project name (after the slash) and add your own revision information.
24
 *
25
 * Please do not change the "private" password hashing method implemented in
26
 * here, thereby making your hashes incompatible.  However, if you must, please
27
 * change the hash type identifier (the "$P$") to something different.
28
 *
29
 * Obviously, since this code is in the public domain, the above are not
30
 * requirements (there can be none), but merely suggestions.
31
 *
32
 * @author Solar Designer <[email protected]>
33
 */
34
class PasswordHash
35
{
36
    private $itoa64;
37
    private $iteration_count_log2;
38
    private $portable_hashes;
39
    private $random_state;
40
41
    /**
42
     * Constructor
43
     *
44
     * @param int $iteration_count_log2
45
     * @param boolean $portable_hashes
46
     */
47
    public function __construct($iteration_count_log2, $portable_hashes)
48
    {
49
        $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
50
51
        if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) {
52
            $iteration_count_log2 = 8;
53
        }
54
        $this->iteration_count_log2 = $iteration_count_log2;
55
56
        $this->portable_hashes = $portable_hashes;
57
58
        $this->random_state = microtime();
59
        if (function_exists('getmypid')) {
60
            $this->random_state .= getmypid();
61
        }
62
    }
63
64
    /**
65
     * @param  int $count
66
     * @return String
67
     */
68
    public function get_random_bytes($count)
69
    {
70
        $output = '';
71
        
72
        if (is_callable('random_bytes')) {
73
            return random_bytes($count);
74
        }
75
        
76
        if (@is_readable('/dev/urandom') &&
77
            ($fh = @fopen('/dev/urandom', 'rb'))) {
78
            $output = fread($fh, $count);
79
            fclose($fh);
80
        }
81
82
        if (strlen($output) < $count) {
83
            $output = '';
84
            for ($i = 0; $i < $count; $i += 16) {
85
                $this->random_state =
86
                    md5(microtime() . $this->random_state);
87
                $output .=
88
                    pack('H*', md5($this->random_state));
89
            }
90
            $output = substr($output, 0, $count);
91
        }
92
93
        return $output;
94
    }
95
96
    /**
97
     * @param  String $input
98
     * @param  int $count
99
     * @return String
100
     */
101
    public function encode64($input, $count)
102
    {
103
        $output = '';
104
        $i = 0;
105
        do {
106
            $value = ord($input[$i++]);
107
            $output .= $this->itoa64[$value & 0x3f];
108
            if ($i < $count) {
109
                $value |= ord($input[$i]) << 8;
110
            }
111
            $output .= $this->itoa64[($value >> 6) & 0x3f];
112
            if ($i++ >= $count) {
113
                break;
114
            }
115
            if ($i < $count) {
116
                $value |= ord($input[$i]) << 16;
117
            }
118
            $output .= $this->itoa64[($value >> 12) & 0x3f];
119
            if ($i++ >= $count) {
120
                break;
121
            }
122
            $output .= $this->itoa64[($value >> 18) & 0x3f];
123
        } while ($i < $count);
124
125
        return $output;
126
    }
127
128
    /**
129
     * @param  String $input
130
     * @return String
131
     */
132
    public function gensalt_private($input)
133
    {
134
        $output = '$P$';
135
        $output .= $this->itoa64[min($this->iteration_count_log2 +
136
            ((PHP_VERSION >= '5') ? 5 : 3), 30)];
137
        $output .= $this->encode64($input, 6);
138
139
        return $output;
140
    }
141
142
    /**
143
     * @param  String $password
144
     * @param  String $setting
145
     * @return String
146
     */
147
    public function crypt_private($password, $setting)
148
    {
149
        $output = '*0';
150
        if (substr($setting, 0, 2) == $output) {
151
            $output = '*1';
152
        }
153
154
        $id = substr($setting, 0, 3);
155
        # We use "$P$", phpBB3 uses "$H$" for the same thing
156
        if ($id != '$P$' && $id != '$H$') {
157
            return $output;
158
        }
159
160
        $count_log2 = strpos($this->itoa64, $setting[3]);
161
        if ($count_log2 < 7 || $count_log2 > 30) {
162
            return $output;
163
        }
164
165
        $count = 1 << $count_log2;
166
167
        $salt = substr($setting, 4, 8);
168
        if (strlen($salt) != 8) {
169
            return $output;
170
        }
171
172
        // We're kind of forced to use MD5 here since it's the only
173
        // cryptographic primitive available in all versions of PHP
174
        // currently in use.  To implement our own low-level crypto
175
        // in PHP would result in much worse performance and
176
        // consequently in lower iteration counts and hashes that are
177
        // quicker to crack (by non-PHP code).
178
        if (PHP_VERSION >= '5') {
179
            $hash = md5($salt . $password, TRUE);
180
            do {
181
                $hash = md5($hash . $password, TRUE);
182
            } while (--$count);
183
        } else {
184
            $hash = pack('H*', md5($salt . $password));
185
            do {
186
                $hash = pack('H*', md5($hash . $password));
187
            } while (--$count);
188
        }
189
190
        $output = substr($setting, 0, 12);
191
        $output .= $this->encode64($hash, 16);
192
193
        return $output;
194
    }
195
196
    /**
197
     * @param  String $input
198
     * @return String
199
     */
200
    public function gensalt_extended($input)
201
    {
202
        $count_log2 = min($this->iteration_count_log2 + 8, 24);
203
        // This should be odd to not reveal weak DES keys, and the
204
        // maximum valid value is (2**24 - 1) which is odd anyway.
205
        $count = (1 << $count_log2) - 1;
206
207
        $output = '_';
208
        $output .= $this->itoa64[$count & 0x3f];
209
        $output .= $this->itoa64[($count >> 6) & 0x3f];
210
        $output .= $this->itoa64[($count >> 12) & 0x3f];
211
        $output .= $this->itoa64[($count >> 18) & 0x3f];
212
213
        $output .= $this->encode64($input, 3);
214
215
        return $output;
216
    }
217
218
    /**
219
     * @param  String $input
220
     * @return String
221
     */
222
    public function gensalt_blowfish($input)
223
    {
224
        // This one needs to use a different order of characters and a
225
        // different encoding scheme from the one in encode64() above.
226
        // We care because the last character in our encoded string will
227
        // only represent 2 bits.  While two known implementations of
228
        // bcrypt will happily accept and correct a salt string which
229
        // has the 4 unused bits set to non-zero, we do not want to take
230
        // chances and we also do not want to waste an additional byte
231
        // of entropy.
232
        $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
233
234
        $output = '$2a$';
235
        $output .= chr(ord('0') + $this->iteration_count_log2 / 10);
236
        $output .= chr(ord('0') + $this->iteration_count_log2 % 10);
237
        $output .= '$';
238
239
        $i = 0;
240
        do {
241
            $c1 = ord($input[$i++]);
242
            $output .= $itoa64[$c1 >> 2];
243
            $c1 = ($c1 & 0x03) << 4;
244
            if ($i >= 16) {
245
                $output .= $itoa64[$c1];
246
                break;
247
            }
248
249
            $c2 = ord($input[$i++]);
250
            $c1 |= $c2 >> 4;
251
            $output .= $itoa64[$c1];
252
            $c1 = ($c2 & 0x0f) << 2;
253
254
            $c2 = ord($input[$i++]);
255
            $c1 |= $c2 >> 6;
256
            $output .= $itoa64[$c1];
257
            $output .= $itoa64[$c2 & 0x3f];
258
        } while (1);
259
260
        return $output;
261
    }
262
263
    /**
264
     * @param String $password
265
     */
266
    public function HashPassword($password)
267
    {
268
        $random = '';
269
270 View Code Duplication
        if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
271
            $random = $this->get_random_bytes(16);
272
            $hash =
273
                crypt($password, $this->gensalt_blowfish($random));
274
            if (strlen($hash) == 60) {
275
                return $hash;
276
            }
277
        }
278
279 View Code Duplication
        if (CRYPT_EXT_DES == 1 && !$this->portable_hashes) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
280
            if (strlen($random) < 3) {
281
                $random = $this->get_random_bytes(3);
282
            }
283
            $hash =
284
                crypt($password, $this->gensalt_extended($random));
285
            if (strlen($hash) == 20) {
286
                return $hash;
287
            }
288
        }
289
290
        if (strlen($random) < 6) {
291
            $random = $this->get_random_bytes(6);
292
        }
293
294
        $hash =
295
            $this->crypt_private($password,
296
            $this->gensalt_private($random));
297
        if (strlen($hash) == 34) {
298
            return $hash;
299
        }
300
301
        // Returning '*' on error is safe here, but would _not_ be safe
302
        // in a crypt(3)-like function used _both_ for generating new
303
        // hashes and for validating passwords against existing hashes.
304
        return '*';
305
    }
306
307
    /**
308
     * @param String $password
309
     * @param String $stored_hash
310
     * @return boolean
311
     */
312
    public function CheckPassword($password, $stored_hash)
313
    {
314
        $hash = $this->crypt_private($password, $stored_hash);
315
        if ($hash[0] == '*') {
316
            $hash = crypt($password, $stored_hash);
317
        }
318
319
        return hash_equals($stored_hash, $hash);
320
    }
321
}
322
323
324
/**
325
 * hash_equals compatibility function
326
 *
327
 * @package     CodeIgniter
328
 * @author      EllisLab Dev Team
329
 * @copyright   Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
330
 * @copyright   Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/)
331
 * @license     http://opensource.org/licenses/MIT	MIT License
332
 * @link        https://codeigniter.com
333
 *
334
 * Source: https://github.com/bcit-ci/CodeIgniter/blob/3.1.4/system/core/compat/hash.php
335
 * For PHP < 5.6
336
 */
337
if ( ! function_exists('hash_equals'))
338
{
339
	/**
340
	 * hash_equals()
341
	 *
342
	 * @link	http://php.net/hash_equals
343
	 * @param	string	$known_string
344
	 * @param	string	$user_string
345
	 * @return	bool
346
	 */
347
	function hash_equals($known_string, $user_string)
348
	{
349
		if ( ! is_string($known_string))
350
		{
351
			trigger_error('hash_equals(): Expected known_string to be a string, '.strtolower(gettype($known_string)).' given', E_USER_WARNING);
352
			return FALSE;
353
		}
354
		elseif ( ! is_string($user_string))
355
		{
356
			trigger_error('hash_equals(): Expected user_string to be a string, '.strtolower(gettype($user_string)).' given', E_USER_WARNING);
357
			return FALSE;
358
		}
359
		elseif (($length = strlen($known_string)) !== strlen($user_string))
360
		{
361
			return FALSE;
362
		}
363
		$diff = 0;
364
		for ($i = 0; $i < $length; $i++)
365
		{
366
			$diff |= ord($known_string[$i]) ^ ord($user_string[$i]);
367
		}
368
		return ($diff === 0);
369
	}
370
}
371
372