| | 2021 | /** |
| | 2022 | * Generate a cryptographically secure random string |
| | 2023 | * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/ |
| | 2024 | * |
| | 2025 | * @param int $bytes - how many bytes do we want? |
| | 2026 | */ |
| | 2027 | function wp_random_bytes($bytes = 32) { |
| | 2028 | $buf = ''; |
| | 2029 | /** |
| | 2030 | * This is preferable to userspace RNGs (e.g. openssl) |
| | 2031 | * because it reads directly from /dev/urandom on most OS's, |
| | 2032 | * and uses Windows's Crypto APIs transparently. |
| | 2033 | * |
| | 2034 | * Requires the mcrypt extension be installed/enabled. |
| | 2035 | */ |
| | 2036 | if (function_exists('mcrypt_create_iv')) { |
| | 2037 | $buf = mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM); |
| | 2038 | if($buf !== FALSE) { |
| | 2039 | return $buf; |
| | 2040 | } |
| | 2041 | } |
| | 2042 | /** |
| | 2043 | * If /dev/urandom is available, let's prefer it over every other method |
| | 2044 | * |
| | 2045 | * This should only return false on Windows hosts and in chroot jails |
| | 2046 | */ |
| | 2047 | if (is_readable('/dev/urandom')) { |
| | 2048 | $fp = fopen('/dev/urandom', 'rb'); |
| | 2049 | // Extra cautious, thanks to feedback from Taylor Hornby (https://defuse.ca) |
| | 2050 | $streamset = stream_set_read_buffer($fp, 0); |
| | 2051 | if ($fp !== false && $streamset === 0) { |
| | 2052 | $buf = ''; |
| | 2053 | // How many bytes do we need? |
| | 2054 | $remaining = $bytes; |
| | 2055 | do { |
| | 2056 | $read = fread($fp, $remaining); |
| | 2057 | if ($read === FALSE) { |
| | 2058 | // We cannot safely read from urandom. |
| | 2059 | $buf = FALSE; |
| | 2060 | break; |
| | 2061 | } |
| | 2062 | // Decrease the number of bytes returned from remaining |
| | 2063 | $remaining -= wp_binsafe_strlen($read); |
| | 2064 | |
| | 2065 | // We actually read bytes! Let's append them to our buffer |
| | 2066 | $buf .= $read; |
| | 2067 | } while ($remaining > 0); |
| | 2068 | // Let's make sure we have enough bytes |
| | 2069 | |
| | 2070 | fclose($fp); |
| | 2071 | if ($buf !== FALSE) { |
| | 2072 | return $buf; |
| | 2073 | } |
| | 2074 | } |
| | 2075 | } |
| | 2076 | /** |
| | 2077 | * This is available since PHP 5.3.0 on every PHP install. |
| | 2078 | * |
| | 2079 | * Better than nothing, but not really urandom |
| | 2080 | */ |
| | 2081 | if (function_exists('openssl_random_pseudo_bytes')) { |
| | 2082 | $buf = openssl_random_pseudo_bytes($bytes); |
| | 2083 | if ($buf !== false) { |
| | 2084 | return $buf; |
| | 2085 | } |
| | 2086 | } |
| | 2087 | |
| | 2088 | /** |
| | 2089 | * Windows with PHP < 5.3.0 will not have the function |
| | 2090 | * openssl_random_pseudo_bytes() available, so let's use |
| | 2091 | * CAPICOM to work around this deficiency. |
| | 2092 | */ |
| | 2093 | if (class_exists('\\COM', false)) { |
| | 2094 | try { |
| | 2095 | if ($buf === FALSE) { |
| | 2096 | $buf = ''; // Make it a string, not false |
| | 2097 | } |
| | 2098 | $util = new \COM('CAPICOM.Utilities.1'); |
| | 2099 | $execs = 0; |
| | 2100 | /** |
| | 2101 | * Let's not let it loop forever. If we run N times and fail to |
| | 2102 | * get N bytes of entropy, then CAPICOM has failed us. |
| | 2103 | */ |
| | 2104 | while ($execs < $bytes) { |
| | 2105 | $buf .= base64_decode($util->GetRandom($bytes, 0)); |
| | 2106 | if (wp_binsafe_strlen($buf) >= $bytes) { |
| | 2107 | return wp_binsafe_substr($buf, 0, $bytes); |
| | 2108 | } |
| | 2109 | ++$execs; |
| | 2110 | } |
| | 2111 | } catch (\Exception $e) { |
| | 2112 | unset($e); // Let's not let CAPICOM errors kill our app |
| | 2113 | } |
| | 2114 | } |
| | 2115 | if ($buf === FALSE) { |
| | 2116 | $buf = ''; // Make it a string, not false |
| | 2117 | } |
| | 2118 | /** |
| | 2119 | * Okay, at this point, we cannot guarantee a CSPRNG no matter what we |
| | 2120 | * do. Our only recourse is to return something insecure. |
| | 2121 | */ |
| | 2122 | for ($i = 0; $i < $bytes; ++$i) { |
| | 2123 | $buf .= chr(wp_rand(0, 255)); |
| | 2124 | } |
| | 2125 | return $buf; |
| | 2126 | } |
| | 2127 | endif; |
| | 2128 | |
| | 2129 | if ( !function_exists('wp_binsafe_strlen') ): |
| | 2130 | /** |
| | 2131 | * Binary-safe string length (won't get chewed by multibyte strings) |
| | 2132 | * |
| | 2133 | * @param string $str The string whose length we are retreiving |
| | 2134 | * @return int The string length (binary-safe) |
| | 2135 | */ |
| | 2136 | function wp_binsafe_strlen($str) { |
| | 2137 | if (function_exists('mb_strlen')) { |
| | 2138 | return mb_strlen($str, '8bit'); |
| | 2139 | } |
| | 2140 | return strlen($str); |
| | 2141 | } |
| | 2142 | endif; |
| | 2143 | |
| | 2144 | if ( !function_exists('wp_binsafe_substr') ): |
| | 2145 | /** |
| | 2146 | * Binary-safe substring (won't get chewed by multibyte strings) |
| | 2147 | * |
| | 2148 | * @param string $str The string whose length we are retreiving |
| | 2149 | * @param int $start The |
| | 2150 | * @return int The string length (binary-safe) |
| | 2151 | */ |
| | 2152 | function wp_binsafe_substr($str, $start, $length = null) { |
| | 2153 | if (function_exists('mb_substr')) { |
| | 2154 | return mb_substr($str, $start, $length, '8bit'); |
| | 2155 | } |
| | 2156 | return substr($str, $start, $length); |
| | 2157 | } |
| | 2158 | endif; |
| | 2159 | |
| | 2160 | if ( !function_exists('wp_random_positive_int') ): |
| | 2161 | /** |
| | 2162 | * Generate a random positive integer between 0 and PHP_INT_MAX |
| | 2163 | * |
| | 2164 | * @return int A random positive integer |
| | 2165 | */ |
| | 2166 | function wp_random_positive_int() { |
| | 2167 | $buf = wp_random_bytes(PHP_INT_SIZE); |
| | 2168 | if($buf === false) { |
| | 2169 | trigger_error('Random number failure', E_USER_ERROR); |
| | 2170 | } |
| | 2171 | |
| | 2172 | $val = 0; |
| | 2173 | $i = strlen($buf); |
| | 2174 | |
| | 2175 | do { |
| | 2176 | $i--; |
| | 2177 | $val <<= 8; |
| | 2178 | $val |= ord($buf[$i]); |
| | 2179 | } while ($i != 0); |
| | 2180 | |
| | 2181 | return $val & PHP_INT_MAX; |
| | 2182 | } |
| | 2183 | endif; |
| | 2184 | |
| | 2185 | if ( !function_exists('wp_secure_rand') ): |
| | 2186 | /** |
| | 2187 | * Generates an unbiased, unpredictably random number |
| | 2188 | * |
| | 2189 | * @since x.x.x |
| | 2190 | * |
| | 2191 | * @param int $min Lower limit for the generated number |
| | 2192 | * @param int $max Upper limit for the generated number |
| | 2193 | * @return int A random number between min and max |
| | 2194 | */ |
| | 2195 | function wp_secure_rand( $min = 0, $max = 0) { |
| | 2196 | $range = $max - $min; |
| | 2197 | if ($range < 2) { |
| | 2198 | return $min; |
| | 2199 | } |
| | 2200 | |
| | 2201 | // 7776 -> 13 |
| | 2202 | $bits = ceil(log($range)/log(2)); |
| | 2203 | |
| | 2204 | // 2^13 - 1 == 8191 or 0x00001111 11111111 |
| | 2205 | $mask = ceil(pow(2, $bits)) - 1; |
| | 2206 | |
| | 2207 | do { |
| | 2208 | // Grab a random integer |
| | 2209 | $val = wp_random_positive_int(); |
| | 2210 | if ($val === FALSE) { |
| | 2211 | // RNG failure |
| | 2212 | return FALSE; |
| | 2213 | } |
| | 2214 | // Apply mask |
| | 2215 | $val = $val & $mask; |
| | 2216 | // If $val is larger than the maximum acceptable number for |
| | 2217 | // $min and $max, we discard and try again. |
| | 2218 | } while ($val > $range); |
| | 2219 | return (int) ($min + $val); |
| | 2220 | } |
| | 2221 | |
| | 2222 | endif; |
| | 2223 | |
| | 2224 | |