- static $buffer = '';
- if ( is_null( $this->strong ) ) {
- // Set strength to false initially until we know what source data is coming from
- $this->strong = true;
- }
-
- if ( strlen( $buffer ) < $bytes ) {
- // If available make use of PHP 7's random_bytes
- // On Linux, getrandom syscall will be used if available.
- // On Windows CryptGenRandom will always be used
- // On other platforms, /dev/urandom will be used.
- // Avoids polyfills from before php 7.0
- // All error situations will throw Exceptions and or Errors
- if ( PHP_VERSION_ID >= 70000
- || ( defined( 'HHVM_VERSION_ID' ) && HHVM_VERSION_ID >= 31101 )
- ) {
- $rem = $bytes - strlen( $buffer );
- $buffer .= random_bytes( $rem );
- }
- if ( strlen( $buffer ) >= $bytes ) {
- $this->strong = true;
- }
- }
-
- if ( strlen( $buffer ) < $bytes && function_exists( 'mcrypt_create_iv' ) ) {
- // If available make use of mcrypt_create_iv URANDOM source to generate randomness
- // On unix-like systems this reads from /dev/urandom but does it without any buffering
- // and bypasses openbasedir restrictions, so it's preferable to reading directly
- // On Windows starting in PHP 5.3.0 Windows' native CryptGenRandom is used to generate
- // entropy so this is also preferable to just trying to read urandom because it may work
- // on Windows systems as well.
- $rem = $bytes - strlen( $buffer );
- $iv = mcrypt_create_iv( $rem, MCRYPT_DEV_URANDOM );
- if ( $iv === false ) {
- $this->logger->debug( "mcrypt_create_iv returned false." );
- } else {
- $buffer .= $iv;
- $this->logger->debug( "mcrypt_create_iv generated " . strlen( $iv ) .
- " bytes of randomness." );
- }
- }
-
- if ( strlen( $buffer ) < $bytes && function_exists( 'openssl_random_pseudo_bytes' ) ) {
- $rem = $bytes - strlen( $buffer );
- $openssl_strong = false;
- $openssl_bytes = openssl_random_pseudo_bytes( $rem, $openssl_strong );
- if ( $openssl_bytes === false ) {
- $this->logger->debug( "openssl_random_pseudo_bytes returned false." );
- } else {
- $buffer .= $openssl_bytes;
- $this->logger->debug( "openssl_random_pseudo_bytes generated " .
- strlen( $openssl_bytes ) . " bytes of " .
- ( $openssl_strong ? "strong" : "weak" ) . " randomness." );
- }
- if ( strlen( $buffer ) >= $bytes ) {
- // openssl tells us if the random source was strong, if some of our data was generated
- // using it use it's say on whether the randomness is strong
- $this->strong = !!$openssl_strong;
- }
- }
-
- // Only read from urandom if we can control the buffer size or were passed forceStrong
- if ( strlen( $buffer ) < $bytes &&
- ( function_exists( 'stream_set_read_buffer' ) || $forceStrong )
- ) {
- $rem = $bytes - strlen( $buffer );
- if ( !function_exists( 'stream_set_read_buffer' ) && $forceStrong ) {
- $this->logger->debug( "Was forced to read from /dev/urandom " .
- "without control over the buffer size." );
- }
- // /dev/urandom is generally considered the best possible commonly
- // available random source, and is available on most *nix systems.
- Wikimedia\suppressWarnings();
- $urandom = fopen( "/dev/urandom", "rb" );
- Wikimedia\restoreWarnings();
-
- // Attempt to read all our random data from urandom
- // php's fread always does buffered reads based on the stream's chunk_size
- // so in reality it will usually read more than the amount of data we're
- // asked for and not storing that risks depleting the system's random pool.
- // If stream_set_read_buffer is available set the chunk_size to the amount
- // of data we need. Otherwise read 8k, php's default chunk_size.
- if ( $urandom ) {
- // php's default chunk_size is 8k
- $chunk_size = 1024 * 8;
- if ( function_exists( 'stream_set_read_buffer' ) ) {
- // If possible set the chunk_size to the amount of data we need
- stream_set_read_buffer( $urandom, $rem );
- $chunk_size = $rem;
- }
- $random_bytes = fread( $urandom, max( $chunk_size, $rem ) );
- $buffer .= $random_bytes;
- fclose( $urandom );
- $this->logger->debug( "/dev/urandom generated " . strlen( $random_bytes ) .
- " bytes of randomness." );
-
- if ( strlen( $buffer ) >= $bytes ) {
- // urandom is always strong, set to true if all our data was generated using it
- $this->strong = true;
- }
- } else {
- $this->logger->debug( "/dev/urandom could not be opened." );
- }
- }
-
- // If we cannot use or generate enough data from a secure source
- // use this loop to generate a good set of pseudo random data.
- // This works by initializing a random state using a pile of unstable data
- // and continually shoving it through a hash along with a variable salt.
- // We hash the random state with more salt to avoid the state from leaking
- // out and being used to predict the /randomness/ that follows.
- if ( strlen( $buffer ) < $bytes ) {
- $this->logger->debug( __METHOD__ .
- ": Falling back to using a pseudo random state to generate randomness." );
- }
- while ( strlen( $buffer ) < $bytes ) {
- $buffer .= MWCryptHash::hmac( $this->randomState(), strval( mt_rand() ) );
- // This code is never really cryptographically strong, if we use it
- // at all, then set strong to false.
- $this->strong = false;
- }
-
- // Once the buffer has been filled up with enough random data to fulfill
- // the request shift off enough data to handle the request and leave the
- // unused portion left inside the buffer for the next request for random data
- $generated = substr( $buffer, 0, $bytes );
- $buffer = substr( $buffer, $bytes );
-
- $this->logger->debug( strlen( $buffer ) .
- " bytes of randomness leftover in the buffer." );
-
- return $generated;