PhpassPassword::hash()   D
last analyzed

Complexity

Conditions 10
Paths 29

Size

Total Lines 39
Code Lines 23

Duplication

Lines 18
Ratio 46.15 %

Code Coverage

Tests 23
CRAP Score 10.1536

Importance

Changes 0
Metric Value
cc 10
eloc 23
nc 29
nop 1
dl 18
loc 39
ccs 23
cts 26
cp 0.8846
crap 10.1536
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
 * @author Todd Burry <[email protected]>
4
 * @copyright 2009-2014 Vanilla Forums Inc.
5
 * @license MIT
6
 */
7
8
namespace Garden\Password;
9
10
/**
11
 * Implements the portable PHP password hashing framework.
12
 *
13
 * The code in this class is copied from the phppass library version 0.3 located at http://www.openwall.com/phpass/.
14
 * Any code copied from the phppass library is copyright the original owner.
15
 */
16
class PhpassPassword implements IPassword {
17
    const HASH_PHPASS = 0x00;
18
    const HASH_BLOWFISH = 0x01;
19
    const HASH_EXTDES = 0x02;
20
    const HASH_BEST = 0x03;
21
22
    protected $itoa64;
23
    protected $iteration_count_log2;
24
    protected $portable;
25
    protected $random_state;
26
27
    protected $hashMethod;
28
29
    /**
30
     * Initializes an instance of the of the {@link PhpPass} class.
31
     *
32
     * @param int $hashMethod The hash method to use when hashing passwords.
33
     * @param int $iteration_count_log2 The number of times to iterate when generating the passwords.
34
     */
35 6
    public function __construct($hashMethod = PhpassPassword::HASH_PHPASS, $iteration_count_log2 = 8) {
36 6
        $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
37
38 6
        if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) {
39 4
            $iteration_count_log2 = 8;
40 4
        }
41 6
        $this->iteration_count_log2 = $iteration_count_log2;
42 6
        $this->hashMethod = $hashMethod;
43
44 6
        $this->random_state = microtime();
45 6
        if (function_exists('getmypid')) {
46 6
            $this->random_state .= getmypid();
47 6
        }
48 6
    }
49
50
    /**
51
     * {@inheritdoc}
52
     */
