示例#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
 /**
  * Decrypts a legacy ciphertext produced by version 1 of this library.
  *
  * @param string $ciphertext
  * @param string $key
  *
  * @throws Ex\EnvironmentIsBrokenException
  * @throws Ex\WrongKeyOrModifiedCiphertextException
  *
  * @return string
  */
 public static function legacyDecrypt($ciphertext, $key)
 {
     RuntimeTests::runtimeTest();
     // Extract the HMAC from the front of the ciphertext.
     if (Core::ourStrlen($ciphertext) <= Core::LEGACY_MAC_BYTE_SIZE) {
         throw new Ex\WrongKeyOrModifiedCiphertextException('Ciphertext is too short.');
     }
     $hmac = Core::ourSubstr($ciphertext, 0, Core::LEGACY_MAC_BYTE_SIZE);
     if ($hmac === false) {
         throw new Ex\EnvironmentIsBrokenException();
     }
     $ciphertext = Core::ourSubstr($ciphertext, Core::LEGACY_MAC_BYTE_SIZE);
     if ($ciphertext === false) {
         throw new Ex\EnvironmentIsBrokenException();
     }
     // Regenerate the same authentication sub-key.
     $akey = Core::HKDF(Core::LEGACY_HASH_FUNCTION_NAME, $key, Core::LEGACY_KEY_BYTE_SIZE, Core::LEGACY_AUTHENTICATION_INFO_STRING, null);
     if (self::verifyHMAC($hmac, $ciphertext, $akey)) {
         // Regenerate the same encryption sub-key.
         $ekey = Core::HKDF(Core::LEGACY_HASH_FUNCTION_NAME, $key, Core::LEGACY_KEY_BYTE_SIZE, Core::LEGACY_ENCRYPTION_INFO_STRING, null);
         // Extract the IV from the ciphertext.
         if (Core::ourStrlen($ciphertext) <= Core::LEGACY_BLOCK_BYTE_SIZE) {
             throw new Ex\WrongKeyOrModifiedCiphertextException('Ciphertext is too short.');
         }
         $iv = Core::ourSubstr($ciphertext, 0, Core::LEGACY_BLOCK_BYTE_SIZE);
         if ($iv === false) {
             throw new Ex\EnvironmentIsBrokenException();
         }
         $ciphertext = Core::ourSubstr($ciphertext, Core::LEGACY_BLOCK_BYTE_SIZE);
         if ($ciphertext === false) {
             throw new Ex\EnvironmentIsBrokenException();
         }
         // Do the decryption.
         $plaintext = self::plainDecrypt($ciphertext, $ekey, $iv, Core::LEGACY_CIPHER_METHOD);
         return $plaintext;
     } else {
         throw new Ex\WrongKeyOrModifiedCiphertextException('Integrity check failed.');
     }
 }
