/** * Recalculate and verify the HMAC of the input file * * @param resource $input * @param resource|string $mac (hash context) * @param Config $config * * @return array Hashes of various chunks * @throws CryptoException\CannotPerformOperation * @throws CryptoException\InvalidMessage */ private static final function streamVerify(ReadOnlyFile $input, $mac, Config $config) : array { $start = $input->getPos(); $cipher_end = $input->getSize() - $config->MAC_SIZE; $input->reset($cipher_end); $stored_mac = $input->readBytes($config->MAC_SIZE); $input->reset($start); $chunk_macs = []; $break = false; while (!$break && $input->getPos() < $cipher_end) { /** * 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) { $break = true; $read = $input->readBytes($cipher_end - $input->getPos()); } else { $read = $input->readBytes($config->BUFFER); } /** * We're updating our HMAC and nothing else */ if ($config->USE_BLAKE2B) { \Sodium\crypto_generichash_update($mac, $read); $chunkMAC = '' . $mac; $chunk_macs[] = \Sodium\crypto_generichash_final($chunkMAC, $config->MAC_SIZE); } else { \hash_update($mac, $read); /** * Store a MAC of each chunk */ $chunkMAC = \hash_copy($mac); if ($chunkMAC === false) { throw new CryptoException\CannotPerformOperation('An unknown error has occurred'); } $chunk_macs[] = \hash_final($chunkMAC, true); } } /** * We should now have enough data to generate an identical HMAC */ if ($config->USE_BLAKE2B) { $finalHMAC = \Sodium\crypto_generichash_final($mac, $config->MAC_SIZE); } else { $finalHMAC = \hash_final($mac, true); } /** * Use hash_equals() to be timing-invariant */ if (!\hash_equals($finalHMAC, $stored_mac)) { throw new CryptoException\InvalidMessage('Invalid message authentication code'); } $input->reset($start); return $chunk_macs; }
/** * Recalculate and verify the HMAC of the input file * * @param ReadOnlyFile $input The file we are verifying * @param resource|string $mac (hash context) * @param Config $config Version-specific settings * @return array Hashes of various chunks * @throws CannotPerformOperation * @throws InvalidMessage */ private static final function streamVerify(ReadOnlyFile $input, $mac, Config $config) : array { $start = $input->getPos(); // Grab the stored MAC: $cipher_end = $input->getSize() - $config->MAC_SIZE; $input->reset($cipher_end); $stored_mac = $input->readBytes($config->MAC_SIZE); $input->reset($start); $chunkMACs = []; $break = false; while (!$break && $input->getPos() < $cipher_end) { /** * 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) { $break = true; $read = $input->readBytes($cipher_end - $input->getPos()); } else { $read = $input->readBytes($config->BUFFER); } /** * We're updating our HMAC and nothing else */ \Sodium\crypto_generichash_update($mac, $read); // Copy the hash state then store the MAC of this chunk $chunkMAC = Util::safeStrcpy($mac); $chunkMACs[] = \Sodium\crypto_generichash_final($chunkMAC, $config->MAC_SIZE); } /** * We should now have enough data to generate an identical MAC */ $finalHMAC = \Sodium\crypto_generichash_final($mac, $config->MAC_SIZE); /** * Use hash_equals() to be timing-invariant */ if (!\hash_equals($finalHMAC, $stored_mac)) { throw new InvalidMessage('Invalid message authentication code'); } $input->reset($start); return $chunkMACs; }