示例#1
0
 /**
  * Derives authentication and encryption keys from the secret, using a slow
  * key derivation function if the secret is a password.
  *
  * @param string $salt
  *
  * @throws Ex\EnvironmentIsBrokenException
  *
  * @return DerivedKeys
  */
 public function deriveKeys($salt)
 {
     if (Core::ourStrlen($salt) !== Core::SALT_BYTE_SIZE) {
         throw new Ex\EnvironmentIsBrokenException('Bad salt.');
     }
     if ($this->secret_type === self::SECRET_TYPE_KEY) {
         $akey = Core::HKDF(Core::HASH_FUNCTION_NAME, $this->secret->getRawBytes(), Core::KEY_BYTE_SIZE, Core::AUTHENTICATION_INFO_STRING, $salt);
         $ekey = Core::HKDF(Core::HASH_FUNCTION_NAME, $this->secret->getRawBytes(), Core::KEY_BYTE_SIZE, Core::ENCRYPTION_INFO_STRING, $salt);
         return new DerivedKeys($akey, $ekey);
     } elseif ($this->secret_type === self::SECRET_TYPE_PASSWORD) {
         /* Our PBKDF2 polyfill is vulnerable to a DoS attack documented in
          * GitHub issue #230. The fix is to pre-hash the password to ensure
          * it is short. We do the prehashing here instead of in pbkdf2() so
          * that pbkdf2() still computes the function as defined by the
          * standard. */
         $prehash = \hash(Core::HASH_FUNCTION_NAME, $this->secret, true);
         $prekey = Core::pbkdf2(Core::HASH_FUNCTION_NAME, $prehash, $salt, self::PBKDF2_ITERATIONS, Core::KEY_BYTE_SIZE, true);
         $akey = Core::HKDF(Core::HASH_FUNCTION_NAME, $prekey, Core::KEY_BYTE_SIZE, Core::AUTHENTICATION_INFO_STRING, $salt);
         /* Note the cryptographic re-use of $salt here. */
         $ekey = Core::HKDF(Core::HASH_FUNCTION_NAME, $prekey, Core::KEY_BYTE_SIZE, Core::ENCRYPTION_INFO_STRING, $salt);
         return new DerivedKeys($akey, $ekey);
     } else {
         throw new Ex\EnvironmentIsBrokenException('Bad secret type.');
     }
 }
示例#2
0
 /**
  * Convert a hexadecimal string into a binary string without cache-timing 
  * leaks
  *
  * @param string $hex_string
  * @return string (raw binary)
  */
 public static function hexToBin($hex_string)
 {
     $hex_pos = 0;
     $bin = '';
     $hex_len = Core::ourStrlen($hex_string);
     $state = 0;
     $c_acc = 0;
     while ($hex_pos < $hex_len) {
         $c = \ord($hex_string[$hex_pos]);
         $c_num = $c ^ 48;
         $c_num0 = $c_num - 10 >> 8;
         $c_alpha = ($c & ~32) - 55;
         $c_alpha0 = ($c_alpha - 10 ^ $c_alpha - 16) >> 8;
         if (($c_num0 | $c_alpha0) === 0) {
             throw new \RangeException('Encoding::hexToBin() only expects hexadecimal characters');
         }
         $c_val = $c_num0 & $c_num | $c_alpha & $c_alpha0;
         if ($state === 0) {
             $c_acc = $c_val * 16;
         } else {
             $bin .= \chr($c_acc | $c_val);
         }
         $state = $state ? 0 : 1;
         ++$hex_pos;
     }
     return $bin;
 }
示例#3
0
 public function testOurSubstrTrailingEmptyStringBugNormal()
 {
     // Same as above but with a non-weird string.
     $str = 'AAAAAAAAAAAAAAAA';
     if (ini_get('mbstring.func_overload') == 7) {
         $this->assertSame(16, strlen($str));
     } else {
         $this->assertSame(16, strlen($str));
     }
     $this->assertSame(16, Core::ourStrlen($str));
     $this->assertSame('', Core::ourSubstr($str, 16));
 }