示例#3
0
文件: File.php 项目: robstoll/PuMa
 /**
  * Decrypt the contents of a file handle $inputHandle and store the results
  * in $outputHandle using HKDF of $key to decrypt then verify
  *
  * @param resource $inputHandle
  * @param resource $outputHandle
  * @param Key $key
  * @return boolean
  */
 public static function decryptResource($inputHandle, $outputHandle, Key $key)
 {
     // Because we don't have strict typing in PHP 5
     if (!\is_resource($inputHandle)) {
         throw new Ex\InvalidInput('Input handle must be a resource!');
     }
     if (!\is_resource($outputHandle)) {
         throw new Ex\InvalidInput('Output handle must be a resource!');
     }
     // Parse the header.
     $header = self::readBytes($inputHandle, Core::HEADER_VERSION_SIZE);
     $config = self::getFileVersionConfigFromHeader($header, Core::CURRENT_FILE_VERSION);
     // Let's add this check before anything
     if (!\in_array($config->hashFunctionName(), \hash_algos())) {
         throw new Ex\CannotPerformOperationException('The specified hash function does not exist');
     }
     // Let's grab the file salt.
     $file_salt = self::readBytes($inputHandle, $config->saltByteSize());
     // For storing MACs of each buffer chunk
     $macs = [];
     /**
      * 1. We need to decode some values from our files
      */
     /**
      * Let's split our keys
      *
      * $ekey -- Encryption Key -- used for AES
      */
     $ekey = Core::HKDF($config->hashFunctionName(), $key->getRawBytes(), $config->keyByteSize(), $config->encryptionInfoString(), $file_salt, $config);
     /**
      * $akey -- Authentication Key -- used for HMAC
      */
     $akey = Core::HKDF($config->hashFunctionName(), $key->getRawBytes(), $config->keyByteSize(), $config->authenticationInfoString(), $file_salt, $config);
     /**
      * Grab our IV from the encrypted message
      *
      * It should be the first N blocks of the file (N = 16)
      */
     $ivsize = \openssl_cipher_iv_length($config->cipherMethod());
     $iv = self::readBytes($inputHandle, $ivsize);
     // How much do we increase the counter after each buffered encryption to prevent nonce reuse
     $inc = $config->bufferByteSize() / $config->blockByteSize();
     $thisIv = $iv;
     /**
      * Let's grab our MAC
      *
      * It should be the last N blocks of the file (N = 32)
      */
     if (\fseek($inputHandle, -1 * $config->macByteSize(), SEEK_END) === false) {
         throw new Ex\CannotPerformOperationException('Cannot seek to beginning of MAC within input file');
     }
     // Grab our last position of ciphertext before we read the MAC
     $cipher_end = \ftell($inputHandle);
     if ($cipher_end === false) {
         throw new Ex\CannotPerformOperationException('Cannot read input file');
     }
     --$cipher_end;
     // We need to subtract one
     // We keep our MAC stored in this variable
     $stored_mac = self::readBytes($inputHandle, $config->macByteSize());
     /**
      * We begin recalculating the HMAC for the entire file...
      */
     $hmac = \hash_init($config->hashFunctionName(), HASH_HMAC, $akey);
     if ($hmac === false) {
         throw new Ex\CannotPerformOperationException('Cannot initialize a hash context');
     }
     /**
      * Reset file pointer to the beginning of the file after the header
      */
     if (\fseek($inputHandle, Core::HEADER_VERSION_SIZE, SEEK_SET) === false) {
         throw new Ex\CannotPerformOperationException('Cannot read seek within input file');
     }
     /**
      * Set it to the first non-salt and non-IV byte
      */
     if (\fseek($inputHandle, $config->saltByteSize() + $ivsize, SEEK_CUR) === false) {
         throw new Ex\CannotPerformOperationException('Cannot read seek input file to beginning of ciphertext');
     }
     /**
      * 2. Let's recalculate the MAC
      */
     /**
      * Let's initialize our $hmac hasher with our Salt and IV
      */
     \hash_update($hmac, $header);
     \hash_update($hmac, $file_salt);
     \hash_update($hmac, $iv);
     $hmac2 = \hash_copy($hmac);
     $break = false;
     while (!$break) {
         /**
          * First, grab the current position
          */
         $pos = \ftell($inputHandle);
         if ($pos === false) {
             throw new Ex\CannotPerformOperationException('Could not get current position in input file during decryption');
         }
         /**
          * Would a full DBUFFER read put it past the end of the
          * ciphertext? If so, only return a portion of the file.
          */
         if ($pos + $config->bufferByteSize() >= $cipher_end) {
             $break = true;
             $read = self::readBytes($inputHandle, $cipher_end - $pos + 1);
         } else {
             $read = self::readBytes($inputHandle, $config->bufferByteSize());
         }
         if ($read === false) {
             throw new Ex\CannotPerformOperationException('Could not read input file during decryption');
         }
         /**
          * We're updating our HMAC and nothing else
          */
         \hash_update($hmac, $read);
         /**
          * Store a MAC of each chunk
          */
         $chunkMAC = \hash_copy($hmac);
         if ($chunkMAC === false) {
             throw new Ex\CannotPerformOperationException('Cannot duplicate a hash context');
         }
         $macs[] = \hash_final($chunkMAC);
     }
     /**
      * We should now have enough data to generate an identical HMAC
      */
     $finalHMAC = \hash_final($hmac, true);
     /**
      * 3. Did we match?
      */
     if (!Core::hashEquals($finalHMAC, $stored_mac)) {
         throw new Ex\InvalidCiphertextException('Message Authentication failure; tampering detected.');
     }
     /**
      * 4. Okay, let's begin decrypting
      */
     /**
      * Return file pointer to the first non-header, non-IV byte in the file
      */
     if (\fseek($inputHandle, $config->saltByteSize() + $ivsize + Core::HEADER_VERSION_SIZE, SEEK_SET) === false) {
         throw new Ex\CannotPerformOperationException('Could not move the input file pointer during decryption');
     }
     /**
      * Should we break the writing?
      */
     $breakW = false;
     /**
      * This loop writes plaintext to the destination file:
      */
     while (!$breakW) {
         /**
          * Get the current position
          */
         $pos = \ftell($inputHandle);
         if ($pos === false) {
             throw new Ex\CannotPerformOperationException('Could not get current position in input file during decryption');
         }
         /**
          * Would a full BUFFER read put it past the end of the
          * ciphertext? If so, only return a portion of the file.
          */
         if ($pos + $config->bufferByteSize() >= $cipher_end) {
             $breakW = true;
             $read = self::readBytes($inputHandle, $cipher_end - $pos + 1);
         } else {
             $read = self::readBytes($inputHandle, $config->bufferByteSize());
         }
         /**
          * Recalculate the MAC, compare with the one stored in the $macs
          * array to ensure attackers couldn't tamper with the file
          * after MAC verification
          */
         \hash_update($hmac2, $read);
         $calcMAC = \hash_copy($hmac2);
         if ($calcMAC === false) {
             throw new Ex\CannotPerformOperationException('Cannot duplicate a hash context');
         }
         $calc = \hash_final($calcMAC);
         if (empty($macs)) {
             throw new Ex\InvalidCiphertextException('File was modified after MAC verification');
         } elseif (!Core::hashEquals(\array_shift($macs), $calc)) {
             throw new Ex\InvalidCiphertextException('File was modified after MAC verification');
         }
         $thisIv = Core::incrementCounter($thisIv, $inc, $config);
         /**
          * Perform the AES decryption. Decrypts the message.
          */
         $decrypted = \openssl_decrypt($read, $config->cipherMethod(), $ekey, OPENSSL_RAW_DATA, $thisIv);
         /**
          * Test for decryption faulure
          */
         if ($decrypted === false) {
             throw new Ex\CannotPerformOperationException('OpenSSL decryption error');
         }
         /**
          * Write the plaintext out to the output file
          */
         self::writeBytes($outputHandle, $decrypted, Core::ourStrlen($decrypted));
     }
     return true;
 }