53 1
    public function needsRehash($hash) {
54 1
        $id = substr($hash, 0, 3);
55 1
        $portable = ($id != '$P$' && $id != '$H$');
56
57 1
        return $portable;
58
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63 12
    public function hash($password) {
64 12
        $random = '';
65
66 12 View Code Duplication
        if (CRYPT_BLOWFISH == 1 && ($this->hashMethod & self::HASH_BLOWFISH)) {
67 7
            $random = $this->getRandomBytes(16);
68
            $hash =
69 7
                crypt($password, $this->gensaltBlowfish($random));
70 7
            if (strlen($hash) == 60) {
71 7
                return $hash;
72
            }
73
        }
74
75 6 View Code Duplication
        if (CRYPT_EXT_DES == 1 && ($this->hashMethod & self::HASH_EXTDES)) {
76 2
            if (strlen($random) < 3) {
77 2
                $random = $this->getRandomBytes(3);
78 2
            }
79
            $hash =
80 2
                crypt($password, $this->gensaltExtended($random));
81 2
            if (strlen($hash) == 20) {
82 2
                return $hash;
83
            }
84
        }
85
86 5
        if (strlen($random) < 6) {
87 5
            $random = $this->getRandomBytes(6);
88 5
        }
89 5
        $hash = $this->cryptPrivate(
90 5
            $password,
91 5
            $this->gensaltPrivate($random)
92 5
        );
93 5
        if (strlen($hash) == 34) {
94 5
            return $hash;
95
        }
96
97
        # Returning '*' on error is safe here, but would _not_ be safe
98
        # in a crypt(3)-like function used _both_ for generating new
99
        # hashes and for validating passwords against existing hashes.
100
        return '*';
101
    }
102
103
    /**
104
     * Get a string of random bytes.
105
     *
106
     * @param int $count The number of bytes to get.
107
     * @return string Returns a string of the generated random bytes.
108
     */
109 12
    protected function getRandomBytes($count) {
110 12
        $output = '';
111 12
        if (is_readable('/dev/urandom') &&
112 12
            ($fh = @fopen('/dev/urandom', 'rb'))
113 12
        ) {
114 12
            $output = fread($fh, $count);
115 12
            fclose($fh);
116 12
        }
117
118 12
        if (strlen($output) < $count) {
119
            $output = '';
120
            for ($i = 0; $i < $count; $i += 16) {
121
                $this->random_state =
122
                    md5(microtime().$this->random_state);
123
                $output .=
124
                    pack('H*', md5($this->random_state));
125
            }
126
            $output = substr($output, 0, $count);
127
        }
128
129 12
        return $output;
130
    }
131
132
    /**
133
     * Generate a password salt appropriate for blowfish.
134
     *
135
     * @param string $input The random input to generate the salt from.
136
     * @return string The generated salt.
137
     */
138 7
    protected function gensaltBlowfish($input) {
139
        # This one needs to use a different order of characters and a
140
        # different encoding scheme from the one in encode64() above.
141
        # We care because the last character in our encoded string will
142
        # only represent 2 bits.  While two known implementations of
143
        # bcrypt will happily accept and correct a salt string which
144
        # has the 4 unused bits set to non-zero, we do not want to take
145
        # chances and we also do not want to waste an additional byte
146
        # of entropy.
147 7
        $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
148
149 7
        $output = '$2a$';
150 7
        $output .= chr(ord('0') + $this->iteration_count_log2 / 10);
151 7
        $output .= chr(ord('0') + $this->iteration_count_log2 % 10);
152 7
        $output .= '$';
153
154 7
        $i = 0;
155
        do {
156 7
            $c1 = ord($input[$i++]);
157 7
            $output .= $itoa64[$c1 >> 2];
158 7
            $c1 = ($c1 & 0x03) << 4;
159 7
            if ($i >= 16) {
160 7
                $output .= $itoa64[$c1];
161 7
                break;
162
            }
163
164 7
            $c2 = ord($input[$i++]);
165 7
            $c1 |= $c2 >> 4;
166 7
            $output .= $itoa64[$c1];
167 7
            $c1 = ($c2 & 0x0f) << 2;
168
169 7
            $c2 = ord($input[$i++]);
170 7
            $c1 |= $c2 >> 6;
171 7
            $output .= $itoa64[$c1];
172 7
            $output .= $itoa64[$c2 & 0x3f];
173 7
        } while (1);
174
175 7
        return $output;
176
    }
177
178
    /**
179
     * Generate a password salt based on the input.
180
     *
181
     * @param string $input The string to generate the salt from.
182
     * @return string The generated salt.
183
     */
184 2
    private function gensaltExtended($input) {
185 2
        $count_log2 = min($this->iteration_count_log2 + 8, 24);
186
        # This should be odd to not reveal weak DES keys, and the
187
        # maximum valid value is (2**24 - 1) which is odd anyway.
188 2
        $count = (1 << $count_log2) - 1;
189
190 2
        $output = '_';
191 2
        $output .= $this->itoa64[$count & 0x3f];
192 2
        $output .= $this->itoa64[($count >> 6) & 0x3f];
193 2
        $output .= $this->itoa64[($count >> 12) & 0x3f];
194 2
        $output .= $this->itoa64[($count >> 18) & 0x3f];
195
196 2
        $output .= $this->encode64($input, 3);
197
198 2
        return $output;
199
    }
200
201
    /**
202
     * A custom base64 encoding function.
203
     *
204
     * @param string $input The string to encode.
205
     * @param int $count The number of characters to encode.
206
     * @return string Returns the encoded string.
207
     */
208 6
    protected function encode64($input, $count) {
209 6
        $output = '';
210 6
        $i = 0;
211
        do {
212 6
            $value = ord($input[$i++]);
213 6
            $output .= $this->itoa64[$value & 0x3f];
214 6
            if ($i < $count) {
215 6
                $value |= ord($input[$i]) << 8;
216 6
            }
217 6
            $output .= $this->itoa64[($value >> 6) & 0x3f];
218 6
            if ($i++ >= $count) {
219 5
                break;
220
            }
221 6
            if ($i < $count) {
222 6
                $value |= ord($input[$i]) << 16;
223 6
            }
224 6
            $output .= $this->itoa64[($value >> 12) & 0x3f];
225 6
            if ($i++ >= $count) {
226
                break;
227
            }
228 6
            $output .= $this->itoa64[($value >> 18) & 0x3f];
229 6
        } while ($i < $count);
230
231 6
        return $output;
232
    }
233
234
    /**
235
     * A portable version of a crypt-like algorithm.
236
     *
237
     * @param string $password The plaintext password to encrypt.
238
     * @param string $setting The hash prefix that defines what kind of algorithm to use.
239
     * @return string Returns the encrypted string.
240
     */
241 11
    private function cryptPrivate($password, $setting) {
242 11
        $output = '*0';
243 11
        if (substr($setting, 0, 2) == $output) {
244
            $output = '*1';
245
        }
246
247 11
        $id = substr($setting, 0, 3);
248
        # We use "$P$", phpBB3 uses "$H$" for the same thing
249 11
        if ($id != '$P$' && $id != '$H$') {
250 7
            return $output;
251
        }
252
253 5
        $count_log2 = strpos($this->itoa64, $setting[3]);
254 5
        if ($count_log2 < 7 || $count_log2 > 30) {
255
            return $output;
256
        }
257
258 5
        $count = 1 << $count_log2;
259
260 5
        $salt = substr($setting, 4, 8);
261 5
        if (strlen($salt) != 8) {
262
            return $output;
263
        }
264
265
        # We're kind of forced to use MD5 here since it's the only
266
        # cryptographic primitive available in all versions of PHP
267
        # currently in use.  To implement our own low-level crypto
268
        # in PHP would result in much worse performance and
269
        # consequently in lower iteration counts and hashes that are
270
        # quicker to crack (by non-PHP code).
271 5 View Code Duplication
        if (PHP_VERSION >= '5') {
272 5
            $hash = md5($salt.$password, true);
273
            do {
274 5
                $hash = md5($hash.$password, false);
275 5
            } while (--$count);
276 5
        } else {
277
            $hash = pack('H*', md5($salt.$password));
278
            do {
279
                $hash = pack('H*', md5($hash.$password));
280
            } while (--$count);
281
        }
282
283 5
        $output = substr($setting, 0, 12);
284 5
        $output .= $this->encode64($hash, 16);
285
286 5
        return $output;
287
    }
288
289
    /**
290
     * Generate a password salt based on the given input string.
291
     *
292
     * @param string $input The input string to generate the salt from.
293
     * @return string Returns the password salt prefixed with `$P$`.
294
     */
295 5
    private function gensaltPrivate($input) {
296 5
        $output = '$P$';
297 5
        $output .= $this->itoa64[min($this->iteration_count_log2 + ((PHP_VERSION >= '5') ? 5 : 3), 30)];
298 5
        $output .= $this->encode64($input, 6);
299
300 5
        return $output;
301
    }
302
303
    /**
304
     * {@inheritdoc}
305
     */
306 11
    public function verify($password, $hash) {
307 11
        if (!$hash) {
308 1
            return false;
309
        }
310 10
        $calc_hash = $this->cryptPrivate($password, $hash);
311 10
        if ($calc_hash[0] === '*') {
312 7
            $calc_hash = crypt($password, $hash);
313 7
        }
314
315 10
        return $calc_hash === $hash;
316
    }
317
318
    /**
319
     * Get the current hash method.
320
     *
321
     * @return int Returns the current hash method.
322
     */
323
    public function getHashMethod() {
324
        return $this->hashMethod;
325
    }
326
327
    /**
328
     * Set the current hash method.
329
     *
330
     * @param int $hashMethod The new hash mathod.
331
     * @return PhpassPassword Returns $this for fluent calls.
332
     */
333 1
    public function setHashMethod($hashMethod) {
334 1
        $this->hashMethod = $hashMethod;
335 1
        return $this;
336
    }
337
}
338