public function testHmac()
 {
     if (MWCryptHash::hashAlgo() !== 'whirlpool') {
         $this->markTestSkipped('Hash algorithm isn\'t whirlpool');
     }
     $data = 'foobar';
     $key = 'secret';
     $hash = 'ddc94177b2020e55ce2049199fd9cc6327f416ff6dc621cc34cb43d9bec61d73372b4790c0e24957f565ecaf2d42821e6303619093e99cbe14a3b9250bda5f81';
     $this->assertEquals(pack('H*', $hash), MWCryptHash::hmac($data, $key), 'Raw hmac');
     $this->assertEquals($hash, MWCryptHash::hmac($data, $key, false), 'Hex hmac');
 }
Exemple #2
0
 /**
  * Decide on the best acceptable hash algorithm we have available for hash()
  * @return string A hash algorithm
  */
 public static function hashAlgo()
 {
     if (!is_null(self::$algo)) {
         return self::$algo;
     }
     $algos = hash_algos();
     $preference = ['whirlpool', 'sha256', 'sha1', 'md5'];
     foreach ($preference as $algorithm) {
         if (in_array($algorithm, $algos)) {
             self::$algo = $algorithm;
             return self::$algo;
         }
     }
     // We only reach here if no acceptable hash is found in the list, this should
     // be a technical impossibility since most of php's hash list is fixed and
     // some of the ones we list are available as their own native functions
     // But since we already require at least 5.2 and hash() was default in
     // 5.1.2 we don't bother falling back to methods like sha1 and md5.
     throw new DomainException("Could not find an acceptable hashing function in hash_algos()");
 }
Exemple #3
0
 /**
  * Hash data as a session ID
  *
  * Generally this will only be used when self::persistsSessionId() is false and
  * the provider has to base the session ID on the verified user's identity
  * or other static data.
  *
  * @param string $data
  * @param string|null $key Defaults to $this->config->get( 'SecretKey' )
  * @return string
  */
 protected final function hashToSessionId($data, $key = null)
 {
     if (!is_string($data)) {
         throw new \InvalidArgumentException('$data must be a string, ' . gettype($data) . ' was passed');
     }
     if ($key !== null && !is_string($key)) {
         throw new \InvalidArgumentException('$key must be a string or null, ' . gettype($key) . ' was passed');
     }
     $hash = \MWCryptHash::hmac("{$this}\n{$data}", $key ?: $this->config->get('SecretKey'), false);
     if (strlen($hash) < 32) {
         // Should never happen, even md5 is 128 bits
         // @codeCoverageIgnoreStart
         throw new \UnexpectedValueException('Hash fuction returned less than 128 bits');
         // @codeCoverageIgnoreEnd
     }
     if (strlen($hash) >= 40) {
         $hash = wfBaseConvert($hash, 16, 32, 32);
     }
     return substr($hash, -32);
 }
Exemple #4
0
 /**
  * @see self::generate()
  */
 public function realGenerate($bytes, $forceStrong = false)
 {
     wfDebug(__METHOD__ . ": Generating cryptographic random bytes for " . wfGetAllCallers(5) . "\n");
     $bytes = floor($bytes);
     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 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.
         if (function_exists('mcrypt_create_iv')) {
             $rem = $bytes - strlen($buffer);
             $iv = mcrypt_create_iv($rem, MCRYPT_DEV_URANDOM);
             if ($iv === false) {
                 wfDebug(__METHOD__ . ": mcrypt_create_iv returned false.\n");
             } else {
                 $buffer .= $iv;
                 wfDebug(__METHOD__ . ": mcrypt_create_iv generated " . strlen($iv) . " bytes of randomness.\n");
             }
         }
     }
     if (strlen($buffer) < $bytes) {
         // If available make use of openssl's random_pseudo_bytes method to
         // attempt to generate randomness. However don't do this on Windows
         // with PHP < 5.3.4 due to a bug:
         // http://stackoverflow.com/questions/1940168/openssl-random-pseudo-bytes-is-slow-php
         // http://git.php.net/?p=php-src.git;a=commitdiff;h=cd62a70863c261b07f6dadedad9464f7e213cad5
         if (function_exists('openssl_random_pseudo_bytes') && (!wfIsWindows() || version_compare(PHP_VERSION, '5.3.4', '>='))) {
             $rem = $bytes - strlen($buffer);
             $openssl_bytes = openssl_random_pseudo_bytes($rem, $openssl_strong);
             if ($openssl_bytes === false) {
                 wfDebug(__METHOD__ . ": openssl_random_pseudo_bytes returned false.\n");
             } else {
                 $buffer .= $openssl_bytes;
                 wfDebug(__METHOD__ . ": openssl_random_pseudo_bytes generated " . strlen($openssl_bytes) . " bytes of " . ($openssl_strong ? "strong" : "weak") . " randomness.\n");
             }
             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) {
             wfDebug(__METHOD__ . ": Was forced to read from /dev/urandom " . "without control over the buffer size.\n");
         }
         // /dev/urandom is generally considered the best possible commonly
         // available random source, and is available on most *nix systems.
         MediaWiki\suppressWarnings();
         $urandom = fopen("/dev/urandom", "rb");
         MediaWiki\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);
             wfDebug(__METHOD__ . ": /dev/urandom generated " . strlen($random_bytes) . " bytes of randomness.\n");
             if (strlen($buffer) >= $bytes) {
                 // urandom is always strong, set to true if all our data was generated using it
                 $this->strong = true;
             }
         } else {
             wfDebug(__METHOD__ . ": /dev/urandom could not be opened.\n");
         }
     }
     // 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) {
         wfDebug(__METHOD__ . ": Falling back to using a pseudo random state to generate randomness.\n");
     }
     while (strlen($buffer) < $bytes) {
         $buffer .= MWCryptHash::hmac($this->randomState(), 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);
     wfDebug(__METHOD__ . ": " . strlen($buffer) . " bytes of randomness leftover in the buffer.\n");
     return $generated;
 }
Exemple #5
0
 /**
  * Get the user's current token.
  * @param bool $forceCreation Force the generation of a new token if the
  *   user doesn't have one (default=true for backwards compatibility).
  * @return string|null Token
  */
 public function getToken($forceCreation = true)
 {
     global $wgAuthenticationTokenVersion;
     $this->load();
     if (!$this->mToken && $forceCreation) {
         $this->setToken();
     }
     if (!$this->mToken) {
         // The user doesn't have a token, return null to indicate that.
         return null;
     } elseif ($this->mToken === self::INVALID_TOKEN) {
         // We return a random value here so existing token checks are very
         // likely to fail.
         return MWCryptRand::generateHex(self::TOKEN_LENGTH);
     } elseif ($wgAuthenticationTokenVersion === null) {
         // $wgAuthenticationTokenVersion not in use, so return the raw secret
         return $this->mToken;
     } else {
         // $wgAuthenticationTokenVersion in use, so hmac it.
         $ret = MWCryptHash::hmac($wgAuthenticationTokenVersion, $this->mToken, false);
         // The raw hash can be overly long. Shorten it up.
         $len = max(32, self::TOKEN_LENGTH);
         if (strlen($ret) < $len) {
             // Should never happen, even md5 is 128 bits
             throw new \UnexpectedValueException('Hmac returned less than 128 bits');
         }
         return substr($ret, -$len);
     }
 }