$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(
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.');
throw new TypeError('Argument 4 must be a string, ' . gettype($key) . ' given.');
if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_NONCEBYTES) {
throw new TypeError('Argument 4 must be CRYPTO_SECRETBOX_NONCEBYTES bytes');
if (self::strlen($key) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_KEYBYTES) {
throw new TypeError('Argument 4 must be CRYPTO_SECRETBOXBOX_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_decrypt($ifp, $ofp, $size, $nonce, $key);
ParagonIE_Sodium_Compat::memzero($key);
} catch (SodiumException $ex) {
/** @psalm-suppress PossiblyUndefinedVariable */
* Sign a file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_sign_detached(), but produces
* @param string $filePath Absolute path to a file on the filesystem
* @param string $secretKey Secret signing key
* @return string Ed25519 signature
* @throws SodiumException
public static function sign(
if (!is_string($filePath)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($filePath) . ' given.');
if (!is_string($secretKey)) {
throw new TypeError('Argument 2 must be a string, ' . gettype($secretKey) . ' given.');
if (self::strlen($secretKey) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_SECRETKEYBYTES) {
throw new TypeError('Argument 2 must be CRYPTO_SIGN_SECRETKEYBYTES bytes');
if (PHP_INT_SIZE === 4) {
return self::sign_core32($filePath, $secretKey);
$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');
$az = hash('sha512', self::substr($secretKey, 0, 32), true);
$az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
$az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);
$hs = hash_init('sha512');
self::hash_update($hs, self::substr($az, 32, 32));
$hs = self::updateHashWithFile($hs, $fp, $size);
/** @var string $nonceHash */
$nonceHash = hash_final($hs, true);
$pk = self::substr($secretKey, 32, 32);
/** @var string $nonce */
$nonce = ParagonIE_Sodium_Core_Ed25519::sc_reduce($nonceHash) . self::substr($nonceHash, 32);
$sig = ParagonIE_Sodium_Core_Ed25519::ge_p3_tobytes(
ParagonIE_Sodium_Core_Ed25519::ge_scalarmult_base($nonce)
$hs = hash_init('sha512');
self::hash_update($hs, self::substr($sig, 0, 32));
self::hash_update($hs, self::substr($pk, 0, 32));
$hs = self::updateHashWithFile($hs, $fp, $size);
/** @var string $hramHash */
$hramHash = hash_final($hs, true);
$hram = ParagonIE_Sodium_Core_Ed25519::sc_reduce($hramHash);
/** @var string $sigAfter */
$sigAfter = ParagonIE_Sodium_Core_Ed25519::sc_muladd($hram, $az, $nonce);
$sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);
ParagonIE_Sodium_Compat::memzero($az);
} catch (SodiumException $ex) {
* Verify a file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_sign_verify_detached(), but
* produces the same result.
* @param string $sig Ed25519 signature
* @param string $filePath Absolute path to a file on the filesystem
* @param string $publicKey Signing public key
* @throws SodiumException
public static function verify(
throw new TypeError('Argument 1 must be a string, ' . gettype($sig) . ' given.');
if (!is_string($filePath)) {
throw new TypeError('Argument 2 must be a string, ' . gettype($filePath) . ' given.');
if (!is_string($publicKey)) {
throw new TypeError('Argument 3 must be a string, ' . gettype($publicKey) . ' given.');
if (self::strlen($sig) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_BYTES) {
throw new TypeError('Argument 1 must be CRYPTO_SIGN_BYTES bytes');
if (self::strlen($publicKey) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_PUBLICKEYBYTES) {
throw new TypeError('Argument 3 must be CRYPTO_SIGN_PUBLICKEYBYTES bytes');
if (self::strlen($sig) < 64) {
throw new SodiumException('Signature is too short');
if (PHP_INT_SIZE === 4) {
return self::verify_core32($sig, $filePath, $publicKey);
(ParagonIE_Sodium_Core_Ed25519::chrToInt($sig[63]) & 240)
ParagonIE_Sodium_Core_Ed25519::check_S_lt_L(self::substr($sig, 32, 32))
throw new SodiumException('S < L - Invalid signature');
if (ParagonIE_Sodium_Core_Ed25519::small_order($sig)) {
throw new SodiumException('Signature is on too small of an order');
if ((self::chrToInt($sig[63]) & 224) !== 0) {
throw new SodiumException('Invalid signature');
for ($i = 0; $i < 32; ++$i) {
$d |= self::chrToInt($publicKey[$i]);
throw new SodiumException('All zero public key');
$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');
/** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
$orig = ParagonIE_Sodium_Compat::$fastMult;
// Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
ParagonIE_Sodium_Compat::$fastMult = true;
/** @var ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A */
$A = ParagonIE_Sodium_Core_Ed25519::ge_frombytes_negate_vartime($publicKey);
$hs = hash_init('sha512');
self::hash_update($hs, self::substr($sig, 0, 32));
self::hash_update($hs, self::substr($publicKey, 0, 32));
$hs = self::updateHashWithFile($hs, $fp, $size);
/** @var string $hDigest */
$hDigest = hash_final($hs, true);
$h = ParagonIE_Sodium_Core_Ed25519::sc_reduce($hDigest) . self::substr($hDigest, 32);
/** @var ParagonIE_Sodium_Core_Curve25519_Ge_P2 $R */
$R = ParagonIE_Sodium_Core_Ed25519::ge_double_scalarmult_vartime(
/** @var string $rcheck */
$rcheck = ParagonIE_Sodium_Core_Ed25519::ge_tobytes($R);
// Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
ParagonIE_Sodium_Compat::$fastMult = $orig;
return self::verify_32($rcheck, self::substr($sig, 0, 32));
* @param string $boxKeypair
* @throws SodiumException
protected static function box_encrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
if (PHP_INT_SIZE === 4) {
return self::secretbox_encrypt(
ParagonIE_Sodium_Crypto32::box_beforenm(
ParagonIE_Sodium_Crypto32::box_secretkey($boxKeypair),
ParagonIE_Sodium_Crypto32::box_publickey($boxKeypair)
return self::secretbox_encrypt(
ParagonIE_Sodium_Crypto::box_beforenm(
ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
* @param string $boxKeypair
* @throws SodiumException
protected static function box_decrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
if (PHP_INT_SIZE === 4) {
return self::secretbox_decrypt(
ParagonIE_Sodium_Crypto32::box_beforenm(
ParagonIE_Sodium_Crypto32::box_secretkey($boxKeypair),
ParagonIE_Sodium_Crypto32::box_publickey($boxKeypair)
return self::secretbox_decrypt(
ParagonIE_Sodium_Crypto::box_beforenm(
ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
* @throws SodiumException
protected static function secretbox_encrypt($ifp, $ofp, $mlen, $nonce, $key)
if (PHP_INT_SIZE === 4) {
return self::secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $key);
$plaintext = fread($ifp, 32);
if (!is_string($plaintext)) {
throw new SodiumException('Could not read input file');
$first32 = self::ftell($ifp);
/** @var string $subkey */
$subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);
/** @var string $realNonce */
$realNonce = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
/** @var string $block0 */
$block0 = str_repeat("\x00", 32);
/** @var int $mlen - Length of the plaintext message */
if ($mlen0 > 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES) {
$mlen0 = 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES;
$block0 .= ParagonIE_Sodium_Core_Util::substr($plaintext, 0, $mlen0);
/** @var string $block0 */
$block0 = ParagonIE_Sodium_Core_Salsa20::salsa20_xor(
$state = new ParagonIE_Sodium_Core_Poly1305_State(
ParagonIE_Sodium_Core_Util::substr(
ParagonIE_Sodium_Crypto::onetimeauth_poly1305_KEYBYTES
// Pre-write 16 blank bytes for the Poly1305 tag
$start = self::ftell($ofp);
fwrite($ofp, str_repeat("\x00", 16));
$cBlock = ParagonIE_Sodium_Core_Util::substr(
ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES
$incr = self::BUFFER_SIZE >> 6;
* Set the cursor to the end of the first half-block. All future bytes will
* generated from salsa20_xor_ic, starting from 1 (second block).
fseek($ifp, $first32, SEEK_SET);
$blockSize = $mlen > self::BUFFER_SIZE
$plaintext = fread($ifp, $blockSize);
if (!is_string($plaintext)) {
throw new SodiumException('Could not read input file');
$cBlock = ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
fwrite($ofp, $cBlock, $blockSize);
ParagonIE_Sodium_Compat::memzero($block0);
ParagonIE_Sodium_Compat::memzero($subkey);
} catch (SodiumException $ex) {
$end = self::ftell($ofp);
* Write the Poly1305 authentication tag that provides integrity
* over the ciphertext (encrypt-then-MAC)
fseek($ofp, $start, SEEK_SET);
fwrite($ofp, $state->finish(), ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_MACBYTES);
fseek($ofp, $end, SEEK_SET);
* @throws SodiumException