| | 2020 | /** |
| | 2021 | * Generate a cryptographically secure random string |
| | 2022 | * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/ |
| | 2023 | * |
| | 2024 | * @param int $bytes Number of random bytes. |
| | 2025 | * @return string A string of random charcters of $bytes length. |
| | 2026 | */ |
| | 2027 | function wp_random_bytes( $bytes = 32 ) { |
| | 2028 | |
| | 2029 | // Uses system native Crypto API in most OS's |
| | 2030 | // Uses native Windows Crypto API in PHP 5.3+ (MCRYPT_DEV_URANDOM) |
| | 2031 | // Prior to 5.3.7 this may also fatal on Windows - https://bugs.php.net/bug.php?id=55169 |
| | 2032 | $use_mcrypt = function_exists('mcrypt_create_iv'); |
| | 2033 | $use_mcrypt = $use_mcrypt && ! ( 'WIN' == substr( PHP_OS, 0, 3 ) && version_compare( phpversion(), '5.3.7', '<') ); |
| | 2034 | if ( $use_mcrypt ) { |
| | 2035 | $buf = mcrypt_create_iv( $bytes, MCRYPT_DEV_URANDOM ); |
| | 2036 | if ( $buf !== false ) { |
| | 2037 | return $buf; |
| | 2038 | } |
| | 2039 | } |
| | 2040 | |
| | 2041 | // Use /dev/urandom if available |
| | 2042 | // Will cause issues with open_basedir denying access |
| | 2043 | if ( @is_readable( '/dev/urandom' ) ) { |
| | 2044 | $fp = @fopen( '/dev/urandom', 'rb' ); |
| | 2045 | |
| | 2046 | // Use unbuffered reads if available in PHP 5.3.3+ to avoid read-ahead buffering eating entropy |
| | 2047 | if ( $fp && function_exists( 'stream_set_read_buffer' ) ) { |
| | 2048 | if ( false === stream_set_read_buffer( $fp, 0 ) ) { |
| | 2049 | // We'll skip using /dev/urandom if we can't use an unbuffered file |
| | 2050 | fclose( $fp ); |
| | 2051 | unset( $fp ); |
| | 2052 | } |
| | 2053 | } |
| | 2054 | |
| | 2055 | if ( $fp ) { |
| | 2056 | $buf = ''; |
| | 2057 | |
| | 2058 | mbstring_binary_safe_encoding(); |
| | 2059 | |
| | 2060 | // How many bytes do we need? |
| | 2061 | $bytes_remaining = $bytes; |
| | 2062 | do { |
| | 2063 | $buf .= $read = fread( $fp, $bytes_remaining ); |
| | 2064 | if ( $read === false ) { |
| | 2065 | // We cannot safely read from /dev/urandom. |
| | 2066 | $buf = false; |
| | 2067 | break; |
| | 2068 | } |
| | 2069 | $bytes_remaining -= strlen( $read ); |
| | 2070 | } while( $bytes_remaining > 0 ); |
| | 2071 | |
| | 2072 | reset_mbstring_encoding(); |
| | 2073 | |
| | 2074 | fclose( $fp ); |
| | 2075 | |
| | 2076 | if ( $buf ) { |
| | 2077 | return $buf; |
| | 2078 | } |
| | 2079 | } |
| | 2080 | } |
| | 2081 | |
| | 2082 | // Available since PHP 5.3.0 |
| | 2083 | // Uses native Windows Cyrpto API's in PHP 5.4+ |
| | 2084 | // can be exceedingly slow before PHP 5.3.4 on windows - https://bugs.php.net/bug.php?id=51636 |
| | 2085 | $use_openssl = function_exists('openssl_random_pseudo_bytes'); |
| | 2086 | $use_openssl = $use_openssl && ! ( 'WIN' == substr( PHP_OS, 0, 3 ) && version_compare( phpversion(), '5.3.4', '<') ); |
| | 2087 | if ( $use_openssl ) { |
| | 2088 | $buf = openssl_random_pseudo_bytes( $bytes ); |
| | 2089 | if ( $buf !== false ) { |
| | 2090 | return $buf; |
| | 2091 | } |
| | 2092 | } |
| | 2093 | |
| | 2094 | // CAPICOM: Supported on 32bit Windows 2008 and older only (includes non-server Windows 7 & XP) |
| | 2095 | // Windows 2008 R2 and newer with .NET 1.0+ should use \DOTNET('mscorlib', 'System.Security.Cryptography.RNGCryptoServiceProvider') if available |
| | 2096 | // Lots of reports of the DOTNET route not working for many under windows throwing fatals / exceptions due to type mismatches |
| | 2097 | |
| | 2098 | // UNTESTED, and deprecated in windows. |
| | 2099 | if ( false && class_exists( 'COM', false ) ) { |
| | 2100 | try { |
| | 2101 | $execs = 0; |
| | 2102 | $buf = ''; |
| | 2103 | $util = new COM( 'CAPICOM.Utilities.1' ); |
| | 2104 | if ( ! is_callable( $util, 'GetRandom' ) ) { |
| | 2105 | throw new Exception( '$util->GetRandom not callable' ); |
| | 2106 | } |
| | 2107 | |
| | 2108 | /* |
| | 2109 | * Let's not let it loop forever. If we run N times and fail to |
| | 2110 | * get N bytes of entropy, then CAPICOM has failed us. |
| | 2111 | */ |
| | 2112 | mbstring_binary_safe_encoding(); |
| | 2113 | while ( $execs++ < $bytes) { |
| | 2114 | $buf .= base64_decode( $util->GetRandom( $bytes, 0 ) ); |
| | 2115 | if ( strlen( $buf ) >= $bytes ) { |
| | 2116 | reset_mbstring_encoding(); |
| | 2117 | return substr( $buf, 0, $bytes ); |
| | 2118 | } |
| | 2119 | } |
| | 2120 | reset_mbstring_encoding(); |
| | 2121 | } catch ( Exception $e ) { |
| | 2122 | reset_mbstring_encoding(); |
| | 2123 | } |
| | 2124 | } |
| | 2125 | if ( false && class_exists( 'DOTNET', false ) ) { |
| | 2126 | //DOTNET('mscorlib', 'System.Security.Cryptography.RNGCryptoServiceProvider') |
| | 2127 | } |
| | 2128 | |
| | 2129 | // At this point, we're forced to fallback to a hash-based random source |
| | 2130 | $buf = ''; |
| | 2131 | for ( $i = 0; $i < $bytes; ++$i ) { |
| | 2132 | $buf .= chr( wp_rand( 0, 255 ) ); |
| | 2133 | } |
| | 2134 | |
| | 2135 | return $buf; |
| | 2136 | } |
| | 2137 | |
| | 2138 | /** |
| | 2139 | * Generate a random positive integer between 0 and PHP_INT_MAX. |
| | 2140 | * |
| | 2141 | * @return int A random positive integer. |
| | 2142 | */ |
| | 2143 | function wp_random_positive_int() { |
| | 2144 | $buf = wp_random_bytes( PHP_INT_SIZE ); |
| | 2145 | |
| | 2146 | $val = 0; |
| | 2147 | $i = strlen( $buf ); |
| | 2148 | |
| | 2149 | // TODO: Test on a 32bit machine with PHP_INT_SIZE = 4 |
| | 2150 | do { |
| | 2151 | $i--; |
| | 2152 | $val <<= 8; |
| | 2153 | $val |= ord( $buf[ $i ] ); |
| | 2154 | } while( $i != 0 ); |
| | 2155 | |
| | 2156 | // effectively absint() |
| | 2157 | return $val & PHP_INT_MAX; |
| | 2158 | } |
| | 2159 | |
| | 2160 | /** |
| | 2161 | * Generates an unbiased, unpredictably random number. |
| | 2162 | * |
| | 2163 | * @since x.x.x |
| | 2164 | * |
| | 2165 | * @param int $min Lower limit for the generated number. |
| | 2166 | * @param int $max Upper limit for the generated number. |
| | 2167 | * @return int A random number between min and max inclusively. |
| | 2168 | */ |
| | 2169 | function wp_secure_rand( $min = 0, $max = PHP_INT_MAX ) { |
| | 2170 | if ( $min == $max ) { |
| | 2171 | return $min; |
| | 2172 | } |
| | 2173 | |
| | 2174 | // Calculate from 0..($max-$min), resulting in $min+$rand..$max |
| | 2175 | $max -= $min; |
| | 2176 | |
| | 2177 | // 7776 -> 13 |
| | 2178 | $bits = ceil( log( $max + 1 ) / log( 2 ) ); |
| | 2179 | |
| | 2180 | // 2^13 - 1 == 8191 or 0x00001111 11111111 |
| | 2181 | // Calculate a mask of how much of the PHP int we're interested in |
| | 2182 | $mask = ceil( pow( 2, $bits ) ) - 1; |
| | 2183 | |
| | 2184 | $val = 0; |
| | 2185 | do { |
| | 2186 | // Grab a random integer |
| | 2187 | $val = wp_random_positive_int(); |
| | 2188 | |
| | 2189 | // Apply the mask |
| | 2190 | $val &= $mask; |
| | 2191 | |
| | 2192 | // Try again if this exceeds our upper limit. |
| | 2193 | } while ( $val > $max ); |
| | 2194 | |
| | 2195 | // $min..($min+$max) |
| | 2196 | return (int) ( $min + $val ); |
| | 2197 | } |
| | 2198 | |
| | 2199 | |