示例#4
0
 /**
  * Runs the runtime tests.
  *
  * @throws Ex\EnvironmentIsBrokenException
  */
 public static function runtimeTest()
 {
     // 0: Tests haven't been run yet.
     // 1: Tests have passed.
     // 2: Tests are running right now.
     // 3: Tests have failed.
     static $test_state = 0;
     if ($test_state === 1 || $test_state === 2) {
         return;
     }
     if ($test_state === 3) {
         /* If an intermittent problem caused a test to fail previously, we
          * want that to be indicated to the user with every call to this
          * library. This way, if the user first does something they really
          * don't care about, and just ignores all exceptions, they won't get
          * screwed when they then start to use the library for something
          * they do care about. */
         throw new Ex\EnvironmentIsBrokenException('Tests failed previously.');
     }
     try {
         $test_state = 2;
         Core::ensureFunctionExists('openssl_get_cipher_methods');
         if (\in_array(Core::CIPHER_METHOD, \openssl_get_cipher_methods()) === false) {
             throw new Ex\EnvironmentIsBrokenException('Cipher method not supported. This is normally caused by an outdated ' . 'version of OpenSSL (and/or OpenSSL compiled for FIPS compliance). ' . 'Please upgrade to a newer version of OpenSSL that supports ' . Core::CIPHER_METHOD . ' to use this library.');
         }
         RuntimeTests::AESTestVector();
         RuntimeTests::HMACTestVector();
         RuntimeTests::HKDFTestVector();
         RuntimeTests::testEncryptDecrypt();
         if (Core::ourStrlen(Key::createNewRandomKey()->getRawBytes()) != Core::KEY_BYTE_SIZE) {
             throw new Ex\EnvironmentIsBrokenException();
         }
         if (Core::ENCRYPTION_INFO_STRING == Core::AUTHENTICATION_INFO_STRING) {
             throw new Ex\EnvironmentIsBrokenException();
         }
     } catch (Ex\EnvironmentIsBrokenException $ex) {
         // Do this, otherwise it will stay in the "tests are running" state.
         $test_state = 3;
         throw $ex;
     }
     // Change this to '0' make the tests always re-run (for benchmarking).
     $test_state = 1;
 }
示例#5
0
 /**
  * Decrypts a ciphertext to a string with either a key or a password.
  *
  * @param string        $ciphertext
  * @param KeyOrPassword $secret
  * @param bool          $raw_binary
  *
  * @throws Ex\EnvironmentIsBrokenException
  * @throws Ex\WrongKeyOrModifiedCiphertextException
  *
  * @return string
  */
 private static function decryptInternal($ciphertext, KeyOrPassword $secret, $raw_binary)
 {
     RuntimeTests::runtimeTest();
     if (!$raw_binary) {
         try {
             $ciphertext = Encoding::hexToBin($ciphertext);
         } catch (Ex\BadFormatException $ex) {
             throw new Ex\WrongKeyOrModifiedCiphertextException('Ciphertext has invalid hex encoding.');
         }
     }
     if (Core::ourStrlen($ciphertext) < Core::MINIMUM_CIPHERTEXT_SIZE) {
         throw new Ex\WrongKeyOrModifiedCiphertextException('Ciphertext is too short.');
     }
     // Get and check the version header.
     $header = Core::ourSubstr($ciphertext, 0, Core::HEADER_VERSION_SIZE);
     if ($header !== Core::CURRENT_VERSION) {
         throw new Ex\WrongKeyOrModifiedCiphertextException('Bad version header.');
     }
     // Get the salt.
     $salt = Core::ourSubstr($ciphertext, Core::HEADER_VERSION_SIZE, Core::SALT_BYTE_SIZE);
     if ($salt === false) {
         throw new Ex\EnvironmentIsBrokenException();
     }
     // Get the IV.
     $iv = Core::ourSubstr($ciphertext, Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE, Core::BLOCK_BYTE_SIZE);
     if ($iv === false) {
         throw new Ex\EnvironmentIsBrokenException();
     }
     // Get the HMAC.
     $hmac = Core::ourSubstr($ciphertext, Core::ourStrlen($ciphertext) - Core::MAC_BYTE_SIZE, Core::MAC_BYTE_SIZE);
     if ($hmac === false) {
         throw new Ex\EnvironmentIsBrokenException();
     }
     // Get the actual encrypted ciphertext.
     $encrypted = Core::ourSubstr($ciphertext, Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + Core::BLOCK_BYTE_SIZE, Core::ourStrlen($ciphertext) - Core::MAC_BYTE_SIZE - Core::SALT_BYTE_SIZE - Core::BLOCK_BYTE_SIZE - Core::HEADER_VERSION_SIZE);
     if ($encrypted === false) {
         throw new Ex\EnvironmentIsBrokenException();
     }
     // Derive the separate encryption and authentication keys from the key
     // or password, whichever it is.
     $keys = $secret->deriveKeys($salt);
     if (self::verifyHMAC($hmac, $header . $salt . $iv . $encrypted, $keys->getAuthenticationKey())) {
         $plaintext = self::plainDecrypt($encrypted, $keys->getEncryptionKey(), $iv, Core::CIPHER_METHOD);
         return $plaintext;
     } else {
         throw new Ex\WrongKeyOrModifiedCiphertextException('Integrity check failed.');
     }
 }
