/** * Unseal a (file handle) * * @param ReadOnlyFile $input * @param MutableFile $output * @param EncryptionSecretKey $secretkey * * @return bool * @throws CryptoException\CannotPerformOperation * @throws CryptoException\InvalidMessage */ protected static function unsealData(ReadOnlyFile $input, MutableFile $output, EncryptionSecretKey $secretkey) : bool { $secret_key = $secretkey->getRawKeyMaterial(); $public_key = \Sodium\crypto_box_publickey_from_secretkey($secret_key); if ($input->getSize() < Halite::VERSION_TAG_LEN) { throw new CryptoException\InvalidMessage("File is too small to have been encrypted by Halite."); } // Parse the header, ensuring we get 4 bytes $header = $input->readBytes(Halite::VERSION_TAG_LEN); // Load the config $config = self::getConfig($header, 'seal'); if ($input->getSize() < $config->SHORTEST_CIPHERTEXT_LENGTH) { throw new CryptoException\InvalidMessage("File is too small to have been encrypted by Halite."); } // Let's grab the public key and salt $eph_public = $input->readBytes($config->PUBLICKEY_BYTES); $hkdfsalt = $input->readBytes($config->HKDF_SALT_LEN); $nonce = \Sodium\crypto_generichash($eph_public . $public_key, '', \Sodium\CRYPTO_STREAM_NONCEBYTES); $ephemeral = new EncryptionPublicKey($eph_public); $key = AsymmetricCrypto::getSharedSecret($secretkey, $ephemeral, true); list($encKey, $authKey) = self::splitKeys($key, $hkdfsalt, $config); // We no longer need the original key after we split it unset($key); if ($config->USE_BLAKE2B) { $mac = \Sodium\crypto_generichash_init($authKey); \Sodium\crypto_generichash_update($mac, $header); \Sodium\crypto_generichash_update($mac, $eph_public); \Sodium\crypto_generichash_update($mac, $hkdfsalt); $old_macs = self::streamVerify($input, '' . $mac, $config); } else { $mac = \hash_init('sha256', HASH_HMAC, $authKey); \hash_update($mac, $header); \hash_update($mac, $eph_public); \hash_update($mac, $hkdfsalt); // This will throw an exception if it fails. $old_macs = self::streamVerify($input, \hash_copy($mac), $config); } $ret = self::streamDecrypt($input, $output, new EncryptionKey($encKey), $nonce, $mac, $config, $old_macs); unset($encKey); unset($authKey); unset($nonce); unset($mac); unset($config); unset($old_macs); return $ret; }
/** * 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); }
/** * Unseal the contents of a file. * * @param ReadOnlyFile $input * @param MutableFile $output * @param EncryptionSecretKey $secretKey * @return bool * @throws CannotPerformOperation * @throws InvalidMessage */ protected static function unsealData(ReadOnlyFile $input, MutableFile $output, EncryptionSecretKey $secretKey) : bool { $publicKey = $secretKey->derivePublicKey(); // Is the file at least as long as a header? if ($input->getSize() < Halite::VERSION_TAG_LEN) { throw new InvalidMessage("Input file is too small to have been encrypted by Halite."); } // Parse the header, ensuring we get 4 bytes $header = $input->readBytes(Halite::VERSION_TAG_LEN); // Load the config $config = self::getConfig($header, 'seal'); if ($input->getSize() < $config->SHORTEST_CIPHERTEXT_LENGTH) { throw new InvalidMessage("Input file is too small to have been encrypted by Halite."); } // Let's grab the public key and salt $ephPublic = $input->readBytes($config->PUBLICKEY_BYTES); $hkdfSalt = $input->readBytes($config->HKDF_SALT_LEN); // Generate the same nonce, as per sealData() $nonce = \Sodium\crypto_generichash($ephPublic . $publicKey->getRawKeyMaterial(), '', \Sodium\CRYPTO_STREAM_NONCEBYTES); // Create a key object out of the public key: $ephemeral = new EncryptionPublicKey(new HiddenString($ephPublic)); $key = AsymmetricCrypto::getSharedSecret($secretKey, $ephemeral, true); unset($ephemeral); list($encKey, $authKey) = self::splitKeys($key, $hkdfSalt, $config); // We no longer need the original key after we split it unset($key); $mac = \Sodium\crypto_generichash_init($authKey); \Sodium\crypto_generichash_update($mac, $header); \Sodium\crypto_generichash_update($mac, $ephPublic); \Sodium\crypto_generichash_update($mac, $hkdfSalt); $oldMACs = self::streamVerify($input, Util::safeStrcpy($mac), $config); // We no longer need this: \Sodium\memzero($hkdfSalt); $ret = self::streamDecrypt($input, $output, new EncryptionKey(new HiddenString($encKey)), $nonce, $mac, $config, $oldMACs); unset($encKey); unset($authKey); unset($nonce); unset($mac); unset($config); unset($oldMACs); return $ret; }