public function crypt($password)
 {
     if (count($this->args) == 0) {
         $this->args[] = base64_encode(MWCryptRand::generate(16, true));
     }
     if (function_exists('hash_pbkdf2')) {
         $hash = hash_pbkdf2($this->params['algo'], $password, base64_decode($this->args[0]), (int) $this->params['rounds'], (int) $this->params['length'], true);
         if (!is_string($hash)) {
             throw new PasswordError('Error when hashing password.');
         }
     } else {
         $hashLenHash = hash($this->params['algo'], '', true);
         if (!is_string($hashLenHash)) {
             throw new PasswordError('Error when hashing password.');
         }
         $hashLen = strlen($hashLenHash);
         $blockCount = ceil($this->params['length'] / $hashLen);
         $hash = '';
         $salt = base64_decode($this->args[0]);
         for ($i = 1; $i <= $blockCount; ++$i) {
             $roundTotal = $lastRound = hash_hmac($this->params['algo'], $salt . pack('N', $i), $password, true);
             for ($j = 1; $j < $this->params['rounds']; ++$j) {
                 $lastRound = hash_hmac($this->params['algo'], $lastRound, $password, true);
                 $roundTotal ^= $lastRound;
             }
             $hash .= $roundTotal;
         }
         $hash = substr($hash, 0, $this->params['length']);
     }
     $this->hash = base64_encode($hash);
 }
 /**
  * Updates the underlying hash by encrypting it with the newest secret.
  *
  * @throws MWException If the configuration is not valid
  * @return bool True if the password was updated
  */
 public function update()
 {
     if (count($this->args) != 2 || $this->params == $this->getDefaultParams()) {
         // Hash does not need updating
         return false;
     }
     // Decrypt the underlying hash
     $underlyingHash = openssl_decrypt(base64_decode($this->args[1]), $this->params['cipher'], $this->config['secrets'][$this->params['secret']], 0, base64_decode($this->args[0]));
     // Reset the params
     $this->params = $this->getDefaultParams();
     // Check the key size with the new params
     $iv = MWCryptRand::generate(openssl_cipher_iv_length($this->params['cipher']), true);
     $this->hash = base64_encode(openssl_encrypt($underlyingHash, $this->params['cipher'], $this->config['secrets'][$this->params['secret']], 0, $iv));
     $this->args = array(base64_encode($iv));
     return true;
 }
Example #3
0
 /**
  * @param string $password Password to encrypt
  *
  * @throws PasswordError If bcrypt has an unknown error
  * @throws MWException If bcrypt is not supported by PHP
  */
 public function crypt($password)
 {
     if (!defined('CRYPT_BLOWFISH')) {
         throw new MWException('Bcrypt is not supported.');
     }
     // Either use existing hash or make a new salt
     // Bcrypt expects 22 characters of base64-encoded salt
     // Note: bcrypt does not use MIME base64. It uses its own base64 without any '=' padding.
     //       It expects a 128 bit salt, so it will ignore anything after the first 128 bits
     if (!isset($this->args[0])) {
         $this->args[] = substr(strtr(base64_encode(MWCryptRand::generate(16, true)), '+', '.'), 0, 22);
     }
     $hash = crypt($password, sprintf('$2y$%02d$%s', (int) $this->params['rounds'], $this->args[0]));
     if (!is_string($hash) || strlen($hash) <= 13) {
         throw new PasswordError('Error when hashing password.');
     }
     // Strip the $2y$
     $parts = explode($this->getDelimiter(), substr($hash, 4));
     $this->params['rounds'] = (int) $parts[0];
     $this->args[0] = substr($parts[1], 0, 22);
     $this->hash = substr($parts[1], 22);
 }
Example #4
0
 /**
  * Set a value in the session, encrypted
  *
  * This relies on the secrecy of $wgSecretKey (by default), or $wgSessionSecret.
  *
  * @param string|int $key
  * @param mixed $value
  */
 public function setSecret($key, $value)
 {
     global $wgSessionInsecureSecrets;
     list($encKey, $hmacKey) = $this->getSecretKeys();
     $serialized = serialize($value);
     // The code for encryption (with OpenSSL) and sealing is taken from
     // Chris Steipp's OATHAuthUtils class in Extension::OATHAuth.
     // Encrypt
     // @todo: import a pure-PHP library for AES instead of doing $wgSessionInsecureSecrets
     $iv = \MWCryptRand::generate(16, true);
     if (function_exists('openssl_encrypt')) {
         $ciphertext = openssl_encrypt($serialized, 'aes-256-ctr', $encKey, OPENSSL_RAW_DATA, $iv);
         if ($ciphertext === false) {
             throw new UnexpectedValueException('Encryption failed: ' . openssl_error_string());
         }
     } elseif (function_exists('mcrypt_encrypt')) {
         $ciphertext = mcrypt_encrypt('rijndael-128', $encKey, $serialized, 'ctr', $iv);
         if ($ciphertext === false) {
             throw new UnexpectedValueException('Encryption failed');
         }
     } elseif ($wgSessionInsecureSecrets) {
         $ex = new \Exception('No encryption is available, storing data as plain text');
         $this->logger->warning($ex->getMessage(), ['exception' => $ex]);
         $ciphertext = $serialized;
     } else {
         throw new \BadMethodCallException('Encryption is not available. You really should install the PHP OpenSSL extension, ' . 'or failing that the mcrypt extension. But if you really can\'t and you\'re willing ' . 'to accept insecure storage of sensitive session data, set ' . '$wgSessionInsecureSecrets = true in LocalSettings.php to make this exception go away.');
     }
     // Seal
     $sealed = base64_encode($iv) . '.' . base64_encode($ciphertext);
     $hmac = hash_hmac('sha256', $sealed, $hmacKey, true);
     $encrypted = base64_encode($hmac) . '.' . $sealed;
     // Store
     $this->set($key, $encrypted);
 }