示例#6
0
 public function testCreateNewRandomKey()
 {
     $key = Key::createNewRandomKey();
     $this->assertSame(32, Core::ourStrlen($key->getRawBytes()));
 }
示例#7
0
 /**
  * INTERNAL USE ONLY: Decodes, verifies the header and checksum, and returns
  * the encoded byte string.
  *
  * @param string $expected_header
  * @param string $string
  *
  * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException
  * @throws \Defuse\Crypto\Exception\BadFormatException
  *
  * @return string
  */
 public static function loadBytesFromChecksummedAsciiSafeString($expected_header, $string)
 {
     // Headers must be a constant length to prevent one type's header from
     // being a prefix of another type's header, leading to ambiguity.
     if (Core::ourStrlen($expected_header) !== self::SERIALIZE_HEADER_BYTES) {
         throw new Ex\EnvironmentIsBrokenException('Header must be 4 bytes.');
     }
     $bytes = Encoding::hexToBin($string);
     /* Make sure we have enough bytes to get the version header and checksum. */
     if (Core::ourStrlen($bytes) < self::SERIALIZE_HEADER_BYTES + self::CHECKSUM_BYTE_SIZE) {
         throw new Ex\BadFormatException('Encoded data is shorter than expected.');
     }
     /* Grab the version header. */
     $actual_header = Core::ourSubstr($bytes, 0, self::SERIALIZE_HEADER_BYTES);
     if ($actual_header !== $expected_header) {
         throw new Ex\BadFormatException('Invalid header.');
     }
     /* Grab the bytes that are part of the checksum. */
     $checked_bytes = Core::ourSubstr($bytes, 0, Core::ourStrlen($bytes) - self::CHECKSUM_BYTE_SIZE);
     /* Grab the included checksum. */
     $checksum_a = Core::ourSubstr($bytes, Core::ourStrlen($bytes) - self::CHECKSUM_BYTE_SIZE, self::CHECKSUM_BYTE_SIZE);
     /* Re-compute the checksum. */
     $checksum_b = \hash(self::CHECKSUM_HASH_ALGO, $checked_bytes, true);
     /* Check if the checksum matches. */
     if (!Core::hashEquals($checksum_a, $checksum_b)) {
         throw new Ex\BadFormatException("Data is corrupted, the checksum doesn't match");
     }
     return Core::ourSubstr($bytes, self::SERIALIZE_HEADER_BYTES, Core::ourStrlen($bytes) - self::SERIALIZE_HEADER_BYTES - self::CHECKSUM_BYTE_SIZE);
 }
