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