/** * 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; }
/** * Calculate a BLAHE2b checksum of a file * * @param string $fileHandle The file you'd like to checksum * @param string $key An optional BLAKE2b key * @param bool $raw Set to true if you don't want hex * * @return string */ public static function checksumResource($fileHandle, \ParagonIE\Halite\Contract\CryptoKeyInterface $key = null, $raw = false) { // Input validation if (!\is_resource($fileHandle)) { throw new \ParagonIE\Halite\Alerts\InvalidType('Expected input handle to be a resource'); } $config = self::getConfig(Halite::HALITE_VERSION, 'checksum'); if ($key) { $state = \Sodium\crypto_generichash_init($key->get(), $config['HASH_LEN']); } else { $state = \Sodium\crypto_generichash_init(null, $config['HASH_LEN']); } while (!\feof($fileHandle)) { $read = \fread($fileHandle, $config['BUFFER']); if ($read === false) { throw new CryptoException\FileAccessDenied('Could not read from the file'); } \Sodium\crypto_generichash_update($state, $read); } if ($raw) { return \Sodium\crypto_generichash_final($state, $config['HASH_LEN']); } return \Sodium\bin2hex(\Sodium\crypto_generichash_final($state, $config['HASH_LEN'])); }
/** * * @param \ParagonIE\Halite\Contract\StreamInterface $fileStream * @param AuthenticationKey $key * @param type $raw * @return type */ public static function checksumStream(StreamInterface $fileStream, KeyInterface $key = null, $raw = false) { $config = self::getConfig(Halite::HALITE_VERSION_FILE, 'checksum'); if ($key) { if (!$key instanceof AuthenticationKey) { throw new \ParagonIE\Halite\Alerts\InvalidKey('Argument 2: Expected an instance of AuthenticationKey'); } $state = \Sodium\crypto_generichash_init($key->get(), $config->HASH_LEN); } else { $state = \Sodium\crypto_generichash_init(null, $config->HASH_LEN); } $size = $fileStream->getSize(); while ($fileStream->remainingBytes() > 0) { $read = $fileStream->readBytes($fileStream->getPos() + $config->BUFFER > $size ? $size - $fileStream->getPos() : $config->BUFFER); \Sodium\crypto_generichash_update($state, $read); } if ($raw) { return \Sodium\crypto_generichash_final($state, $config->HASH_LEN); } return \Sodium\bin2hex(\Sodium\crypto_generichash_final($state, $config->HASH_LEN)); }
/** * Calculate a BLAKE2b hash of a file * * @return string */ public function getHash() { $init = $this->pos; \fseek($this->fp, 0, SEEK_SET); // Create a hash context: $h = \Sodium\crypto_generichash_init(null, \Sodium\CRYPTO_GENERICHASH_BYTES_MAX); for ($i = 0; $i < $this->stat['size']; $i += self::CHUNK) { if ($i + self::CHUNK > $this->stat['size']) { $c = \fread($this->fp, $this->stat['size'] - $i); } else { $c = \fread($this->fp, self::CHUNK); } \Sodium\crypto_generichash_update($h, $c); } // Reset the file pointer's internal cursor to where it was: \fseek($this->fp, $init, SEEK_SET); return \Sodium\crypto_generichash_final($h); }
/** * 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; }