| 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 | |