if (class_exists('ParagonIE_Sodium_File', false)) {
* Class ParagonIE_Sodium_File
class ParagonIE_Sodium_File extends ParagonIE_Sodium_Core_Util
/* PHP's default buffer size is 8192 for fread()/fwrite(). */
const BUFFER_SIZE = 8192;
* Box a file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_box(), but produces
* @param string $inputFile Absolute path to a file on the filesystem
* @param string $outputFile Absolute path to a file on the filesystem
* @param string $nonce Number to be used only once
* @param string $keyPair ECDH secret key and ECDH public key concatenated
* @throws SodiumException
public static function box($inputFile, $outputFile, $nonce, $keyPair)
if (!is_string($inputFile)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
if (!is_string($outputFile)) {
throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
if (!is_string($nonce)) {
throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
if (!is_string($keyPair)) {
throw new TypeError('Argument 4 must be a string, ' . gettype($keyPair) . ' given.');
if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_NONCEBYTES) {
throw new TypeError('Argument 3 must be CRYPTO_BOX_NONCEBYTES bytes');
if (self::strlen($keyPair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
throw new TypeError('Argument 4 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
$size = filesize($inputFile);
throw new SodiumException('Could not obtain the file size');
/** @var resource $ifp */
$ifp = fopen($inputFile, 'rb');
if (!is_resource($ifp)) {
throw new SodiumException('Could not open input file for reading');
/** @var resource $ofp */
$ofp = fopen($outputFile, 'wb');
if (!is_resource($ofp)) {
throw new SodiumException('Could not open output file for writing');
$res = self::box_encrypt($ifp, $ofp, $size, $nonce, $keyPair);
* Open a boxed file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_box_open(), but produces
* Warning: Does not protect against TOCTOU attacks. You should
* just load the file into memory and use crypto_box_open() if
* you are worried about those.
* @param string $inputFile
* @param string $outputFile
* @throws SodiumException
public static function box_open($inputFile, $outputFile, $nonce, $keypair)
if (!is_string($inputFile)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
if (!is_string($outputFile)) {
throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
if (!is_string($nonce)) {
throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
if (!is_string($keypair)) {
throw new TypeError('Argument 4 must be a string, ' . gettype($keypair) . ' given.');
if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_NONCEBYTES) {
throw new TypeError('Argument 4 must be CRYPTO_BOX_NONCEBYTES bytes');
if (self::strlen($keypair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
throw new TypeError('Argument 4 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
$size = filesize($inputFile);
throw new SodiumException('Could not obtain the file size');
/** @var resource $ifp */
$ifp = fopen($inputFile, 'rb');
if (!is_resource($ifp)) {
throw new SodiumException('Could not open input file for reading');
/** @var resource $ofp */
$ofp = fopen($outputFile, 'wb');
if (!is_resource($ofp)) {
throw new SodiumException('Could not open output file for writing');
$res = self::box_decrypt($ifp, $ofp, $size, $nonce, $keypair);
ParagonIE_Sodium_Compat::memzero($nonce);
ParagonIE_Sodium_Compat::memzero($ephKeypair);
} catch (SodiumException $ex) {
if (isset($ephKeypair)) {
* Seal a file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_box_seal(), but produces
* @param string $inputFile Absolute path to a file on the filesystem
* @param string $outputFile Absolute path to a file on the filesystem
* @param string $publicKey ECDH public key
* @throws SodiumException
public static function box_seal($inputFile, $outputFile, $publicKey)
if (!is_string($inputFile)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
if (!is_string($outputFile)) {
throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
if (!is_string($publicKey)) {
throw new TypeError('Argument 3 must be a string, ' . gettype($publicKey) . ' given.');
if (self::strlen($publicKey) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
throw new TypeError('Argument 3 must be CRYPTO_BOX_PUBLICKEYBYTES bytes');
$size = filesize($inputFile);
throw new SodiumException('Could not obtain the file size');
/** @var resource $ifp */
$ifp = fopen($inputFile, 'rb');
if (!is_resource($ifp)) {
throw new SodiumException('Could not open input file for reading');
/** @var resource $ofp */
$ofp = fopen($outputFile, 'wb');
if (!is_resource($ofp)) {
throw new SodiumException('Could not open output file for writing');
/** @var string $ephKeypair */
$ephKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair();
/** @var string $msgKeypair */
$msgKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey(
ParagonIE_Sodium_Compat::crypto_box_secretkey($ephKeypair),
/** @var string $ephemeralPK */
$ephemeralPK = ParagonIE_Sodium_Compat::crypto_box_publickey($ephKeypair);
/** @var string $nonce */
$nonce = ParagonIE_Sodium_Compat::crypto_generichash(
$ephemeralPK . $publicKey,
/** @var int $firstWrite */
ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES
if (!is_int($firstWrite)) {
ParagonIE_Sodium_Compat::memzero($ephKeypair);
throw new SodiumException('Could not write to output file');
if ($firstWrite !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
ParagonIE_Sodium_Compat::memzero($ephKeypair);
throw new SodiumException('Error writing public key to output file');
$res = self::box_encrypt($ifp, $ofp, $size, $nonce, $msgKeypair);
ParagonIE_Sodium_Compat::memzero($nonce);
ParagonIE_Sodium_Compat::memzero($ephKeypair);
} catch (SodiumException $ex) {
/** @psalm-suppress PossiblyUndefinedVariable */
* Open a sealed file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_box_seal_open(), but produces
* Warning: Does not protect against TOCTOU attacks. You should
* just load the file into memory and use crypto_box_seal_open() if
* you are worried about those.
* @param string $inputFile
* @param string $outputFile
* @param string $ecdhKeypair
* @throws SodiumException
public static function box_seal_open($inputFile, $outputFile, $ecdhKeypair)
if (!is_string($inputFile)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
if (!is_string($outputFile)) {
throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
if (!is_string($ecdhKeypair)) {
throw new TypeError('Argument 3 must be a string, ' . gettype($ecdhKeypair) . ' given.');
if (self::strlen($ecdhKeypair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
throw new TypeError('Argument 3 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
$publicKey = ParagonIE_Sodium_Compat::crypto_box_publickey($ecdhKeypair);
$size = filesize($inputFile);
throw new SodiumException('Could not obtain the file size');
/** @var resource $ifp */
$ifp = fopen($inputFile, 'rb');
if (!is_resource($ifp)) {
throw new SodiumException('Could not open input file for reading');
/** @var resource $ofp */
$ofp = fopen($outputFile, 'wb');
if (!is_resource($ofp)) {
throw new SodiumException('Could not open output file for writing');
$ephemeralPK = fread($ifp, ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES);
if (!is_string($ephemeralPK)) {
throw new SodiumException('Could not read input file');
if (self::strlen($ephemeralPK) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
throw new SodiumException('Could not read public key from sealed file');
$nonce = ParagonIE_Sodium_Compat::crypto_generichash(
$ephemeralPK . $publicKey,
$msgKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey(
ParagonIE_Sodium_Compat::crypto_box_secretkey($ecdhKeypair),
$res = self::box_decrypt($ifp, $ofp, $size, $nonce, $msgKeypair);
ParagonIE_Sodium_Compat::memzero($nonce);
ParagonIE_Sodium_Compat::memzero($ephKeypair);
} catch (SodiumException $ex) {
if (isset($ephKeypair)) {
* Calculate the BLAKE2b hash of a file.
* @param string $filePath Absolute path to a file on the filesystem
* @param string|null $key BLAKE2b key
* @param int $outputLength Length of hash output
* @return string BLAKE2b hash
* @throws SodiumException
* @psalm-suppress FailedTypeResolution
public static function generichash($filePath, $key = '', $outputLength = 32)
if (!is_string($filePath)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($filePath) . ' given.');
throw new TypeError('Argument 2 must be a string, ' . gettype($key) . ' given.');
if (!is_int($outputLength)) {
if (!is_numeric($outputLength)) {
throw new TypeError('Argument 3 must be an integer, ' . gettype($outputLength) . ' given.');
$outputLength = (int) $outputLength;
if (self::strlen($key) < ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_KEYBYTES_MIN) {
throw new TypeError('Argument 2 must be at least CRYPTO_GENERICHASH_KEYBYTES_MIN bytes');
if (self::strlen($key) > ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_KEYBYTES_MAX) {
throw new TypeError('Argument 2 must be at most CRYPTO_GENERICHASH_KEYBYTES_MAX bytes');
if ($outputLength < ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_BYTES_MIN) {
throw new SodiumException('Argument 3 must be at least CRYPTO_GENERICHASH_BYTES_MIN');
if ($outputLength > ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_BYTES_MAX) {
throw new SodiumException('Argument 3 must be at least CRYPTO_GENERICHASH_BYTES_MAX');
$size = filesize($filePath);
throw new SodiumException('Could not obtain the file size');
$fp = fopen($filePath, 'rb');
throw new SodiumException('Could not open input file for reading');
$ctx = ParagonIE_Sodium_Compat::crypto_generichash_init($key, $outputLength);
$read = fread($fp, $blockSize);
throw new SodiumException('Could not read input file');
ParagonIE_Sodium_Compat::crypto_generichash_update($ctx, $read);
return ParagonIE_Sodium_Compat::crypto_generichash_final($ctx, $outputLength);
* Encrypt a file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_secretbox(), but produces
* @param string $inputFile Absolute path to a file on the filesystem
* @param string $outputFile Absolute path to a file on the filesystem
* @param string $nonce Number to be used only once
* @param string $key Encryption key
* @throws SodiumException
public static function secretbox($inputFile, $outputFile, $nonce, $key)
if (!is_string($inputFile)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given..');
if (!is_string($outputFile)) {
throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
if (!is_string($nonce)) {
throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_NONCEBYTES) {
throw new TypeError('Argument 3 must be CRYPTO_SECRETBOX_NONCEBYTES bytes');
throw new TypeError('Argument 4 must be a string, ' . gettype($key) . ' given.');
if (self::strlen($key) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_KEYBYTES) {
throw new TypeError('Argument 4 must be CRYPTO_SECRETBOX_KEYBYTES bytes');
$size = filesize($inputFile);
throw new SodiumException('Could not obtain the file size');
/** @var resource $ifp */
$ifp = fopen($inputFile, 'rb');
if (!is_resource($ifp)) {
throw new SodiumException('Could not open input file for reading');
/** @var resource $ofp */
$ofp = fopen($outputFile, 'wb');
if (!is_resource($ofp)) {
throw new SodiumException('Could not open output file for writing');
$res = self::secretbox_encrypt($ifp, $ofp, $size, $nonce, $key);
* Seal a file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_secretbox_open(), but produces
* Warning: Does not protect against TOCTOU attacks. You should
* just load the file into memory and use crypto_secretbox_open() if
* you are worried about those.
* @param string $inputFile
* @param string $outputFile
* @throws SodiumException
public static function secretbox_open($inputFile, $outputFile, $nonce, $key)
if (!is_string($inputFile)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');