示例#8
0
文件: File.php 项目: robstoll/PuMa
 /**
  * Write to a stream; prevent partial writes
  *
  * @param resource $stream
  * @param string $buf
  * @param int $num (number of bytes)
  * @return string
  * @throws Ex\CannotPerformOperationException
  */
 public static final function writeBytes($stream, $buf, $num = null)
 {
     $bufSize = Core::ourStrlen($buf);
     if ($num === null) {
         $num = $bufSize;
     }
     if ($num > $bufSize) {
         throw new Ex\CannotPerformOperationException('Trying to write more bytes than the buffer contains.');
     }
     if ($num < 0) {
         throw new Ex\CannotPerformOperationException('Tried to write less than 0 bytes');
     }
     $remaining = $num;
     while ($remaining > 0) {
         $written = \fwrite($stream, $buf, $remaining);
         if ($written === false) {
             throw new Ex\CannotPerformOperationException('Could not write to the file');
         }
         $buf = Core::ourSubstr($buf, $written, null);
         $remaining -= $written;
     }
     return $num;
 }
示例#9
0
文件: Crypto.php 项目: robstoll/PuMa
 /**
  * Decrypts a ciphertext (legacy -- before version tagging)
  *
  * $ciphertext is the ciphertext to decrypt.
  * $key is the key that the ciphertext was encrypted with.
  * You MUST catch exceptions thrown by this function. Read the docs.
  *
  * @param string $ciphertext
  * @param string $key
  * @return string
  * @throws Ex\CannotPerformOperationException
  * @throws Ex\CryptoTestFailedException
  * @throws Ex\InvalidCiphertextException
  */
 public static function legacyDecrypt($ciphertext, $key)
 {
     RuntimeTests::runtimeTest();
     $config = self::getVersionConfigFromHeader(Core::LEGACY_VERSION, Core::LEGACY_VERSION);
     // Extract the HMAC from the front of the ciphertext.
     if (Core::ourStrlen($ciphertext) <= $config->macByteSize()) {
         throw new Ex\InvalidCiphertextException("Ciphertext is too short.");
     }
     $hmac = Core::ourSubstr($ciphertext, 0, $config->macByteSize());
     if ($hmac === false) {
         throw new Ex\CannotPerformOperationException();
     }
     $ciphertext = Core::ourSubstr($ciphertext, $config->macByteSize());
     if ($ciphertext === false) {
         throw new Ex\CannotPerformOperationException();
     }
     // Regenerate the same authentication sub-key.
     $akey = Core::HKDF($config->hashFunctionName(), $key, $config->keyByteSize(), $config->authenticationInfoString(), null, $config);
     if (self::verifyHMAC($hmac, $ciphertext, $akey, $config)) {
         // Regenerate the same encryption sub-key.
         $ekey = Core::HKDF($config->hashFunctionName(), $key, $config->keyByteSize(), $config->encryptionInfoString(), null, $config);
         // Extract the initialization vector from the ciphertext.
         Core::EnsureFunctionExists("openssl_cipher_iv_length");
         $ivsize = \openssl_cipher_iv_length($config->cipherMethod());
         if ($ivsize === false || $ivsize <= 0) {
             throw new Ex\CannotPerformOperationException("Could not get the IV length from OpenSSL");
         }
         if (Core::ourStrlen($ciphertext) <= $ivsize) {
             throw new Ex\InvalidCiphertextException("Ciphertext is too short.");
         }
         $iv = Core::ourSubstr($ciphertext, 0, $ivsize);
         if ($iv === false) {
             throw new Ex\CannotPerformOperationException();
         }
         $ciphertext = Core::ourSubstr($ciphertext, $ivsize);
         if ($ciphertext === false) {
             throw new Ex\CannotPerformOperationException();
         }
         $plaintext = self::plainDecrypt($ciphertext, $ekey, $iv, $config);
         return $plaintext;
     } else {
         /*
          * We throw an exception instead of returning false because we want
          * a script that doesn't handle this condition to CRASH, instead
          * of thinking the ciphertext decrypted to the value false.
          */
         throw new Ex\InvalidCiphertextException("Integrity check failed.");
     }
 }
示例#10
0
文件: Key.php 项目: robstoll/PuMa
 public function getRawBytes()
 {
     if (is_null($this->key_bytes) || Core::ourStrlen($this->key_bytes) < self::MIN_SAFE_KEY_BYTE_SIZE) {
         throw new CannotPerformOperationException("An attempt was made to use an uninitialzied or too-short key");
     }
     return $this->key_bytes;
 }