/** * 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 testStrlen() { if (!function_exists('RandomCompat_strlen')) { return $this->markTestSkipped('We don\' need to test this in PHP 7.'); } $this->assertEquals(RandomCompat_strlen("𝒳"), 4); }
/** * 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) { if (!is_int($bytes)) { throw new TypeError('Length 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('PHP failed to generate 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'); }
/** * 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) { if (!is_int($bytes)) { throw new TypeError('Length 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) { if (RandomCompat_strlen($buf) === $bytes) { /** * Return our random entropy buffer here: */ return $buf; } } /** * If we reach here, PHP has failed us. */ throw new Exception('PHP failed to generate random data.'); }
/** * 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'); }
/** * 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 * @return string */ function random_bytes($bytes) { $secure = true; $buf = openssl_random_pseudo_bytes($bytes, $secure); if ($buf !== false && $secure) { if (RandomCompat_strlen($buf) === $bytes) { return $buf; } } }
/** * 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'); }
/** * substr() implementation that isn't brittle to mbstring.func_overload * * This version uses mb_substr() in '8bit' mode to treat strings as raw * binary rather than UTF-8, ISO-8859-1, etc * * @param string $binary_string * @param int $start * @param int $length (optional) * * @throws TypeError * * @return string */ function RandomCompat_substr($binary_string, $start, $length = null) { if (!is_string($binary_string)) { throw new TypeError('RandomCompat_substr(): First argument should be a string'); } if (!is_int($start)) { throw new TypeError('RandomCompat_substr(): Second argument should be an integer'); } if ($length === null) { /** * mb_substr($str, 0, NULL, '8bit') returns an empty string on * PHP 5.3, so we have to find the length ourselves. */ $length = RandomCompat_strlen($length) - $start; } elseif (!is_int($length)) { throw new TypeError('RandomCompat_substr(): Third argument should be an integer, or omitted'); } // Consistency with PHP's behavior if ($start === RandomCompat_strlen($binary_string) && $length === 0) { return ''; } if ($start > RandomCompat_strlen($binary_string)) { return false; } return mb_substr($binary_string, $start, $length, '8bit'); }
/** * substr() implementation that isn't brittle to mbstring.func_overload * * This version uses mb_substr() in '8bit' mode to treat strings as raw * binary rather than UTF-8, ISO-8859-1, etc * * @param string $binary_string * @param int $start * @param int $length (optional) * * @throws InvalidArgumentException * * @return string */ function RandomCompat_substr($binary_string, $start, $length = null) { if (!is_string($binary_string)) { throw new InvalidArgumentException('RandomCompat_substr(): First argument should be a string'); } if (!is_int($start)) { throw new InvalidArgumentException('RandomCompat_substr(): Second argument should be an integer'); } if ($length === null) { /** * mb_substr($str, 0, NULL, '8bit') returns an empty string on * PHP 5.3, so we have to find the length ourselves. */ $length = RandomCompat_strlen($length) - $start; } elseif (!is_int($length)) { throw new InvalidArgumentException('RandomCompat_substr(): Third argument should be an integer, or omitted'); } return mb_substr($binary_string, $start, $length, '8bit'); }