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;
}