Defuse\Crypto\File::decryptResourceInternal PHP Method

decryptResourceInternal() public static method

Decrypts a file-backed resource with either a key or a password.
public static decryptResourceInternal ( resource $inputHandle, resource $outputHandle, KeyOrPassword $secret )
$inputHandle resource
$outputHandle resource
$secret KeyOrPassword
    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. */
        }
    }