Example #5
0
 /**
  * MW specific salt, cached from last run
  * @return string Binary string
  */
 protected function getSaltUsingCache()
 {
     if ($this->salt == '') {
         $lastSalt = $this->cache->get($this->cacheKey);
         if ($lastSalt === false) {
             // If we don't have a previous value to use as our salt, we use
             // 16 bytes from MWCryptRand, which will use a small amount of
             // entropy from our pool. Note, "XTR may be deterministic or keyed
             // via an optional “salt value”  (i.e., a non-secret random
             // value)..." - http://eprint.iacr.org/2010/264.pdf. However, we
             // use a strongly random value since we can.
             $lastSalt = MWCryptRand::generate(16);
         }
         // Get a binary string that is hashLen long
         $this->salt = hash($this->algorithm, $lastSalt, true);
     }
     return $this->salt;
 }
Example #6
0
 /**
  * Set a value in the session, encrypted
  *
  * This relies on the secrecy of $wgSecretKey (by default), or $wgSessionSecret.
  *
  * @param string|int $key
  * @param mixed $value
  */
 public function setSecret($key, $value)
 {
     list($encKey, $hmacKey) = $this->getSecretKeys();
     $serialized = serialize($value);
     // The code for encryption (with OpenSSL) and sealing is taken from
     // Chris Steipp's OATHAuthUtils class in Extension::OATHAuth.
     // Encrypt
     // @todo: import a pure-PHP library for AES instead of doing $wgSessionInsecureSecrets
     $iv = \MWCryptRand::generate(16, true);
     $algorithm = self::getEncryptionAlgorithm();
     switch ($algorithm[0]) {
         case 'openssl':
             $ciphertext = openssl_encrypt($serialized, $algorithm[1], $encKey, OPENSSL_RAW_DATA, $iv);
             if ($ciphertext === false) {
                 throw new \UnexpectedValueException('Encryption failed: ' . openssl_error_string());
             }
             break;
         case 'mcrypt':
             // PKCS7 padding
             $blocksize = mcrypt_get_block_size($algorithm[1], $algorithm[2]);
             $pad = $blocksize - strlen($serialized) % $blocksize;
             $serialized .= str_repeat(chr($pad), $pad);
             $ciphertext = mcrypt_encrypt($algorithm[1], $encKey, $serialized, $algorithm[2], $iv);
             if ($ciphertext === false) {
                 throw new \UnexpectedValueException('Encryption failed');
             }
             break;
         case 'insecure':
             $ex = new \Exception('No encryption is available, storing data as plain text');
             $this->logger->warning($ex->getMessage(), ['exception' => $ex]);
             $ciphertext = $serialized;
             break;
         default:
             throw new \LogicException('invalid algorithm');
     }
     // Seal
     $sealed = base64_encode($iv) . '.' . base64_encode($ciphertext);
     $hmac = hash_hmac('sha256', $sealed, $hmacKey, true);
     $encrypted = base64_encode($hmac) . '.' . $sealed;
     // Store
     $this->set($key, $encrypted);
 }
Example #7
0
 public function testSecrets()
 {
     $logger = new \TestLogger();
     $session = TestUtils::getDummySession(null, -1, $logger);
     // Simple defaulting
     $this->assertEquals('defaulted', $session->getSecret('test', 'defaulted'));
     // Bad encrypted data
     $session->set('test', 'foobar');
     $logger->setCollect(true);
     $this->assertEquals('defaulted', $session->getSecret('test', 'defaulted'));
     $logger->setCollect(false);
     $this->assertSame([[LogLevel::WARNING, 'Invalid sealed-secret format']], $logger->getBuffer());
     $logger->clearBuffer();
     // Tampered data
     $session->setSecret('test', 'foobar');
     $encrypted = $session->get('test');
     $session->set('test', $encrypted . 'x');
     $logger->setCollect(true);
     $this->assertEquals('defaulted', $session->getSecret('test', 'defaulted'));
     $logger->setCollect(false);
     $this->assertSame([[LogLevel::WARNING, 'Sealed secret has been tampered with, aborting.']], $logger->getBuffer());
     $logger->clearBuffer();
     // Unserializable data
     $iv = \MWCryptRand::generate(16, true);
     list($encKey, $hmacKey) = \TestingAccessWrapper::newFromObject($session)->getSecretKeys();
     $ciphertext = openssl_encrypt('foobar', 'aes-256-ctr', $encKey, OPENSSL_RAW_DATA, $iv);
     $sealed = base64_encode($iv) . '.' . base64_encode($ciphertext);
     $hmac = hash_hmac('sha256', $sealed, $hmacKey, true);
     $encrypted = base64_encode($hmac) . '.' . $sealed;
     $session->set('test', $encrypted);
     \MediaWiki\suppressWarnings();
     $this->assertEquals('defaulted', $session->getSecret('test', 'defaulted'));
     \MediaWiki\restoreWarnings();
 }