├── LICENSE ├── composer.json ├── dist ├── random_compat.phar.pubkey └── random_compat.phar.pubkey.asc └── lib ├── byte_safe_strings.php ├── cast_to_int.php ├── error_polyfill.php ├── random.php ├── random_bytes_com_dotnet.php ├── random_bytes_dev_urandom.php ├── random_bytes_libsodium.php ├── random_bytes_libsodium_legacy.php ├── random_bytes_mcrypt.php └── random_int.php /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Paragon Initiative Enterprises 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paragonie/random_compat", 3 | "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", 4 | "keywords": [ 5 | "csprng", 6 | "random", 7 | "polyfill", 8 | "pseudorandom" 9 | ], 10 | "license": "MIT", 11 | "type": "library", 12 | "authors": [ 13 | { 14 | "name": "Paragon Initiative Enterprises", 15 | "email": "security@paragonie.com", 16 | "homepage": "https://paragonie.com" 17 | } 18 | ], 19 | "support": { 20 | "issues": "https://github.com/paragonie/random_compat/issues", 21 | "email": "info@paragonie.com", 22 | "source": "https://github.com/paragonie/random_compat" 23 | }, 24 | "require": { 25 | "php": ">=5.2.0" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": "*" 29 | }, 30 | "suggest": { 31 | "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." 32 | }, 33 | "autoload": { 34 | "files": [ 35 | "lib/random.php" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /dist/random_compat.phar.pubkey: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm 3 | pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p 4 | +h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc 5 | -----END PUBLIC KEY----- 6 | -------------------------------------------------------------------------------- /dist/random_compat.phar.pubkey.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | Version: GnuPG v2.0.22 (MingW32) 3 | 4 | iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip 5 | QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg 6 | 1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW 7 | NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA 8 | NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV 9 | JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74= 10 | =B6+8 11 | -----END PGP SIGNATURE----- 12 | -------------------------------------------------------------------------------- /lib/byte_safe_strings.php: -------------------------------------------------------------------------------- 1 | RandomCompat_strlen($binary_string)) { 135 | return ''; 136 | } 137 | 138 | return (string) mb_substr( 139 | (string) $binary_string, 140 | (int) $start, 141 | (int) $length, 142 | '8bit' 143 | ); 144 | } 145 | 146 | } else { 147 | 148 | /** 149 | * substr() implementation that isn't brittle to mbstring.func_overload 150 | * 151 | * This version just uses the default substr() 152 | * 153 | * @param string $binary_string 154 | * @param int $start 155 | * @param int|null $length (optional) 156 | * 157 | * @throws TypeError 158 | * 159 | * @return string 160 | */ 161 | function RandomCompat_substr($binary_string, $start, $length = null) 162 | { 163 | if (!is_string($binary_string)) { 164 | throw new TypeError( 165 | 'RandomCompat_substr(): First argument should be a string' 166 | ); 167 | } 168 | 169 | if (!is_int($start)) { 170 | throw new TypeError( 171 | 'RandomCompat_substr(): Second argument should be an integer' 172 | ); 173 | } 174 | 175 | if ($length !== null) { 176 | if (!is_int($length)) { 177 | throw new TypeError( 178 | 'RandomCompat_substr(): Third argument should be an integer, or omitted' 179 | ); 180 | } 181 | 182 | return (string) substr( 183 | (string )$binary_string, 184 | (int) $start, 185 | (int) $length 186 | ); 187 | } 188 | 189 | return (string) substr( 190 | (string) $binary_string, 191 | (int) $start 192 | ); 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /lib/cast_to_int.php: -------------------------------------------------------------------------------- 1 | operators might accidentally let a float 38 | * through. 39 | * 40 | * @param int|float $number The number we want to convert to an int 41 | * @param bool $fail_open Set to true to not throw an exception 42 | * 43 | * @return float|int 44 | * @psalm-suppress InvalidReturnType 45 | * 46 | * @throws TypeError 47 | */ 48 | function RandomCompat_intval($number, $fail_open = false) 49 | { 50 | if (is_int($number) || is_float($number)) { 51 | $number += 0; 52 | } elseif (is_numeric($number)) { 53 | /** @psalm-suppress InvalidOperand */ 54 | $number += 0; 55 | } 56 | /** @var int|float $number */ 57 | 58 | if ( 59 | is_float($number) 60 | && 61 | $number > ~PHP_INT_MAX 62 | && 63 | $number < PHP_INT_MAX 64 | ) { 65 | $number = (int) $number; 66 | } 67 | 68 | if (is_int($number)) { 69 | return (int) $number; 70 | } elseif (!$fail_open) { 71 | throw new TypeError( 72 | 'Expected an integer.' 73 | ); 74 | } 75 | return $number; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/error_polyfill.php: -------------------------------------------------------------------------------- 1 | = 70000) { 48 | return; 49 | } 50 | 51 | if (!defined('RANDOM_COMPAT_READ_BUFFER')) { 52 | define('RANDOM_COMPAT_READ_BUFFER', 8); 53 | } 54 | 55 | $RandomCompatDIR = dirname(__FILE__); 56 | 57 | require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'byte_safe_strings.php'; 58 | require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'cast_to_int.php'; 59 | require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'error_polyfill.php'; 60 | 61 | if (!is_callable('random_bytes')) { 62 | /** 63 | * PHP 5.2.0 - 5.6.x way to implement random_bytes() 64 | * 65 | * We use conditional statements here to define the function in accordance 66 | * to the operating environment. It's a micro-optimization. 67 | * 68 | * In order of preference: 69 | * 1. Use libsodium if available. 70 | * 2. fread() /dev/urandom if available (never on Windows) 71 | * 3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM) 72 | * 4. COM('CAPICOM.Utilities.1')->GetRandom() 73 | * 74 | * See RATIONALE.md for our reasoning behind this particular order 75 | */ 76 | if (extension_loaded('libsodium')) { 77 | // See random_bytes_libsodium.php 78 | if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) { 79 | require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_libsodium.php'; 80 | } elseif (method_exists('Sodium', 'randombytes_buf')) { 81 | require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_libsodium_legacy.php'; 82 | } 83 | } 84 | 85 | /** 86 | * Reading directly from /dev/urandom: 87 | */ 88 | if (DIRECTORY_SEPARATOR === '/') { 89 | // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast 90 | // way to exclude Windows. 91 | $RandomCompatUrandom = true; 92 | $RandomCompat_basedir = ini_get('open_basedir'); 93 | 94 | if (!empty($RandomCompat_basedir)) { 95 | $RandomCompat_open_basedir = explode( 96 | PATH_SEPARATOR, 97 | strtolower($RandomCompat_basedir) 98 | ); 99 | $RandomCompatUrandom = (array() !== array_intersect( 100 | array('/dev', '/dev/', '/dev/urandom'), 101 | $RandomCompat_open_basedir 102 | )); 103 | $RandomCompat_open_basedir = null; 104 | } 105 | 106 | if ( 107 | !is_callable('random_bytes') 108 | && 109 | $RandomCompatUrandom 110 | && 111 | @is_readable('/dev/urandom') 112 | ) { 113 | // Error suppression on is_readable() in case of an open_basedir 114 | // or safe_mode failure. All we care about is whether or not we 115 | // can read it at this point. If the PHP environment is going to 116 | // panic over trying to see if the file can be read in the first 117 | // place, that is not helpful to us here. 118 | 119 | // See random_bytes_dev_urandom.php 120 | require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_dev_urandom.php'; 121 | } 122 | // Unset variables after use 123 | $RandomCompat_basedir = null; 124 | } else { 125 | $RandomCompatUrandom = false; 126 | } 127 | 128 | /** 129 | * mcrypt_create_iv() 130 | * 131 | * We only want to use mcypt_create_iv() if: 132 | * 133 | * - random_bytes() hasn't already been defined 134 | * - the mcrypt extensions is loaded 135 | * - One of these two conditions is true: 136 | * - We're on Windows (DIRECTORY_SEPARATOR !== '/') 137 | * - We're not on Windows and /dev/urandom is readabale 138 | * (i.e. we're not in a chroot jail) 139 | * - Special case: 140 | * - If we're not on Windows, but the PHP version is between 141 | * 5.6.10 and 5.6.12, we don't want to use mcrypt. It will 142 | * hang indefinitely. This is bad. 143 | * - If we're on Windows, we want to use PHP >= 5.3.7 or else 144 | * we get insufficient entropy errors. 145 | */ 146 | if ( 147 | !is_callable('random_bytes') 148 | && 149 | // Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be. 150 | (DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307) 151 | && 152 | // Prevent this code from hanging indefinitely on non-Windows; 153 | // see https://bugs.php.net/bug.php?id=69833 154 | ( 155 | DIRECTORY_SEPARATOR !== '/' || 156 | (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613) 157 | ) 158 | && 159 | extension_loaded('mcrypt') 160 | ) { 161 | // See random_bytes_mcrypt.php 162 | require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_mcrypt.php'; 163 | } 164 | $RandomCompatUrandom = null; 165 | 166 | /** 167 | * This is a Windows-specific fallback, for when the mcrypt extension 168 | * isn't loaded. 169 | */ 170 | if ( 171 | !is_callable('random_bytes') 172 | && 173 | extension_loaded('com_dotnet') 174 | && 175 | class_exists('COM') 176 | ) { 177 | $RandomCompat_disabled_classes = preg_split( 178 | '#\s*,\s*#', 179 | strtolower(ini_get('disable_classes')) 180 | ); 181 | 182 | if (!in_array('com', $RandomCompat_disabled_classes)) { 183 | try { 184 | $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1'); 185 | /** @psalm-suppress TypeDoesNotContainType */ 186 | if (is_callable(array($RandomCompatCOMtest, 'GetRandom'))) { 187 | // See random_bytes_com_dotnet.php 188 | require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_com_dotnet.php'; 189 | } 190 | } catch (com_exception $e) { 191 | // Don't try to use it. 192 | } 193 | } 194 | $RandomCompat_disabled_classes = null; 195 | $RandomCompatCOMtest = null; 196 | } 197 | 198 | /** 199 | * throw new Exception 200 | */ 201 | if (!is_callable('random_bytes')) { 202 | /** 203 | * We don't have any more options, so let's throw an exception right now 204 | * and hope the developer won't let it fail silently. 205 | * 206 | * @param mixed $length 207 | * @psalm-suppress InvalidReturnType 208 | * @throws Exception 209 | * @return string 210 | */ 211 | function random_bytes($length) 212 | { 213 | unset($length); // Suppress "variable not used" warnings. 214 | throw new Exception( 215 | 'There is no suitable CSPRNG installed on your system' 216 | ); 217 | return ''; 218 | } 219 | } 220 | } 221 | 222 | if (!is_callable('random_int')) { 223 | require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_int.php'; 224 | } 225 | 226 | $RandomCompatDIR = null; 227 | -------------------------------------------------------------------------------- /lib/random_bytes_com_dotnet.php: -------------------------------------------------------------------------------- 1 | GetRandom($bytes, 0)); 75 | if (RandomCompat_strlen($buf) >= $bytes) { 76 | /** 77 | * Return our random entropy buffer here: 78 | */ 79 | return (string) RandomCompat_substr($buf, 0, $bytes); 80 | } 81 | ++$execCount; 82 | } while ($execCount < $bytes); 83 | 84 | /** 85 | * If we reach here, PHP has failed us. 86 | */ 87 | throw new Exception( 88 | 'Could not gather sufficient random data' 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/random_bytes_dev_urandom.php: -------------------------------------------------------------------------------- 1 | $st */ 82 | $st = fstat($fp); 83 | if (($st['mode'] & 0170000) !== 020000) { 84 | fclose($fp); 85 | $fp = false; 86 | } 87 | } 88 | } 89 | 90 | if (is_resource($fp)) { 91 | /** 92 | * stream_set_read_buffer() does not exist in HHVM 93 | * 94 | * If we don't set the stream's read buffer to 0, PHP will 95 | * internally buffer 8192 bytes, which can waste entropy 96 | * 97 | * stream_set_read_buffer returns 0 on success 98 | */ 99 | if (is_callable('stream_set_read_buffer')) { 100 | stream_set_read_buffer($fp, RANDOM_COMPAT_READ_BUFFER); 101 | } 102 | if (is_callable('stream_set_chunk_size')) { 103 | stream_set_chunk_size($fp, RANDOM_COMPAT_READ_BUFFER); 104 | } 105 | } 106 | } 107 | 108 | try { 109 | /** @var int $bytes */ 110 | $bytes = RandomCompat_intval($bytes); 111 | } catch (TypeError $ex) { 112 | throw new TypeError( 113 | 'random_bytes(): $bytes must be an integer' 114 | ); 115 | } 116 | 117 | if ($bytes < 1) { 118 | throw new Error( 119 | 'Length must be greater than 0' 120 | ); 121 | } 122 | 123 | /** 124 | * This if() block only runs if we managed to open a file handle 125 | * 126 | * It does not belong in an else {} block, because the above 127 | * if (empty($fp)) line is logic that should only be run once per 128 | * page load. 129 | */ 130 | if (is_resource($fp)) { 131 | /** 132 | * @var int 133 | */ 134 | $remaining = $bytes; 135 | 136 | /** 137 | * @var string|bool 138 | */ 139 | $buf = ''; 140 | 141 | /** 142 | * We use fread() in a loop to protect against partial reads 143 | */ 144 | do { 145 | /** 146 | * @var string|bool 147 | */ 148 | $read = fread($fp, $remaining); 149 | if (!is_string($read)) { 150 | /** 151 | * We cannot safely read from the file. Exit the 152 | * do-while loop and trigger the exception condition 153 | * 154 | * @var string|bool 155 | */ 156 | $buf = false; 157 | break; 158 | } 159 | /** 160 | * Decrease the number of bytes returned from remaining 161 | */ 162 | $remaining -= RandomCompat_strlen($read); 163 | /** 164 | * @var string $buf 165 | */ 166 | $buf .= $read; 167 | } while ($remaining > 0); 168 | 169 | /** 170 | * Is our result valid? 171 | * @var string|bool $buf 172 | */ 173 | if (is_string($buf)) { 174 | if (RandomCompat_strlen($buf) === $bytes) { 175 | /** 176 | * Return our random entropy buffer here: 177 | */ 178 | return $buf; 179 | } 180 | } 181 | } 182 | 183 | /** 184 | * If we reach here, PHP has failed us. 185 | */ 186 | throw new Exception( 187 | 'Error reading from source device' 188 | ); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /lib/random_bytes_libsodium.php: -------------------------------------------------------------------------------- 1 | 2147483647) { 66 | $buf = ''; 67 | for ($i = 0; $i < $bytes; $i += 1073741824) { 68 | $n = ($bytes - $i) > 1073741824 69 | ? 1073741824 70 | : $bytes - $i; 71 | $buf .= \Sodium\randombytes_buf($n); 72 | } 73 | } else { 74 | /** @var string|bool $buf */ 75 | $buf = \Sodium\randombytes_buf($bytes); 76 | } 77 | 78 | if (is_string($buf)) { 79 | if (RandomCompat_strlen($buf) === $bytes) { 80 | return $buf; 81 | } 82 | } 83 | 84 | /** 85 | * If we reach here, PHP has failed us. 86 | */ 87 | throw new Exception( 88 | 'Could not gather sufficient random data' 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/random_bytes_libsodium_legacy.php: -------------------------------------------------------------------------------- 1 | 2147483647) { 70 | for ($i = 0; $i < $bytes; $i += 1073741824) { 71 | $n = ($bytes - $i) > 1073741824 72 | ? 1073741824 73 | : $bytes - $i; 74 | $buf .= Sodium::randombytes_buf((int) $n); 75 | } 76 | } else { 77 | $buf .= Sodium::randombytes_buf((int) $bytes); 78 | } 79 | 80 | if (is_string($buf)) { 81 | if (RandomCompat_strlen($buf) === $bytes) { 82 | return $buf; 83 | } 84 | } 85 | 86 | /** 87 | * If we reach here, PHP has failed us. 88 | */ 89 | throw new Exception( 90 | 'Could not gather sufficient random data' 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/random_bytes_mcrypt.php: -------------------------------------------------------------------------------- 1 | operators might accidentally let a float 50 | * through. 51 | */ 52 | 53 | try { 54 | /** @var int $min */ 55 | $min = RandomCompat_intval($min); 56 | } catch (TypeError $ex) { 57 | throw new TypeError( 58 | 'random_int(): $min must be an integer' 59 | ); 60 | } 61 | 62 | try { 63 | /** @var int $max */ 64 | $max = RandomCompat_intval($max); 65 | } catch (TypeError $ex) { 66 | throw new TypeError( 67 | 'random_int(): $max must be an integer' 68 | ); 69 | } 70 | 71 | /** 72 | * Now that we've verified our weak typing system has given us an integer, 73 | * let's validate the logic then we can move forward with generating random 74 | * integers along a given range. 75 | */ 76 | if ($min > $max) { 77 | throw new Error( 78 | 'Minimum value must be less than or equal to the maximum value' 79 | ); 80 | } 81 | 82 | if ($max === $min) { 83 | return (int) $min; 84 | } 85 | 86 | /** 87 | * Initialize variables to 0 88 | * 89 | * We want to store: 90 | * $bytes => the number of random bytes we need 91 | * $mask => an integer bitmask (for use with the &) operator 92 | * so we can minimize the number of discards 93 | */ 94 | $attempts = $bits = $bytes = $mask = $valueShift = 0; 95 | /** @var int $attempts */ 96 | /** @var int $bits */ 97 | /** @var int $bytes */ 98 | /** @var int $mask */ 99 | /** @var int $valueShift */ 100 | 101 | /** 102 | * At this point, $range is a positive number greater than 0. It might 103 | * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to 104 | * a float and we will lose some precision. 105 | * 106 | * @var int|float $range 107 | */ 108 | $range = $max - $min; 109 | 110 | /** 111 | * Test for integer overflow: 112 | */ 113 | if (!is_int($range)) { 114 | 115 | /** 116 | * Still safely calculate wider ranges. 117 | * Provided by @CodesInChaos, @oittaa 118 | * 119 | * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435 120 | * 121 | * We use ~0 as a mask in this case because it generates all 1s 122 | * 123 | * @ref https://eval.in/400356 (32-bit) 124 | * @ref http://3v4l.org/XX9r5 (64-bit) 125 | */ 126 | $bytes = PHP_INT_SIZE; 127 | /** @var int $mask */ 128 | $mask = ~0; 129 | 130 | } else { 131 | 132 | /** 133 | * $bits is effectively ceil(log($range, 2)) without dealing with 134 | * type juggling 135 | */ 136 | while ($range > 0) { 137 | if ($bits % 8 === 0) { 138 | ++$bytes; 139 | } 140 | ++$bits; 141 | $range >>= 1; 142 | /** @var int $mask */ 143 | $mask = $mask << 1 | 1; 144 | } 145 | $valueShift = $min; 146 | } 147 | 148 | /** @var int $val */ 149 | $val = 0; 150 | /** 151 | * Now that we have our parameters set up, let's begin generating 152 | * random integers until one falls between $min and $max 153 | */ 154 | /** @psalm-suppress RedundantCondition */ 155 | do { 156 | /** 157 | * The rejection probability is at most 0.5, so this corresponds 158 | * to a failure probability of 2^-128 for a working RNG 159 | */ 160 | if ($attempts > 128) { 161 | throw new Exception( 162 | 'random_int: RNG is broken - too many rejections' 163 | ); 164 | } 165 | 166 | /** 167 | * Let's grab the necessary number of random bytes 168 | */ 169 | $randomByteString = random_bytes($bytes); 170 | 171 | /** 172 | * Let's turn $randomByteString into an integer 173 | * 174 | * This uses bitwise operators (<< and |) to build an integer 175 | * out of the values extracted from ord() 176 | * 177 | * Example: [9F] | [6D] | [32] | [0C] => 178 | * 159 + 27904 + 3276800 + 201326592 => 179 | * 204631455 180 | */ 181 | $val &= 0; 182 | for ($i = 0; $i < $bytes; ++$i) { 183 | $val |= ord($randomByteString[$i]) << ($i * 8); 184 | } 185 | /** @var int $val */ 186 | 187 | /** 188 | * Apply mask 189 | */ 190 | $val &= $mask; 191 | $val += $valueShift; 192 | 193 | ++$attempts; 194 | /** 195 | * If $val overflows to a floating point number, 196 | * ... or is larger than $max, 197 | * ... or smaller than $min, 198 | * then try again. 199 | */ 200 | } while (!is_int($val) || $val > $max || $val < $min); 201 | 202 | return (int) $val; 203 | } 204 | } 205 | --------------------------------------------------------------------------------