| 2020 | /** |
| 2021 | * Generate a cryptographically secure random string |
| 2022 | * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/ |
| 2023 | * |
| 2024 | * @since x.x.x |
| 2025 | * @internal |
| 2026 | * |
| 2027 | * @param int $bytes Number of random bytes. |
| 2028 | * @return string|WP_Error A string of random charcters of $bytes length, WP_Error if external random sources unavailable |
| 2029 | */ |
| 2030 | function wp_external_random_bytes( $bytes = 32 ) { |
| 2031 | |
| 2032 | // Uses system native Crypto API in most OS's |
| 2033 | // Uses native Windows Crypto API in PHP 5.3+ (MCRYPT_DEV_URANDOM) |
| 2034 | // Prior to 5.3.7 this may also fatal on Windows - https://bugs.php.net/bug.php?id=55169 |
| 2035 | $use_mcrypt = function_exists('mcrypt_create_iv'); |
| 2036 | $use_mcrypt = $use_mcrypt && ! ( 'WIN' == substr( PHP_OS, 0, 3 ) && version_compare( phpversion(), '5.3.7', '<') ); |
| 2037 | if ( $use_mcrypt ) { |
| 2038 | $buf = mcrypt_create_iv( $bytes, MCRYPT_DEV_URANDOM ); |
| 2039 | if ( $buf !== false ) { |
| 2040 | return $buf; |
| 2041 | } |
| 2042 | } |
| 2043 | |
| 2044 | // Use /dev/urandom if available |
| 2045 | // Will cause issues with open_basedir denying access |
| 2046 | if ( @is_readable( '/dev/urandom' ) ) { |
| 2047 | $fp = @fopen( '/dev/urandom', 'rb' ); |
| 2048 | |
| 2049 | // Use unbuffered reads if available in PHP 5.3.3+ to avoid read-ahead buffering eating entropy |
| 2050 | if ( $fp && function_exists( 'stream_set_read_buffer' ) ) { |
| 2051 | if ( false === stream_set_read_buffer( $fp, 0 ) ) { |
| 2052 | // We'll skip using /dev/urandom if we can't use an unbuffered file |
| 2053 | fclose( $fp ); |
| 2054 | unset( $fp ); |
| 2055 | } |
| 2056 | } |
| 2057 | |
| 2058 | if ( $fp ) { |
| 2059 | $buf = ''; |
| 2060 | |
| 2061 | mbstring_binary_safe_encoding(); |
| 2062 | |
| 2063 | // How many bytes do we need? |
| 2064 | $bytes_remaining = $bytes; |
| 2065 | do { |
| 2066 | $buf .= $read = fread( $fp, $bytes_remaining ); |
| 2067 | if ( $read === false ) { |
| 2068 | // We cannot safely read from /dev/urandom. |
| 2069 | $buf = false; |
| 2070 | break; |
| 2071 | } |
| 2072 | $bytes_remaining -= strlen( $read ); |
| 2073 | } while( $bytes_remaining > 0 ); |
| 2074 | |
| 2075 | reset_mbstring_encoding(); |
| 2076 | |
| 2077 | fclose( $fp ); |
| 2078 | |
| 2079 | if ( $buf ) { |
| 2080 | return $buf; |
| 2081 | } |
| 2082 | } |
| 2083 | } |
| 2084 | |
| 2085 | // Available since PHP 5.3.0 |
| 2086 | // Uses native Windows Cyrpto API's in PHP 5.4+ |
| 2087 | // can be exceedingly slow before PHP 5.3.4 on windows - https://bugs.php.net/bug.php?id=51636 |
| 2088 | $use_openssl = function_exists('openssl_random_pseudo_bytes'); |
| 2089 | $use_openssl = $use_openssl && ! ( 'WIN' == substr( PHP_OS, 0, 3 ) && version_compare( phpversion(), '5.3.4', '<') ); |
| 2090 | if ( $use_openssl ) { |
| 2091 | $buf = openssl_random_pseudo_bytes( $bytes ); |
| 2092 | if ( $buf !== false ) { |
| 2093 | return $buf; |
| 2094 | } |
| 2095 | } |
| 2096 | |
| 2097 | return new WP_Error( 'external_rand_source_unavailable', 'External Random Sources unavailable' ); |
| 2098 | } |
| 2099 | |
| 2100 | /** |
| 2101 | * Generate a random positive integer between 0 and PHP_INT_MAX. |
| 2102 | * |
| 2103 | * @since x.x.x |
| 2104 | * @internal |
| 2105 | * |
| 2106 | * @return int|WP_Error A random positive integer, WP_Error upon no external random sources being available |
| 2107 | */ |
| 2108 | function wp_external_random_positive_int() { |
| 2109 | $buf = wp_external_random_bytes( PHP_INT_SIZE ); |
| 2110 | if ( is_wp_error( $buf ) ) { |
| 2111 | return $buf; |
| 2112 | } |
| 2113 | |
| 2114 | $val = 0; |
| 2115 | $i = strlen( $buf ); |
| 2116 | |
| 2117 | // TODO: Test on a 32bit machine with PHP_INT_SIZE = 4 |
| 2118 | do { |
| 2119 | $i--; |
| 2120 | $val <<= 8; |
| 2121 | $val |= ord( $buf[ $i ] ); |
| 2122 | } while( $i != 0 ); |
| 2123 | |
| 2124 | // effectively absint() |
| 2125 | return $val & PHP_INT_MAX; |
| 2126 | } |
| 2127 | |
| 2128 | /** |
| 2129 | * Generates an unbiased, unpredictably random number. |
| 2130 | * |
| 2131 | * @since x.x.x |
| 2132 | * @internal |
| 2133 | * |
| 2134 | * @param int $min Lower limit for the generated number. |
| 2135 | * @param int $max Upper limit for the generated number. |
| 2136 | * @return int|WP_Error A random number between min and max inclusively, WP_Error on external rand-source failure |
| 2137 | */ |
| 2138 | function wp_external_rand( $min = 0, $max = PHP_INT_MAX ) { |
| 2139 | // Compat for `wp_rand()` |
| 2140 | if ( 0 === $max ) { |
| 2141 | $max = PHP_INT_MAX; |
| 2142 | } |
| 2143 | if ( $min == $max ) { |
| 2144 | return $min; |
| 2145 | } |
| 2146 | |
| 2147 | // Calculate from 0..($max-$min), resulting in $min+$rand..$max |
| 2148 | $max -= $min; |
| 2149 | |
| 2150 | // 7776 -> 13 |
| 2151 | $bits = ceil( log( $max + 1 ) / log( 2 ) ); |
| 2152 | |
| 2153 | // 2^13 - 1 == 8191 or 0x00001111 11111111 |
| 2154 | // Calculate a mask of how much of the PHP int we're interested in |
| 2155 | $mask = ceil( pow( 2, $bits ) ) - 1; |
| 2156 | |
| 2157 | do { |
| 2158 | $val = wp_external_random_positive_int(); |
| 2159 | if ( is_wp_error( $val ) ) { |
| 2160 | return $val; |
| 2161 | } |
| 2162 | |
| 2163 | // Apply the mask to our val |
| 2164 | $val &= $mask; |
| 2165 | } while ( $val > $max ); |
| 2166 | |
| 2167 | // $min..($min+$max) |
| 2168 | return (int) ( $min + $val ); |
| 2169 | } |
| 2170 | |
| 2171 | |