/** * Stream decryption - Do not call directly * * @param ReadOnlyFile $input * @param MutableFile $output * @param Key $encKey * @param string $nonce * @param resource $mac (hash context) * @param Config $config * @return bool * @throws CryptoException\AccessDenied * @throws CryptoException\CannotPerformOperation * @throws CryptoException\FileModified * @throws CryptoException\InvalidKey * @throws CryptoException\InvalidMessage */ private static final function streamDecrypt(ReadOnlyFile $input, MutableFile $output, EncryptionKey $encKey, string $nonce, $mac, Config $config, array &$chunk_macs) : bool { $start = $input->getPos(); $cipher_end = $input->getSize() - $config->MAC_SIZE; // Begin the streaming decryption $input->reset($start); while ($input->remainingBytes() > $config->MAC_SIZE) { /** * Would a full BUFFER read put it past the end of the * ciphertext? If so, only return a portion of the file. */ if ($input->getPos() + $config->BUFFER > $cipher_end) { $read = $input->readBytes($cipher_end - $input->getPos()); } else { $read = $input->readBytes($config->BUFFER); } if ($config->USE_BLAKE2B) { \Sodium\crypto_generichash_update($mac, $read); $calcMAC = '' . $mac; $calc = \Sodium\crypto_generichash_final($calcMAC, $config->MAC_SIZE); } else { \hash_update($mac, $read); $calcMAC = \hash_copy($mac); if ($calcMAC === false) { throw new CryptoException\CannotPerformOperation('An unknown error has occurred'); } $calc = \hash_final($calcMAC, true); } if (empty($chunk_macs)) { throw new CryptoException\InvalidMessage('Invalid message authentication code'); } else { $chkmac = \array_shift($chunk_macs); if (!\hash_equals($chkmac, $calc)) { throw new CryptoException\InvalidMessage('Invalid message authentication code'); } } $decrypted = \Sodium\crypto_stream_xor($read, $nonce, $encKey->getRawKeyMaterial()); $output->writeBytes($decrypted); \Sodium\increment($nonce); } \Sodium\memzero($nonce); return true; }
/** * Stream decryption - Do not call directly * * @param ReadOnlyFile $input * @param MutableFile $output * @param EncryptionKey $encKey * @param string $nonce * @param string $mac (hash context for BLAKE2b) * @param Config $config * @param array &$chunk_macs * @return bool * @throws FileAccessDenied * @throws CannotPerformOperation * @throws FileModified * @throws InvalidKey * @throws InvalidMessage */ private static final function streamDecrypt(ReadOnlyFile $input, MutableFile $output, EncryptionKey $encKey, string $nonce, string $mac, Config $config, array &$chunk_macs) : bool { $start = $input->getPos(); $cipher_end = $input->getSize() - $config->MAC_SIZE; // Begin the streaming decryption $input->reset($start); while ($input->remainingBytes() > $config->MAC_SIZE) { /** * Would a full BUFFER read put it past the end of the * ciphertext? If so, only return a portion of the file. */ if ($input->getPos() + $config->BUFFER > $cipher_end) { $read = $input->readBytes($cipher_end - $input->getPos()); } else { $read = $input->readBytes($config->BUFFER); } // Version 2+ uses a keyed BLAKE2b hash instead of HMAC \Sodium\crypto_generichash_update($mac, $read); $calcMAC = Util::safeStrcpy($mac); $calc = \Sodium\crypto_generichash_final($calcMAC, $config->MAC_SIZE); if (empty($chunk_macs)) { // Someone attempted to add a chunk at the end. throw new InvalidMessage('Invalid message authentication code'); } else { $chunkMAC = \array_shift($chunk_macs); if (!\hash_equals($chunkMAC, $calc)) { // This chunk was altered after the original MAC was verified throw new InvalidMessage('Invalid message authentication code'); } } // This is where the decryption actually occurs: $decrypted = \Sodium\crypto_stream_xor($read, $nonce, $encKey->getRawKeyMaterial()); $output->writeBytes($decrypted); \Sodium\increment($nonce); } \Sodium\memzero($nonce); return true; }