示例#4
0
 /**
  * Test HKDF against test vectors.
  *
  * @throws Ex\EnvironmentIsBrokenException
  */
 private static function HKDFTestVector()
 {
     // HKDF test vectors from RFC 5869
     // Test Case 1
     $ikm = \str_repeat("\v", 22);
     $salt = Encoding::hexToBin('000102030405060708090a0b0c');
     $info = Encoding::hexToBin('f0f1f2f3f4f5f6f7f8f9');
     $length = 42;
     $okm = Encoding::hexToBin('3cb25f25faacd57a90434f64d0362f2a' . '2d2d0a90cf1a5a4c5db02d56ecc4c5bf' . '34007208d5b887185865');
     $computed_okm = Core::HKDF('sha256', $ikm, $length, $info, $salt);
     if ($computed_okm !== $okm) {
         throw new Ex\EnvironmentIsBrokenException();
     }
     // Test Case 7
     $ikm = \str_repeat("\f", 22);
     $length = 42;
     $okm = Encoding::hexToBin('2c91117204d745f3500d636a62f64f0a' . 'b3bae548aa53d423b0d1f27ebba6f5e5' . '673a081d70cce7acfc48');
     $computed_okm = Core::HKDF('sha1', $ikm, $length, '', null);
     if ($computed_okm !== $okm) {
         throw new Ex\EnvironmentIsBrokenException();
     }
 }
示例#5
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.");
     }
 }