ElggCrypto::getRandomBytes()   F
last analyzed

Complexity

Conditions 19
Paths 259

Size

Total Lines 104

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 284.9861

Importance

Changes 0
Metric Value
cc 19
nc 259
nop 1
dl 0
loc 104
ccs 6
cts 62
cp 0.0968
crap 284.9861
rs 2.3565
c 0
b 0
f 0

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
 * \ElggCrypto
4
 *
5
 * @package    Elgg.Core
6
 * @subpackage Crypto
7
 *
8
 * @access private
9
 */
10
class ElggCrypto {
11
12
	/**
13
	 * Character set for temp passwords (no risk of embedded profanity/glyphs that look similar)
14
	 */
15
	const CHARS_PASSWORD = 'bcdfghjklmnpqrstvwxyz2346789';
16
17
	/**
18
	 * Character set for hexadecimal
19
	 */
20
	const CHARS_HEX = '0123456789abcdef';
21
22
	/**
23
	 * Generate a string of highly randomized bytes (over the full 8-bit range).
24
	 *
25
	 * @param int $length Number of bytes needed
26
	 * @return string Random bytes
27
	 *
28
	 * @author George Argyros <[email protected]>
29
	 * @copyright 2012, George Argyros. All rights reserved.
30
	 * @license Modified BSD
31
	 * @link https://github.com/GeorgeArgyros/Secure-random-bytes-in-PHP/blob/master/srand.php Original
32
	 *
33
	 * Redistribution and use in source and binary forms, with or without
34
	 * modification, are permitted provided that the following conditions are met:
35
	 *    * Redistributions of source code must retain the above copyright
36
	 *      notice, this list of conditions and the following disclaimer.
37
	 *    * Redistributions in binary form must reproduce the above copyright
38
	 *      notice, this list of conditions and the following disclaimer in the
39
	 *      documentation and/or other materials provided with the distribution.
40
	 *    * Neither the name of the <organization> nor the
41
	 *      names of its contributors may be used to endorse or promote products
42
	 *      derived from this software without specific prior written permission.
43
	 *
44
	 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
45
	 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
46
	 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
47
	 * DISCLAIMED. IN NO EVENT SHALL GEORGE ARGYROS BE LIABLE FOR ANY
48
	 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
49
	 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
50
	 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
51
	 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
52
	 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
53
	 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
54
	 */
55 3
	public function getRandomBytes($length) {
56 3
		$SSLstr = '4'; // http://xkcd.com/221/
57
58
		/**
59
		 * Our primary choice for a cryptographic strong randomness function is
60
		 * openssl_random_pseudo_bytes.
61
		 */
62 3
		if (function_exists('openssl_random_pseudo_bytes') && substr(PHP_OS, 0, 3) !== 'WIN') {
63 3
			$SSLstr = openssl_random_pseudo_bytes($length, $strong);
64 3
			if ($strong) {
65 3
				return $SSLstr;
66
			}
67
		}
68
69
		/**
70
		 * If mcrypt extension is available then we use it to gather entropy from
71
		 * the operating system's PRNG. This is better than reading /dev/urandom
72
		 * directly since it avoids reading larger blocks of data than needed.
73
		 */
74
		if (function_exists('mcrypt_create_iv') && substr(PHP_OS, 0, 3) !== 'WIN') {
75
			$str = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
76
			if ($str !== false) {
77
				return $str;
78
			}
79
		}
80
81
		/**
82
		 * No build-in crypto randomness function found. We collect any entropy
83
		 * available in the PHP core PRNGs along with some filesystem info and memory
84
		 * stats. To make this data cryptographically strong we add data either from
85
		 * /dev/urandom or if its unavailable, we gather entropy by measuring the
86
		 * time needed to compute a number of SHA-1 hashes.
87
		 */
88
		$str = '';
89
		$bits_per_round = 2; // bits of entropy collected in each clock drift round
90
		$msec_per_round = 400; // expected running time of each round in microseconds
91
		$hash_len = 20; // SHA-1 Hash length
92
		$total = $length; // total bytes of entropy to collect
93
94
		$handle = @fopen('/dev/urandom', 'rb');
95
		if ($handle && function_exists('stream_set_read_buffer')) {
96
			@stream_set_read_buffer($handle, 0);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
97
		}
98
99
		do {
100
			$bytes = ($total > $hash_len) ? $hash_len : $total;
101
			$total -= $bytes;
102
103
			//collect any entropy available from the PHP system and filesystem
104
			$entropy = rand() . uniqid(mt_rand(), true) . $SSLstr;
105
			$entropy .= implode('', @fstat(@fopen(__FILE__, 'r')));
106
			$entropy .= memory_get_usage() . getmypid();
107
			$entropy .= serialize($_ENV) . serialize($_SERVER);
108
			if (function_exists('posix_times')) {
109
				$entropy .= serialize(posix_times());
110
			}
111
			if (function_exists('zend_thread_id')) {
112
				$entropy .= zend_thread_id();
113
			}
114
115
			if ($handle) {
116
				$entropy .= @fread($handle, $bytes);
117
			} else {
118
				// Measure the time that the operations will take on average
119
				for ($i = 0; $i < 3; $i++) {
120
					$c1 = microtime(true);
121
					$var = sha1(mt_rand());
122
					for ($j = 0; $j < 50; $j++) {
123
						$var = sha1($var);
124
					}
125
					$c2 = microtime(true);
126
					$entropy .= $c1 . $c2;
127
				}
128
129
				// Based on the above measurement determine the total rounds
130
				// in order to bound the total running time.
131
				$rounds = (int) ($msec_per_round * 50 / (int) (($c2 - $c1) * 1000000));
0 ignored issues
show
Bug introduced by
The variable $c2 does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $c1 does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
132
133
				// Take the additional measurements. On average we can expect
134
				// at least $bits_per_round bits of entropy from each measurement.
135
				$iter = $bytes * (int) (ceil(8 / $bits_per_round));
136
137
				for ($i = 0; $i < $iter; $i++) {
138
					$c1 = microtime();
139
					$var = sha1(mt_rand());
140
					for ($j = 0; $j < $rounds; $j++) {
141
						$var = sha1($var);
142
					}
143
					$c2 = microtime();
144
					$entropy .= $c1 . $c2;
145
				}
146
			}
147
148
			// We assume sha1 is a deterministic extractor for the $entropy variable.
149
			$str .= sha1($entropy, true);
150
151
		} while ($length > strlen($str));
152
153
		if ($handle) {
154
			@fclose($handle);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
155
		}
156
157
		return substr($str, 0, $length);
158
	}
159
160
	/**
161
	 * Get an HMAC token builder/validator object
162
	 *
163
	 * @param mixed  $data HMAC data or serializable data
164
	 * @param string $algo Hash algorithm
165
	 * @param string $key  Optional key (default uses site secret)
166
	 *
167
	 * @return \Elgg\Security\Hmac
168
	 */
169 7
	public function getHmac($data, $algo = 'sha256', $key = '') {
170 7
		if (!$key) {
171
			$key = _elgg_services()->siteSecret->get(true);
172
		}
173 7
		return new Elgg\Security\Hmac($key, [$this, 'areEqual'], $data, $algo);
0 ignored issues
show
Bug introduced by
It seems like $key defined by _elgg_services()->siteSecret->get(true) on line 171 can also be of type false or null; however, Elgg\Security\Hmac::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
174
	}
175
176
	/**
177
	 * Generate a random string of specified length.
178
	 *
179
	 * Uses supplied character list for generating the new string.
180
	 * If no character list provided - uses Base64 URL character set.
181
	 *
182
	 * @param int         $length Desired length of the string
183
	 * @param string|null $chars  Characters to be chosen from randomly. If not given, the Base64 URL
184
	 *                            charset will be used.
185
	 *
186
	 * @return string The random string
187
	 *
188
	 * @throws InvalidArgumentException
189
	 *
190
	 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
191
	 * @license   http://framework.zend.com/license/new-bsd New BSD License
192
	 *
193
	 * @see https://github.com/zendframework/zf2/blob/master/library/Zend/Math/Rand.php#L179
194
	 */
195 7
	public function getRandomString($length, $chars = null) {
196 7
		if ($length < 1) {
197
			throw new \InvalidArgumentException('Length should be >= 1');
198
		}
199
200 7
		if (empty($chars)) {
201 4
			$numBytes = ceil($length * 0.75);
202 4
			$bytes    = $this->getRandomBytes($numBytes);
203 4
			$string = substr(rtrim(base64_encode($bytes), '='), 0, $length);
204
205
			// Base64 URL
206 4
			return strtr($string, '+/', '-_');
207
		}
208
209 3
		if ($chars == self::CHARS_HEX) {
210
			// hex is easy
211 1
			$bytes = $this->getRandomBytes(ceil($length / 2));
212 1
			return substr(bin2hex($bytes), 0, $length);
213
		}
214
215 2
		$listLen = strlen($chars);
216
217 2
		if ($listLen == 1) {
218
			return str_repeat($chars, $length);
219
		}
220
221 2
		$bytes  = $this->getRandomBytes($length);
222 2
		$pos    = 0;
223 2
		$result = '';
224 2
		for ($i = 0; $i < $length; $i++) {
225 2
			$pos     = ($pos + ord($bytes[$i])) % $listLen;
226 2
			$result .= $chars[$pos];
227 2
		}
228
229 2
		return $result;
230
	}
231
232
	/**
233
	 * Are two strings equal (compared in constant time)?
234
	 *
235
	 * @param string $str1 First string to compare
236
	 * @param string $str2 Second string to compare
237
	 *
238
	 * @return bool
239
	 *
240
	 * Based on password_verify in PasswordCompat
241
	 * @author Anthony Ferrara <[email protected]>
242
	 * @license http://www.opensource.org/licenses/mit-license.html MIT License
243
	 * @copyright 2012 The Authors
244
	 */
245 1
	public function areEqual($str1, $str2) {
246 1
		$len1 = $this->strlen($str1);
247 1
		$len2 = $this->strlen($str2);
248 1
		if ($len1 !== $len2) {
249
			return false;
250
		}
251
252 1
		$status = 0;
253 1
		for ($i = 0; $i < $len1; $i++) {
254 1
			$status |= (ord($str1[$i]) ^ ord($str2[$i]));
255 1
		}
256
257 1
		return $status === 0;
258
	}
259
260
	/**
261
	 * Count the number of bytes in a string
262
	 *
263
	 * We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension.
264
	 * In this case, strlen() will count the number of *characters* based on the internal encoding. A
265
	 * sequence of bytes might be regarded as a single multibyte character.
266
	 *
267
	 * Use elgg_strlen() to count UTF-characters instead of bytes.
268
	 *
269
	 * @param string $binary_string The input string
270
	 *
271
	 * @return int The number of bytes
272
	 *
273
	 * From PasswordCompat\binary\_strlen
274
	 * @author Anthony Ferrara <[email protected]>
275
	 * @license http://www.opensource.org/licenses/mit-license.html MIT License
276
	 * @copyright 2012 The Authors
277
	 */
278 1
	protected function strlen($binary_string) {
279 1
		if (function_exists('mb_strlen')) {
280 1
			return mb_strlen($binary_string, '8bit');
281
		}
282
		return strlen($binary_string);
283
	}
284
}
285