/** * If the libsodium PHP extension is loaded, we'll use it above any other * solution. * * libsodium-php project: * @ref https://github.com/jedisct1/libsodium-php * * @param int $bytes * * @throws Exception * * @return string */ function random_bytes($bytes) { try { $bytes = RandomCompat_intval($bytes); } catch (TypeError $ex) { throw new TypeError('random_bytes(): $bytes must be an integer'); } if ($bytes < 1) { throw new Error('Length must be greater than 0'); } /** * \Sodium\randombytes_buf() doesn't allow more than 2147483647 bytes to be * generated in one invocation. */ if ($bytes > 2147483647) { $buf = ''; for ($i = 0; $i < $bytes; $i += 1073741824) { $n = $bytes - $i > 1073741824 ? 1073741824 : $bytes - $i; $buf .= \Sodium\randombytes_buf($n); } } else { $buf = \Sodium\randombytes_buf($bytes); } if ($buf !== false) { if (RandomCompat_strlen($buf) === $bytes) { return $buf; } } /** * If we reach here, PHP has failed us. */ throw new Exception('Could not gather sufficient random data'); }
public function testIntval() { if (!function_exists('RandomCompat_intval')) { return $this->markTestSkipped('We don\' need to test this in PHP 7.'); } // Equals $this->assertEquals(abs(RandomCompat_intval(-4.5)), abs(RandomCompat_intval(4.5))); // True $this->assertTrue(is_int(RandomCompat_intval(PHP_INT_MAX, true))); $this->assertTrue(is_int(RandomCompat_intval(~PHP_INT_MAX, true))); $this->assertTrue(is_int(RandomCompat_intval(~PHP_INT_MAX + 1, true))); $this->assertTrue(is_int(RandomCompat_intval("1337e3", true))); $this->assertTrue(is_int(RandomCompat_intval("1.", true))); // False $this->assertFalse(is_int(RandomCompat_intval((double) PHP_INT_MAX, true))); $this->assertFalse(is_int(RandomCompat_intval((double) ~PHP_INT_MAX, true))); $this->assertFalse(is_int(RandomCompat_intval(PHP_INT_MAX + 1, true))); $this->assertFalse(is_int(RandomCompat_intval(~PHP_INT_MAX - 1, true))); $this->assertFalse(is_int(RandomCompat_intval(~PHP_INT_MAX - 0.1, true))); $this->assertFalse(is_int(RandomCompat_intval(PHP_INT_MAX + 0.1, true))); $this->assertFalse(is_int(RandomCompat_intval("hello", true))); if (PHP_INT_SIZE === 8) { $this->assertFalse(is_int(RandomCompat_intval("-9223372036854775809", true))); $this->assertTrue(is_int(RandomCompat_intval("-9223372036854775808", true))); $this->assertFalse(is_int(RandomCompat_intval("9223372036854775808", true))); $this->assertTrue(is_int(RandomCompat_intval("9223372036854775807", true))); } else { $this->assertFalse(is_int(RandomCompat_intval("2147483648", true))); $this->assertTrue(is_int(RandomCompat_intval("2147483647", true))); $this->assertFalse(is_int(RandomCompat_intval("-2147483649", true))); $this->assertTrue(is_int(RandomCompat_intval("-2147483648", true))); } }
/** * Windows with PHP < 5.3.0 will not have the function * openssl_random_pseudo_bytes() available, so let's use * CAPICOM to work around this deficiency. * * @param int $bytes * * @throws Exception * * @return string */ function random_bytes($bytes) { try { $bytes = RandomCompat_intval($bytes); } catch (TypeError $ex) { throw new TypeError('random_bytes(): $bytes must be an integer'); } if ($bytes < 1) { throw new Error('Length must be greater than 0'); } $buf = ''; $util = new COM('CAPICOM.Utilities.1'); $execCount = 0; /** * Let's not let it loop forever. If we run N times and fail to * get N bytes of random data, then CAPICOM has failed us. */ do { $buf .= base64_decode($util->GetRandom($bytes, 0)); if (RandomCompat_strlen($buf) >= $bytes) { /** * Return our random entropy buffer here: */ return RandomCompat_substr($buf, 0, $bytes); } ++$execCount; } while ($execCount < $bytes); /** * If we reach here, PHP has failed us. */ throw new Exception('Could not gather sufficient random data'); }
/** * Since openssl_random_pseudo_bytes() uses openssl's * RAND_pseudo_bytes() API, which has been marked as deprecated by the * OpenSSL team, this is our last resort before failure. * * @ref https://www.openssl.org/docs/crypto/RAND_bytes.html * * @param int $bytes * * @throws Exception * * @return string */ function random_bytes($bytes) { try { $bytes = RandomCompat_intval($bytes); } catch (TypeError $ex) { throw new TypeError('random_bytes(): $bytes must be an integer'); } if ($bytes < 1) { throw new Error('Length must be greater than 0'); } $secure = true; /** * $secure is passed by reference. If it's set to false, fail. Note * that this will only return false if this function fails to return * any data. * * @ref https://github.com/paragonie/random_compat/issues/6#issuecomment-119564973 */ $buf = openssl_random_pseudo_bytes($bytes, $secure); if ($buf !== false && $secure) { if (RandomCompat_strlen($buf) === $bytes) { return $buf; } } /** * If we reach here, PHP has failed us. */ throw new Exception('Could not gather sufficient random data'); }
public function testIntval() { if (!function_exists('RandomCompat_intval')) { return $this->markTestSkipped('We don\' need to test this in PHP 7.'); } // Equals $this->assertEquals(abs(RandomCompat_intval(-4.5)), abs(RandomCompat_intval(4.5))); // True $this->assertTrue(is_int(RandomCompat_intval(PHP_INT_MAX, true))); $this->assertTrue(is_int(RandomCompat_intval(~PHP_INT_MAX, true))); $this->assertTrue(is_int(RandomCompat_intval(~PHP_INT_MAX + 1, true))); // False $this->assertFalse(is_int(RandomCompat_intval((double) PHP_INT_MAX, true))); $this->assertFalse(is_int(RandomCompat_intval((double) ~PHP_INT_MAX, true))); $this->assertFalse(is_int(RandomCompat_intval(PHP_INT_MAX + 1, true))); $this->assertFalse(is_int(RandomCompat_intval(~PHP_INT_MAX - 1, true))); $this->assertFalse(is_int(RandomCompat_intval(PHP_INT_MAX - 0.01, true))); $this->assertFalse(is_int(RandomCompat_intval(~PHP_INT_MAX + 0.01, true))); }
/** * Powered by ext/mcrypt (and thankfully NOT libmcrypt) * * @ref https://bugs.php.net/bug.php?id=55169 * @ref https://github.com/php/php-src/blob/c568ffe5171d942161fc8dda066bce844bdef676/ext/mcrypt/mcrypt.c#L1321-L1386 * * @param int $bytes * * @throws Exception * * @return string */ function random_bytes($bytes) { try { $bytes = RandomCompat_intval($bytes); } catch (TypeError $ex) { throw new TypeError('random_bytes(): $bytes must be an integer'); } if ($bytes < 1) { throw new Error('Length must be greater than 0'); } $buf = @mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM); if ($buf !== false && RandomCompat_strlen($buf) === $bytes) { /** * Return our random entropy buffer here: */ return $buf; } /** * If we reach here, PHP has failed us. */ throw new Exception('Could not gather sufficient random data'); }
/** * Unless open_basedir is enabled, use /dev/urandom for * random numbers in accordance with best practices * * Why we use /dev/urandom and not /dev/random * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers * * @param int $bytes * * @throws Exception * * @return string */ function random_bytes($bytes) { static $fp = null; /** * This block should only be run once */ if (empty($fp)) { /** * We use /dev/urandom if it is a char device. * We never fall back to /dev/random */ $fp = fopen('/dev/urandom', 'rb'); if (!empty($fp)) { $st = fstat($fp); if (($st['mode'] & 0170000) !== 020000) { fclose($fp); $fp = false; } } if (!empty($fp)) { /** * stream_set_read_buffer() does not exist in HHVM * * If we don't set the stream's read buffer to 0, PHP will * internally buffer 8192 bytes, which can waste entropy * * stream_set_read_buffer returns 0 on success */ if (function_exists('stream_set_read_buffer')) { stream_set_read_buffer($fp, RANDOM_COMPAT_READ_BUFFER); } if (function_exists('stream_set_chunk_size')) { stream_set_chunk_size($fp, RANDOM_COMPAT_READ_BUFFER); } } } try { $bytes = RandomCompat_intval($bytes); } catch (TypeError $ex) { throw new TypeError('random_bytes(): $bytes must be an integer'); } if ($bytes < 1) { throw new Error('Length must be greater than 0'); } /** * This if() block only runs if we managed to open a file handle * * It does not belong in an else {} block, because the above * if (empty($fp)) line is logic that should only be run once per * page load. */ if (!empty($fp)) { $remaining = $bytes; $buf = ''; /** * We use fread() in a loop to protect against partial reads */ do { $read = fread($fp, $remaining); if ($read === false) { /** * We cannot safely read from the file. Exit the * do-while loop and trigger the exception condition */ $buf = false; break; } /** * Decrease the number of bytes returned from remaining */ $remaining -= RandomCompat_strlen($read); $buf .= $read; } while ($remaining > 0); /** * Is our result valid? */ if ($buf !== false) { if (RandomCompat_strlen($buf) === $bytes) { /** * Return our random entropy buffer here: */ return $buf; } } } /** * If we reach here, PHP has failed us. */ throw new Exception('Error reading from source device'); }
/** * Fetch a random integer between $min and $max inclusive * * @param int $min * @param int $max * * @throws Exception * * @return int */ function random_int($min, $max) { /** * Type and input logic checks * * If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX) * (non-inclusive), it will sanely cast it to an int. If you it's equal to * ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats * lose precision, so the <= and => operators might accidentally let a float * through. */ try { $min = RandomCompat_intval($min); } catch (TypeError $ex) { throw new TypeError('random_int(): $min must be an integer'); } try { $max = RandomCompat_intval($max); } catch (TypeError $ex) { throw new TypeError('random_int(): $max must be an integer'); } /** * Now that we've verified our weak typing system has given us an integer, * let's validate the logic then we can move forward with generating random * integers along a given range. */ if ($min > $max) { throw new Error('Minimum value must be less than or equal to the maximum value'); } if ($max === $min) { return $min; } /** * Initialize variables to 0 * * We want to store: * $bytes => the number of random bytes we need * $mask => an integer bitmask (for use with the &) operator * so we can minimize the number of discards */ $attempts = $bits = $bytes = $mask = $valueShift = 0; /** * At this point, $range is a positive number greater than 0. It might * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to * a float and we will lose some precision. */ $range = $max - $min; /** * Test for integer overflow: */ if (!is_int($range)) { /** * Still safely calculate wider ranges. * Provided by @CodesInChaos, @oittaa * * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435 * * We use ~0 as a mask in this case because it generates all 1s * * @ref https://eval.in/400356 (32-bit) * @ref http://3v4l.org/XX9r5 (64-bit) */ $bytes = PHP_INT_SIZE; $mask = ~0; } else { /** * $bits is effectively ceil(log($range, 2)) without dealing with * type juggling */ while ($range > 0) { if ($bits % 8 === 0) { ++$bytes; } ++$bits; $range >>= 1; $mask = $mask << 1 | 1; } $valueShift = $min; } /** * Now that we have our parameters set up, let's begin generating * random integers until one falls between $min and $max */ do { /** * The rejection probability is at most 0.5, so this corresponds * to a failure probability of 2^-128 for a working RNG */ if ($attempts > 128) { throw new Exception('random_int: RNG is broken - too many rejections'); } /** * Let's grab the necessary number of random bytes */ $randomByteString = random_bytes($bytes); if ($randomByteString === false) { throw new Exception('Random number generator failure'); } /** * Let's turn $randomByteString into an integer * * This uses bitwise operators (<< and |) to build an integer * out of the values extracted from ord() * * Example: [9F] | [6D] | [32] | [0C] => * 159 + 27904 + 3276800 + 201326592 => * 204631455 */ $val = 0; for ($i = 0; $i < $bytes; ++$i) { $val |= ord($randomByteString[$i]) << $i * 8; } /** * Apply mask */ $val &= $mask; $val += $valueShift; ++$attempts; /** * If $val overflows to a floating point number, * ... or is larger than $max, * ... or smaller than $min, * then try again. */ } while (!is_int($val) || $val > $max || $val < $min); return (int) $val; }