Beispiel #1
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.');
     }
 }
Beispiel #2
0
 /**
  * Decrypts a file-backed resource with either a key or a password.
  *
  * @param resource      $inputHandle
  * @param resource      $outputHandle
  * @param KeyOrPassword $secret
  *
  * @throws Defuse\Crypto\Exception\EnvironmentIsBrokenException
  * @throws Defuse\Crypto\Exception\IOException
  * @throws Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException
  */
 public static function decryptResourceInternal($inputHandle, $outputHandle, KeyOrPassword $secret)
 {
     if (!\is_resource($inputHandle)) {
         throw new Ex\IOException('Input handle must be a resource!');
     }
     if (!\is_resource($outputHandle)) {
         throw new Ex\IOException('Output handle must be a resource!');
     }
     /* Make sure the file is big enough for all the reads we need to do. */
     $stat = \fstat($inputHandle);
     if ($stat['size'] < Core::MINIMUM_CIPHERTEXT_SIZE) {
         throw new Ex\WrongKeyOrModifiedCiphertextException('Input file is too small to have been created by this library.');
     }
     /* Check the version header. */
     $header = self::readBytes($inputHandle, Core::HEADER_VERSION_SIZE);
     if ($header !== Core::CURRENT_VERSION) {
         throw new Ex\WrongKeyOrModifiedCiphertextException('Bad version header.');
     }
     /* Get the salt. */
     $file_salt = self::readBytes($inputHandle, Core::SALT_BYTE_SIZE);
     /* Get the IV. */
     $ivsize = Core::BLOCK_BYTE_SIZE;
     $iv = self::readBytes($inputHandle, $ivsize);
     /* Derive the authentication and encryption keys. */
     $keys = $secret->deriveKeys($file_salt);
     $ekey = $keys->getEncryptionKey();
     $akey = $keys->getAuthenticationKey();
     /* We'll store the MAC of each buffer-sized chunk as we verify the
      * actual MAC, so that we can check them again when decrypting. */
     $macs = [];
     /* $thisIv will be incremented after each call to the decryption. */
     $thisIv = $iv;
     /* How many blocks do we encrypt at a time? We increment by this value. */
     $inc = Core::BUFFER_BYTE_SIZE / Core::BLOCK_BYTE_SIZE;
     /* Get the HMAC. */
     if (\fseek($inputHandle, -1 * Core::MAC_BYTE_SIZE, SEEK_END) === false) {
         throw new Ex\IOException('Cannot seek to beginning of MAC within input file');
     }
     /* Get the position of the last byte in the actual ciphertext. */
     $cipher_end = \ftell($inputHandle);
     if ($cipher_end === false) {
         throw new Ex\IOException('Cannot read input file');
     }
     /* We have the position of the first byte of the HMAC. Go back by one. */
     --$cipher_end;
     /* Read the HMAC. */
     $stored_mac = self::readBytes($inputHandle, Core::MAC_BYTE_SIZE);
     /* Initialize a streaming HMAC state. */
     $hmac = \hash_init(Core::HASH_FUNCTION_NAME, HASH_HMAC, $akey);
     if ($hmac === false) {
         throw new Ex\EnvironmentIsBrokenException('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\IOException('Cannot read seek within input file');
     }
     /* Seek to the start of the actual ciphertext. */
     if (\fseek($inputHandle, Core::SALT_BYTE_SIZE + $ivsize, SEEK_CUR) === false) {
         throw new Ex\IOException('Cannot seek input file to beginning of ciphertext');
     }
     /* PASS #1: Calculating the HMAC. */
     \hash_update($hmac, $header);
     \hash_update($hmac, $file_salt);
     \hash_update($hmac, $iv);
     $hmac2 = \hash_copy($hmac);
     $break = false;
     while (!$break) {
         $pos = \ftell($inputHandle);
         if ($pos === false) {
             throw new Ex\IOException('Could not get current position in input file during decryption');
         }
         /* Read the next buffer-sized chunk (or less). */
         if ($pos + Core::BUFFER_BYTE_SIZE >= $cipher_end) {
             $break = true;
             $read = self::readBytes($inputHandle, $cipher_end - $pos + 1);
         } else {
             $read = self::readBytes($inputHandle, Core::BUFFER_BYTE_SIZE);
         }
         /* Update the HMAC. */
         \hash_update($hmac, $read);
         /* Remember this buffer-sized chunk's HMAC. */
         $chunk_mac = \hash_copy($hmac);
         if ($chunk_mac === false) {
             throw new Ex\EnvironmentIsBrokenException('Cannot duplicate a hash context');
         }
         $macs[] = \hash_final($chunk_mac);
     }
     /* Get the final HMAC, which should match the stored one. */
     $final_mac = \hash_final($hmac, true);
     /* Verify the HMAC. */
     if (!Core::hashEquals($final_mac, $stored_mac)) {
         throw new Ex\WrongKeyOrModifiedCiphertextException('Integrity check failed.');
     }
     /* PASS #2: Decrypt and write output. */
     /* Rewind to the start of the actual ciphertext. */
     if (\fseek($inputHandle, Core::SALT_BYTE_SIZE + $ivsize + Core::HEADER_VERSION_SIZE, SEEK_SET) === false) {
         throw new Ex\IOException('Could not move the input file pointer during decryption');
     }
     $at_file_end = false;
     while (!$at_file_end) {
         $pos = \ftell($inputHandle);
         if ($pos === false) {
             throw new Ex\IOException('Could not get current position in input file during decryption');
         }
         /* Read the next buffer-sized chunk (or less). */
         if ($pos + Core::BUFFER_BYTE_SIZE >= $cipher_end) {
             $at_file_end = true;
             $read = self::readBytes($inputHandle, $cipher_end - $pos + 1);
         } else {
             $read = self::readBytes($inputHandle, Core::BUFFER_BYTE_SIZE);
         }
         /* Recalculate the MAC (so far) and compare it with the one we
          * remembered from pass #1 to ensure attackers didn't change the
          * ciphertext after MAC verification. */
         \hash_update($hmac2, $read);
         $calc_mac = \hash_copy($hmac2);
         if ($calc_mac === false) {
             throw new Ex\EnvironmentIsBrokenException('Cannot duplicate a hash context');
         }
         $calc = \hash_final($calc_mac);
         if (empty($macs)) {
             throw new Ex\WrongKeyOrModifiedCiphertextException('File was modified after MAC verification');
         } elseif (!Core::hashEquals(\array_shift($macs), $calc)) {
             throw new Ex\WrongKeyOrModifiedCiphertextException('File was modified after MAC verification');
         }
         /* Decrypt this buffer-sized chunk. */
         $decrypted = \openssl_decrypt($read, Core::CIPHER_METHOD, $ekey, OPENSSL_RAW_DATA, $thisIv);
         if ($decrypted === false) {
             throw new Ex\EnvironmentIsBrokenException('OpenSSL decryption error');
         }
         /* Write the plaintext to the output file. */
         self::writeBytes($outputHandle, $decrypted, Core::ourStrlen($decrypted));
         /* Increment the IV by the amount of blocks in a buffer. */
         $thisIv = Core::incrementCounter($thisIv, $inc);
         /* WARNING: Usually, unless the file is a multiple of the buffer
          * size, $thisIv will contain an incorrect value here on the last
          * iteration of this loop. */
     }
 }