ru-se.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

File.php (53437B)


      1 <?php
      2 
      3 if (class_exists('ParagonIE_Sodium_File', false)) {
      4     return;
      5 }
      6 /**
      7  * Class ParagonIE_Sodium_File
      8  */
      9 class ParagonIE_Sodium_File extends ParagonIE_Sodium_Core_Util
     10 {
     11     /* PHP's default buffer size is 8192 for fread()/fwrite(). */
     12     const BUFFER_SIZE = 8192;
     13 
     14     /**
     15      * Box a file (rather than a string). Uses less memory than
     16      * ParagonIE_Sodium_Compat::crypto_box(), but produces
     17      * the same result.
     18      *
     19      * @param string $inputFile  Absolute path to a file on the filesystem
     20      * @param string $outputFile Absolute path to a file on the filesystem
     21      * @param string $nonce      Number to be used only once
     22      * @param string $keyPair    ECDH secret key and ECDH public key concatenated
     23      *
     24      * @return bool
     25      * @throws SodiumException
     26      * @throws TypeError
     27      */
     28     public static function box($inputFile, $outputFile, $nonce, $keyPair)
     29     {
     30         /* Type checks: */
     31         if (!is_string($inputFile)) {
     32             throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
     33         }
     34         if (!is_string($outputFile)) {
     35             throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
     36         }
     37         if (!is_string($nonce)) {
     38             throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
     39         }
     40 
     41         /* Input validation: */
     42         if (!is_string($keyPair)) {
     43             throw new TypeError('Argument 4 must be a string, ' . gettype($keyPair) . ' given.');
     44         }
     45         if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_NONCEBYTES) {
     46             throw new TypeError('Argument 3 must be CRYPTO_BOX_NONCEBYTES bytes');
     47         }
     48         if (self::strlen($keyPair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
     49             throw new TypeError('Argument 4 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
     50         }
     51 
     52         /** @var int $size */
     53         $size = filesize($inputFile);
     54         if (!is_int($size)) {
     55             throw new SodiumException('Could not obtain the file size');
     56         }
     57 
     58         /** @var resource $ifp */
     59         $ifp = fopen($inputFile, 'rb');
     60         if (!is_resource($ifp)) {
     61             throw new SodiumException('Could not open input file for reading');
     62         }
     63 
     64         /** @var resource $ofp */
     65         $ofp = fopen($outputFile, 'wb');
     66         if (!is_resource($ofp)) {
     67             fclose($ifp);
     68             throw new SodiumException('Could not open output file for writing');
     69         }
     70 
     71         $res = self::box_encrypt($ifp, $ofp, $size, $nonce, $keyPair);
     72         fclose($ifp);
     73         fclose($ofp);
     74         return $res;
     75     }
     76 
     77     /**
     78      * Open a boxed file (rather than a string). Uses less memory than
     79      * ParagonIE_Sodium_Compat::crypto_box_open(), but produces
     80      * the same result.
     81      *
     82      * Warning: Does not protect against TOCTOU attacks. You should
     83      * just load the file into memory and use crypto_box_open() if
     84      * you are worried about those.
     85      *
     86      * @param string $inputFile
     87      * @param string $outputFile
     88      * @param string $nonce
     89      * @param string $keypair
     90      * @return bool
     91      * @throws SodiumException
     92      * @throws TypeError
     93      */
     94     public static function box_open($inputFile, $outputFile, $nonce, $keypair)
     95     {
     96         /* Type checks: */
     97         if (!is_string($inputFile)) {
     98             throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
     99         }
    100         if (!is_string($outputFile)) {
    101             throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
    102         }
    103         if (!is_string($nonce)) {
    104             throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
    105         }
    106         if (!is_string($keypair)) {
    107             throw new TypeError('Argument 4 must be a string, ' . gettype($keypair) . ' given.');
    108         }
    109 
    110         /* Input validation: */
    111         if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_NONCEBYTES) {
    112             throw new TypeError('Argument 4 must be CRYPTO_BOX_NONCEBYTES bytes');
    113         }
    114         if (self::strlen($keypair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
    115             throw new TypeError('Argument 4 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
    116         }
    117 
    118         /** @var int $size */
    119         $size = filesize($inputFile);
    120         if (!is_int($size)) {
    121             throw new SodiumException('Could not obtain the file size');
    122         }
    123 
    124         /** @var resource $ifp */
    125         $ifp = fopen($inputFile, 'rb');
    126         if (!is_resource($ifp)) {
    127             throw new SodiumException('Could not open input file for reading');
    128         }
    129 
    130         /** @var resource $ofp */
    131         $ofp = fopen($outputFile, 'wb');
    132         if (!is_resource($ofp)) {
    133             fclose($ifp);
    134             throw new SodiumException('Could not open output file for writing');
    135         }
    136 
    137         $res = self::box_decrypt($ifp, $ofp, $size, $nonce, $keypair);
    138         fclose($ifp);
    139         fclose($ofp);
    140         try {
    141             ParagonIE_Sodium_Compat::memzero($nonce);
    142             ParagonIE_Sodium_Compat::memzero($ephKeypair);
    143         } catch (SodiumException $ex) {
    144             if (isset($ephKeypair)) {
    145                 unset($ephKeypair);
    146             }
    147         }
    148         return $res;
    149     }
    150 
    151     /**
    152      * Seal a file (rather than a string). Uses less memory than
    153      * ParagonIE_Sodium_Compat::crypto_box_seal(), but produces
    154      * the same result.
    155      *
    156      * @param string $inputFile  Absolute path to a file on the filesystem
    157      * @param string $outputFile Absolute path to a file on the filesystem
    158      * @param string $publicKey  ECDH public key
    159      *
    160      * @return bool
    161      * @throws SodiumException
    162      * @throws TypeError
    163      */
    164     public static function box_seal($inputFile, $outputFile, $publicKey)
    165     {
    166         /* Type checks: */
    167         if (!is_string($inputFile)) {
    168             throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
    169         }
    170         if (!is_string($outputFile)) {
    171             throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
    172         }
    173         if (!is_string($publicKey)) {
    174             throw new TypeError('Argument 3 must be a string, ' . gettype($publicKey) . ' given.');
    175         }
    176 
    177         /* Input validation: */
    178         if (self::strlen($publicKey) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
    179             throw new TypeError('Argument 3 must be CRYPTO_BOX_PUBLICKEYBYTES bytes');
    180         }
    181 
    182         /** @var int $size */
    183         $size = filesize($inputFile);
    184         if (!is_int($size)) {
    185             throw new SodiumException('Could not obtain the file size');
    186         }
    187 
    188         /** @var resource $ifp */
    189         $ifp = fopen($inputFile, 'rb');
    190         if (!is_resource($ifp)) {
    191             throw new SodiumException('Could not open input file for reading');
    192         }
    193 
    194         /** @var resource $ofp */
    195         $ofp = fopen($outputFile, 'wb');
    196         if (!is_resource($ofp)) {
    197             fclose($ifp);
    198             throw new SodiumException('Could not open output file for writing');
    199         }
    200 
    201         /** @var string $ephKeypair */
    202         $ephKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair();
    203 
    204         /** @var string $msgKeypair */
    205         $msgKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey(
    206             ParagonIE_Sodium_Compat::crypto_box_secretkey($ephKeypair),
    207             $publicKey
    208         );
    209 
    210         /** @var string $ephemeralPK */
    211         $ephemeralPK = ParagonIE_Sodium_Compat::crypto_box_publickey($ephKeypair);
    212 
    213         /** @var string $nonce */
    214         $nonce = ParagonIE_Sodium_Compat::crypto_generichash(
    215             $ephemeralPK . $publicKey,
    216             '',
    217             24
    218         );
    219 
    220         /** @var int $firstWrite */
    221         $firstWrite = fwrite(
    222             $ofp,
    223             $ephemeralPK,
    224             ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES
    225         );
    226         if (!is_int($firstWrite)) {
    227             fclose($ifp);
    228             fclose($ofp);
    229             ParagonIE_Sodium_Compat::memzero($ephKeypair);
    230             throw new SodiumException('Could not write to output file');
    231         }
    232         if ($firstWrite !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
    233             ParagonIE_Sodium_Compat::memzero($ephKeypair);
    234             fclose($ifp);
    235             fclose($ofp);
    236             throw new SodiumException('Error writing public key to output file');
    237         }
    238 
    239         $res = self::box_encrypt($ifp, $ofp, $size, $nonce, $msgKeypair);
    240         fclose($ifp);
    241         fclose($ofp);
    242         try {
    243             ParagonIE_Sodium_Compat::memzero($nonce);
    244             ParagonIE_Sodium_Compat::memzero($ephKeypair);
    245         } catch (SodiumException $ex) {
    246             /** @psalm-suppress PossiblyUndefinedVariable */
    247             unset($ephKeypair);
    248         }
    249         return $res;
    250     }
    251 
    252     /**
    253      * Open a sealed file (rather than a string). Uses less memory than
    254      * ParagonIE_Sodium_Compat::crypto_box_seal_open(), but produces
    255      * the same result.
    256      *
    257      * Warning: Does not protect against TOCTOU attacks. You should
    258      * just load the file into memory and use crypto_box_seal_open() if
    259      * you are worried about those.
    260      *
    261      * @param string $inputFile
    262      * @param string $outputFile
    263      * @param string $ecdhKeypair
    264      * @return bool
    265      * @throws SodiumException
    266      * @throws TypeError
    267      */
    268     public static function box_seal_open($inputFile, $outputFile, $ecdhKeypair)
    269     {
    270         /* Type checks: */
    271         if (!is_string($inputFile)) {
    272             throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
    273         }
    274         if (!is_string($outputFile)) {
    275             throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
    276         }
    277         if (!is_string($ecdhKeypair)) {
    278             throw new TypeError('Argument 3 must be a string, ' . gettype($ecdhKeypair) . ' given.');
    279         }
    280 
    281         /* Input validation: */
    282         if (self::strlen($ecdhKeypair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
    283             throw new TypeError('Argument 3 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
    284         }
    285 
    286         $publicKey = ParagonIE_Sodium_Compat::crypto_box_publickey($ecdhKeypair);
    287 
    288         /** @var int $size */
    289         $size = filesize($inputFile);
    290         if (!is_int($size)) {
    291             throw new SodiumException('Could not obtain the file size');
    292         }
    293 
    294         /** @var resource $ifp */
    295         $ifp = fopen($inputFile, 'rb');
    296         if (!is_resource($ifp)) {
    297             throw new SodiumException('Could not open input file for reading');
    298         }
    299 
    300         /** @var resource $ofp */
    301         $ofp = fopen($outputFile, 'wb');
    302         if (!is_resource($ofp)) {
    303             fclose($ifp);
    304             throw new SodiumException('Could not open output file for writing');
    305         }
    306 
    307         $ephemeralPK = fread($ifp, ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES);
    308         if (!is_string($ephemeralPK)) {
    309             throw new SodiumException('Could not read input file');
    310         }
    311         if (self::strlen($ephemeralPK) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
    312             fclose($ifp);
    313             fclose($ofp);
    314             throw new SodiumException('Could not read public key from sealed file');
    315         }
    316 
    317         $nonce = ParagonIE_Sodium_Compat::crypto_generichash(
    318             $ephemeralPK . $publicKey,
    319             '',
    320             24
    321         );
    322         $msgKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey(
    323             ParagonIE_Sodium_Compat::crypto_box_secretkey($ecdhKeypair),
    324             $ephemeralPK
    325         );
    326 
    327         $res = self::box_decrypt($ifp, $ofp, $size, $nonce, $msgKeypair);
    328         fclose($ifp);
    329         fclose($ofp);
    330         try {
    331             ParagonIE_Sodium_Compat::memzero($nonce);
    332             ParagonIE_Sodium_Compat::memzero($ephKeypair);
    333         } catch (SodiumException $ex) {
    334             if (isset($ephKeypair)) {
    335                 unset($ephKeypair);
    336             }
    337         }
    338         return $res;
    339     }
    340 
    341     /**
    342      * Calculate the BLAKE2b hash of a file.
    343      *
    344      * @param string      $filePath     Absolute path to a file on the filesystem
    345      * @param string|null $key          BLAKE2b key
    346      * @param int         $outputLength Length of hash output
    347      *
    348      * @return string                   BLAKE2b hash
    349      * @throws SodiumException
    350      * @throws TypeError
    351      * @psalm-suppress FailedTypeResolution
    352      */
    353     public static function generichash($filePath, $key = '', $outputLength = 32)
    354     {
    355         /* Type checks: */
    356         if (!is_string($filePath)) {
    357             throw new TypeError('Argument 1 must be a string, ' . gettype($filePath) . ' given.');
    358         }
    359         if (!is_string($key)) {
    360             if (is_null($key)) {
    361                 $key = '';
    362             } else {
    363                 throw new TypeError('Argument 2 must be a string, ' . gettype($key) . ' given.');
    364             }
    365         }
    366         if (!is_int($outputLength)) {
    367             if (!is_numeric($outputLength)) {
    368                 throw new TypeError('Argument 3 must be an integer, ' . gettype($outputLength) . ' given.');
    369             }
    370             $outputLength = (int) $outputLength;
    371         }
    372 
    373         /* Input validation: */
    374         if (!empty($key)) {
    375             if (self::strlen($key) < ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_KEYBYTES_MIN) {
    376                 throw new TypeError('Argument 2 must be at least CRYPTO_GENERICHASH_KEYBYTES_MIN bytes');
    377             }
    378             if (self::strlen($key) > ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_KEYBYTES_MAX) {
    379                 throw new TypeError('Argument 2 must be at most CRYPTO_GENERICHASH_KEYBYTES_MAX bytes');
    380             }
    381         }
    382         if ($outputLength < ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_BYTES_MIN) {
    383             throw new SodiumException('Argument 3 must be at least CRYPTO_GENERICHASH_BYTES_MIN');
    384         }
    385         if ($outputLength > ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_BYTES_MAX) {
    386             throw new SodiumException('Argument 3 must be at least CRYPTO_GENERICHASH_BYTES_MAX');
    387         }
    388 
    389         /** @var int $size */
    390         $size = filesize($filePath);
    391         if (!is_int($size)) {
    392             throw new SodiumException('Could not obtain the file size');
    393         }
    394 
    395         /** @var resource $fp */
    396         $fp = fopen($filePath, 'rb');
    397         if (!is_resource($fp)) {
    398             throw new SodiumException('Could not open input file for reading');
    399         }
    400         $ctx = ParagonIE_Sodium_Compat::crypto_generichash_init($key, $outputLength);
    401         while ($size > 0) {
    402             $blockSize = $size > 64
    403                 ? 64
    404                 : $size;
    405             $read = fread($fp, $blockSize);
    406             if (!is_string($read)) {
    407                 throw new SodiumException('Could not read input file');
    408             }
    409             ParagonIE_Sodium_Compat::crypto_generichash_update($ctx, $read);
    410             $size -= $blockSize;
    411         }
    412 
    413         fclose($fp);
    414         return ParagonIE_Sodium_Compat::crypto_generichash_final($ctx, $outputLength);
    415     }
    416 
    417     /**
    418      * Encrypt a file (rather than a string). Uses less memory than
    419      * ParagonIE_Sodium_Compat::crypto_secretbox(), but produces
    420      * the same result.
    421      *
    422      * @param string $inputFile  Absolute path to a file on the filesystem
    423      * @param string $outputFile Absolute path to a file on the filesystem
    424      * @param string $nonce      Number to be used only once
    425      * @param string $key        Encryption key
    426      *
    427      * @return bool
    428      * @throws SodiumException
    429      * @throws TypeError
    430      */
    431     public static function secretbox($inputFile, $outputFile, $nonce, $key)
    432     {
    433         /* Type checks: */
    434         if (!is_string($inputFile)) {
    435             throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given..');
    436         }
    437         if (!is_string($outputFile)) {
    438             throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
    439         }
    440         if (!is_string($nonce)) {
    441             throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
    442         }
    443 
    444         /* Input validation: */
    445         if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_NONCEBYTES) {
    446             throw new TypeError('Argument 3 must be CRYPTO_SECRETBOX_NONCEBYTES bytes');
    447         }
    448         if (!is_string($key)) {
    449             throw new TypeError('Argument 4 must be a string, ' . gettype($key) . ' given.');
    450         }
    451         if (self::strlen($key) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_KEYBYTES) {
    452             throw new TypeError('Argument 4 must be CRYPTO_SECRETBOX_KEYBYTES bytes');
    453         }
    454 
    455         /** @var int $size */
    456         $size = filesize($inputFile);
    457         if (!is_int($size)) {
    458             throw new SodiumException('Could not obtain the file size');
    459         }
    460 
    461         /** @var resource $ifp */
    462         $ifp = fopen($inputFile, 'rb');
    463         if (!is_resource($ifp)) {
    464             throw new SodiumException('Could not open input file for reading');
    465         }
    466 
    467         /** @var resource $ofp */
    468         $ofp = fopen($outputFile, 'wb');
    469         if (!is_resource($ofp)) {
    470             fclose($ifp);
    471             throw new SodiumException('Could not open output file for writing');
    472         }
    473 
    474         $res = self::secretbox_encrypt($ifp, $ofp, $size, $nonce, $key);
    475         fclose($ifp);
    476         fclose($ofp);
    477         return $res;
    478     }
    479     /**
    480      * Seal a file (rather than a string). Uses less memory than
    481      * ParagonIE_Sodium_Compat::crypto_secretbox_open(), but produces
    482      * the same result.
    483      *
    484      * Warning: Does not protect against TOCTOU attacks. You should
    485      * just load the file into memory and use crypto_secretbox_open() if
    486      * you are worried about those.
    487      *
    488      * @param string $inputFile
    489      * @param string $outputFile
    490      * @param string $nonce
    491      * @param string $key
    492      * @return bool
    493      * @throws SodiumException
    494      * @throws TypeError
    495      */
    496     public static function secretbox_open($inputFile, $outputFile, $nonce, $key)
    497     {
    498         /* Type checks: */
    499         if (!is_string($inputFile)) {
    500             throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
    501         }
    502         if (!is_string($outputFile)) {
    503             throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
    504         }
    505         if (!is_string($nonce)) {
    506             throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
    507         }
    508         if (!is_string($key)) {
    509             throw new TypeError('Argument 4 must be a string, ' . gettype($key) . ' given.');
    510         }
    511 
    512         /* Input validation: */
    513         if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_NONCEBYTES) {
    514             throw new TypeError('Argument 4 must be CRYPTO_SECRETBOX_NONCEBYTES bytes');
    515         }
    516         if (self::strlen($key) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_KEYBYTES) {
    517             throw new TypeError('Argument 4 must be CRYPTO_SECRETBOXBOX_KEYBYTES bytes');
    518         }
    519 
    520         /** @var int $size */
    521         $size = filesize($inputFile);
    522         if (!is_int($size)) {
    523             throw new SodiumException('Could not obtain the file size');
    524         }
    525 
    526         /** @var resource $ifp */
    527         $ifp = fopen($inputFile, 'rb');
    528         if (!is_resource($ifp)) {
    529             throw new SodiumException('Could not open input file for reading');
    530         }
    531 
    532         /** @var resource $ofp */
    533         $ofp = fopen($outputFile, 'wb');
    534         if (!is_resource($ofp)) {
    535             fclose($ifp);
    536             throw new SodiumException('Could not open output file for writing');
    537         }
    538 
    539         $res = self::secretbox_decrypt($ifp, $ofp, $size, $nonce, $key);
    540         fclose($ifp);
    541         fclose($ofp);
    542         try {
    543             ParagonIE_Sodium_Compat::memzero($key);
    544         } catch (SodiumException $ex) {
    545             /** @psalm-suppress PossiblyUndefinedVariable */
    546             unset($key);
    547         }
    548         return $res;
    549     }
    550 
    551     /**
    552      * Sign a file (rather than a string). Uses less memory than
    553      * ParagonIE_Sodium_Compat::crypto_sign_detached(), but produces
    554      * the same result.
    555      *
    556      * @param string $filePath  Absolute path to a file on the filesystem
    557      * @param string $secretKey Secret signing key
    558      *
    559      * @return string           Ed25519 signature
    560      * @throws SodiumException
    561      * @throws TypeError
    562      */
    563     public static function sign($filePath, $secretKey)
    564     {
    565         /* Type checks: */
    566         if (!is_string($filePath)) {
    567             throw new TypeError('Argument 1 must be a string, ' . gettype($filePath) . ' given.');
    568         }
    569         if (!is_string($secretKey)) {
    570             throw new TypeError('Argument 2 must be a string, ' . gettype($secretKey) . ' given.');
    571         }
    572 
    573         /* Input validation: */
    574         if (self::strlen($secretKey) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_SECRETKEYBYTES) {
    575             throw new TypeError('Argument 2 must be CRYPTO_SIGN_SECRETKEYBYTES bytes');
    576         }
    577         if (PHP_INT_SIZE === 4) {
    578             return self::sign_core32($filePath, $secretKey);
    579         }
    580 
    581         /** @var int $size */
    582         $size = filesize($filePath);
    583         if (!is_int($size)) {
    584             throw new SodiumException('Could not obtain the file size');
    585         }
    586 
    587         /** @var resource $fp */
    588         $fp = fopen($filePath, 'rb');
    589         if (!is_resource($fp)) {
    590             throw new SodiumException('Could not open input file for reading');
    591         }
    592 
    593         /** @var string $az */
    594         $az = hash('sha512', self::substr($secretKey, 0, 32), true);
    595 
    596         $az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
    597         $az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);
    598 
    599         $hs = hash_init('sha512');
    600         self::hash_update($hs, self::substr($az, 32, 32));
    601         /** @var resource $hs */
    602         $hs = self::updateHashWithFile($hs, $fp, $size);
    603 
    604         /** @var string $nonceHash */
    605         $nonceHash = hash_final($hs, true);
    606 
    607         /** @var string $pk */
    608         $pk = self::substr($secretKey, 32, 32);
    609 
    610         /** @var string $nonce */
    611         $nonce = ParagonIE_Sodium_Core_Ed25519::sc_reduce($nonceHash) . self::substr($nonceHash, 32);
    612 
    613         /** @var string $sig */
    614         $sig = ParagonIE_Sodium_Core_Ed25519::ge_p3_tobytes(
    615             ParagonIE_Sodium_Core_Ed25519::ge_scalarmult_base($nonce)
    616         );
    617 
    618         $hs = hash_init('sha512');
    619         self::hash_update($hs, self::substr($sig, 0, 32));
    620         self::hash_update($hs, self::substr($pk, 0, 32));
    621         /** @var resource $hs */
    622         $hs = self::updateHashWithFile($hs, $fp, $size);
    623 
    624         /** @var string $hramHash */
    625         $hramHash = hash_final($hs, true);
    626 
    627         /** @var string $hram */
    628         $hram = ParagonIE_Sodium_Core_Ed25519::sc_reduce($hramHash);
    629 
    630         /** @var string $sigAfter */
    631         $sigAfter = ParagonIE_Sodium_Core_Ed25519::sc_muladd($hram, $az, $nonce);
    632 
    633         /** @var string $sig */
    634         $sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);
    635 
    636         try {
    637             ParagonIE_Sodium_Compat::memzero($az);
    638         } catch (SodiumException $ex) {
    639             $az = null;
    640         }
    641         fclose($fp);
    642         return $sig;
    643     }
    644 
    645     /**
    646      * Verify a file (rather than a string). Uses less memory than
    647      * ParagonIE_Sodium_Compat::crypto_sign_verify_detached(), but
    648      * produces the same result.
    649      *
    650      * @param string $sig       Ed25519 signature
    651      * @param string $filePath  Absolute path to a file on the filesystem
    652      * @param string $publicKey Signing public key
    653      *
    654      * @return bool
    655      * @throws SodiumException
    656      * @throws TypeError
    657      * @throws Exception
    658      */
    659     public static function verify($sig, $filePath, $publicKey)
    660     {
    661         /* Type checks: */
    662         if (!is_string($sig)) {
    663             throw new TypeError('Argument 1 must be a string, ' . gettype($sig) . ' given.');
    664         }
    665         if (!is_string($filePath)) {
    666             throw new TypeError('Argument 2 must be a string, ' . gettype($filePath) . ' given.');
    667         }
    668         if (!is_string($publicKey)) {
    669             throw new TypeError('Argument 3 must be a string, ' . gettype($publicKey) . ' given.');
    670         }
    671 
    672         /* Input validation: */
    673         if (self::strlen($sig) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_BYTES) {
    674             throw new TypeError('Argument 1 must be CRYPTO_SIGN_BYTES bytes');
    675         }
    676         if (self::strlen($publicKey) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_PUBLICKEYBYTES) {
    677             throw new TypeError('Argument 3 must be CRYPTO_SIGN_PUBLICKEYBYTES bytes');
    678         }
    679         if (self::strlen($sig) < 64) {
    680             throw new SodiumException('Signature is too short');
    681         }
    682 
    683         if (PHP_INT_SIZE === 4) {
    684             return self::verify_core32($sig, $filePath, $publicKey);
    685         }
    686 
    687         /* Security checks */
    688         if (
    689             (ParagonIE_Sodium_Core_Ed25519::chrToInt($sig[63]) & 240)
    690                 &&
    691             ParagonIE_Sodium_Core_Ed25519::check_S_lt_L(self::substr($sig, 32, 32))
    692         ) {
    693             throw new SodiumException('S < L - Invalid signature');
    694         }
    695         if (ParagonIE_Sodium_Core_Ed25519::small_order($sig)) {
    696             throw new SodiumException('Signature is on too small of an order');
    697         }
    698         if ((self::chrToInt($sig[63]) & 224) !== 0) {
    699             throw new SodiumException('Invalid signature');
    700         }
    701         $d = 0;
    702         for ($i = 0; $i < 32; ++$i) {
    703             $d |= self::chrToInt($publicKey[$i]);
    704         }
    705         if ($d === 0) {
    706             throw new SodiumException('All zero public key');
    707         }
    708 
    709         /** @var int $size */
    710         $size = filesize($filePath);
    711         if (!is_int($size)) {
    712             throw new SodiumException('Could not obtain the file size');
    713         }
    714 
    715         /** @var resource $fp */
    716         $fp = fopen($filePath, 'rb');
    717         if (!is_resource($fp)) {
    718             throw new SodiumException('Could not open input file for reading');
    719         }
    720 
    721         /** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
    722         $orig = ParagonIE_Sodium_Compat::$fastMult;
    723 
    724         // Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
    725         ParagonIE_Sodium_Compat::$fastMult = true;
    726 
    727         /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A */
    728         $A = ParagonIE_Sodium_Core_Ed25519::ge_frombytes_negate_vartime($publicKey);
    729 
    730         $hs = hash_init('sha512');
    731         self::hash_update($hs, self::substr($sig, 0, 32));
    732         self::hash_update($hs, self::substr($publicKey, 0, 32));
    733         /** @var resource $hs */
    734         $hs = self::updateHashWithFile($hs, $fp, $size);
    735         /** @var string $hDigest */
    736         $hDigest = hash_final($hs, true);
    737 
    738         /** @var string $h */
    739         $h = ParagonIE_Sodium_Core_Ed25519::sc_reduce($hDigest) . self::substr($hDigest, 32);
    740 
    741         /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P2 $R */
    742         $R = ParagonIE_Sodium_Core_Ed25519::ge_double_scalarmult_vartime(
    743             $h,
    744             $A,
    745             self::substr($sig, 32)
    746         );
    747 
    748         /** @var string $rcheck */
    749         $rcheck = ParagonIE_Sodium_Core_Ed25519::ge_tobytes($R);
    750 
    751         // Close the file handle
    752         fclose($fp);
    753 
    754         // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
    755         ParagonIE_Sodium_Compat::$fastMult = $orig;
    756         return self::verify_32($rcheck, self::substr($sig, 0, 32));
    757     }
    758 
    759     /**
    760      * @param resource $ifp
    761      * @param resource $ofp
    762      * @param int      $mlen
    763      * @param string   $nonce
    764      * @param string   $boxKeypair
    765      * @return bool
    766      * @throws SodiumException
    767      * @throws TypeError
    768      */
    769     protected static function box_encrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
    770     {
    771         if (PHP_INT_SIZE === 4) {
    772             return self::secretbox_encrypt(
    773                 $ifp,
    774                 $ofp,
    775                 $mlen,
    776                 $nonce,
    777                 ParagonIE_Sodium_Crypto32::box_beforenm(
    778                     ParagonIE_Sodium_Crypto32::box_secretkey($boxKeypair),
    779                     ParagonIE_Sodium_Crypto32::box_publickey($boxKeypair)
    780                 )
    781             );
    782         }
    783         return self::secretbox_encrypt(
    784             $ifp,
    785             $ofp,
    786             $mlen,
    787             $nonce,
    788             ParagonIE_Sodium_Crypto::box_beforenm(
    789                 ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
    790                 ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
    791             )
    792         );
    793     }
    794 
    795 
    796     /**
    797      * @param resource $ifp
    798      * @param resource $ofp
    799      * @param int      $mlen
    800      * @param string   $nonce
    801      * @param string   $boxKeypair
    802      * @return bool
    803      * @throws SodiumException
    804      * @throws TypeError
    805      */
    806     protected static function box_decrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
    807     {
    808         if (PHP_INT_SIZE === 4) {
    809             return self::secretbox_decrypt(
    810                 $ifp,
    811                 $ofp,
    812                 $mlen,
    813                 $nonce,
    814                 ParagonIE_Sodium_Crypto32::box_beforenm(
    815                     ParagonIE_Sodium_Crypto32::box_secretkey($boxKeypair),
    816                     ParagonIE_Sodium_Crypto32::box_publickey($boxKeypair)
    817                 )
    818             );
    819         }
    820         return self::secretbox_decrypt(
    821             $ifp,
    822             $ofp,
    823             $mlen,
    824             $nonce,
    825             ParagonIE_Sodium_Crypto::box_beforenm(
    826                 ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
    827                 ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
    828             )
    829         );
    830     }
    831 
    832     /**
    833      * Encrypt a file
    834      *
    835      * @param resource $ifp
    836      * @param resource $ofp
    837      * @param int $mlen
    838      * @param string $nonce
    839      * @param string $key
    840      * @return bool
    841      * @throws SodiumException
    842      * @throws TypeError
    843      */
    844     protected static function secretbox_encrypt($ifp, $ofp, $mlen, $nonce, $key)
    845     {
    846         if (PHP_INT_SIZE === 4) {
    847             return self::secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $key);
    848         }
    849 
    850         $plaintext = fread($ifp, 32);
    851         if (!is_string($plaintext)) {
    852             throw new SodiumException('Could not read input file');
    853         }
    854         $first32 = self::ftell($ifp);
    855 
    856         /** @var string $subkey */
    857         $subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);
    858 
    859         /** @var string $realNonce */
    860         $realNonce = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
    861 
    862         /** @var string $block0 */
    863         $block0 = str_repeat("\x00", 32);
    864 
    865         /** @var int $mlen - Length of the plaintext message */
    866         $mlen0 = $mlen;
    867         if ($mlen0 > 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES) {
    868             $mlen0 = 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES;
    869         }
    870         $block0 .= ParagonIE_Sodium_Core_Util::substr($plaintext, 0, $mlen0);
    871 
    872         /** @var string $block0 */
    873         $block0 = ParagonIE_Sodium_Core_Salsa20::salsa20_xor(
    874             $block0,
    875             $realNonce,
    876             $subkey
    877         );
    878 
    879         $state = new ParagonIE_Sodium_Core_Poly1305_State(
    880             ParagonIE_Sodium_Core_Util::substr(
    881                 $block0,
    882                 0,
    883                 ParagonIE_Sodium_Crypto::onetimeauth_poly1305_KEYBYTES
    884             )
    885         );
    886 
    887         // Pre-write 16 blank bytes for the Poly1305 tag
    888         $start = self::ftell($ofp);
    889         fwrite($ofp, str_repeat("\x00", 16));
    890 
    891         /** @var string $c */
    892         $cBlock = ParagonIE_Sodium_Core_Util::substr(
    893             $block0,
    894             ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES
    895         );
    896         $state->update($cBlock);
    897         fwrite($ofp, $cBlock);
    898         $mlen -= 32;
    899 
    900         /** @var int $iter */
    901         $iter = 1;
    902 
    903         /** @var int $incr */
    904         $incr = self::BUFFER_SIZE >> 6;
    905 
    906         /*
    907          * Set the cursor to the end of the first half-block. All future bytes will
    908          * generated from salsa20_xor_ic, starting from 1 (second block).
    909          */
    910         fseek($ifp, $first32, SEEK_SET);
    911 
    912         while ($mlen > 0) {
    913             $blockSize = $mlen > self::BUFFER_SIZE
    914                 ? self::BUFFER_SIZE
    915                 : $mlen;
    916             $plaintext = fread($ifp, $blockSize);
    917             if (!is_string($plaintext)) {
    918                 throw new SodiumException('Could not read input file');
    919             }
    920             $cBlock = ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
    921                 $plaintext,
    922                 $realNonce,
    923                 $iter,
    924                 $subkey
    925             );
    926             fwrite($ofp, $cBlock, $blockSize);
    927             $state->update($cBlock);
    928 
    929             $mlen -= $blockSize;
    930             $iter += $incr;
    931         }
    932         try {
    933             ParagonIE_Sodium_Compat::memzero($block0);
    934             ParagonIE_Sodium_Compat::memzero($subkey);
    935         } catch (SodiumException $ex) {
    936             $block0 = null;
    937             $subkey = null;
    938         }
    939         $end = self::ftell($ofp);
    940 
    941         /*
    942          * Write the Poly1305 authentication tag that provides integrity
    943          * over the ciphertext (encrypt-then-MAC)
    944          */
    945         fseek($ofp, $start, SEEK_SET);
    946         fwrite($ofp, $state->finish(), ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_MACBYTES);
    947         fseek($ofp, $end, SEEK_SET);
    948         unset($state);
    949 
    950         return true;
    951     }
    952 
    953     /**
    954      * Decrypt a file
    955      *
    956      * @param resource $ifp
    957      * @param resource $ofp
    958      * @param int $mlen
    959      * @param string $nonce
    960      * @param string $key
    961      * @return bool
    962      * @throws SodiumException
    963      * @throws TypeError
    964      */
    965     protected static function secretbox_decrypt($ifp, $ofp, $mlen, $nonce, $key)
    966     {
    967         if (PHP_INT_SIZE === 4) {
    968             return self::secretbox_decrypt_core32($ifp, $ofp, $mlen, $nonce, $key);
    969         }
    970         $tag = fread($ifp, 16);
    971         if (!is_string($tag)) {
    972             throw new SodiumException('Could not read input file');
    973         }
    974 
    975         /** @var string $subkey */
    976         $subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);
    977 
    978         /** @var string $realNonce */
    979         $realNonce = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
    980 
    981         /** @var string $block0 */
    982         $block0 = ParagonIE_Sodium_Core_Salsa20::salsa20(
    983             64,
    984             ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
    985             $subkey
    986         );
    987 
    988         /* Verify the Poly1305 MAC -before- attempting to decrypt! */
    989         $state = new ParagonIE_Sodium_Core_Poly1305_State(self::substr($block0, 0, 32));
    990         if (!self::onetimeauth_verify($state, $ifp, $tag, $mlen)) {
    991             throw new SodiumException('Invalid MAC');
    992         }
    993 
    994         /*
    995          * Set the cursor to the end of the first half-block. All future bytes will
    996          * generated from salsa20_xor_ic, starting from 1 (second block).
    997          */
    998         $first32 = fread($ifp, 32);
    999         if (!is_string($first32)) {
   1000             throw new SodiumException('Could not read input file');
   1001         }
   1002         $first32len = self::strlen($first32);
   1003         fwrite(
   1004             $ofp,
   1005             self::xorStrings(
   1006                 self::substr($block0, 32, $first32len),
   1007                 self::substr($first32, 0, $first32len)
   1008             )
   1009         );
   1010         $mlen -= 32;
   1011 
   1012         /** @var int $iter */
   1013         $iter = 1;
   1014 
   1015         /** @var int $incr */
   1016         $incr = self::BUFFER_SIZE >> 6;
   1017 
   1018         /* Decrypts ciphertext, writes to output file. */
   1019         while ($mlen > 0) {
   1020             $blockSize = $mlen > self::BUFFER_SIZE
   1021                 ? self::BUFFER_SIZE
   1022                 : $mlen;
   1023             $ciphertext = fread($ifp, $blockSize);
   1024             if (!is_string($ciphertext)) {
   1025                 throw new SodiumException('Could not read input file');
   1026             }
   1027             $pBlock = ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
   1028                 $ciphertext,
   1029                 $realNonce,
   1030                 $iter,
   1031                 $subkey
   1032             );
   1033             fwrite($ofp, $pBlock, $blockSize);
   1034             $mlen -= $blockSize;
   1035             $iter += $incr;
   1036         }
   1037         return true;
   1038     }
   1039 
   1040     /**
   1041      * @param ParagonIE_Sodium_Core_Poly1305_State $state
   1042      * @param resource $ifp
   1043      * @param string $tag
   1044      * @param int $mlen
   1045      * @return bool
   1046      * @throws SodiumException
   1047      * @throws TypeError
   1048      */
   1049     protected static function onetimeauth_verify(
   1050         ParagonIE_Sodium_Core_Poly1305_State $state,
   1051         $ifp,
   1052         $tag = '',
   1053         $mlen = 0
   1054     ) {
   1055         /** @var int $pos */
   1056         $pos = self::ftell($ifp);
   1057 
   1058         /** @var int $iter */
   1059         $iter = 1;
   1060 
   1061         /** @var int $incr */
   1062         $incr = self::BUFFER_SIZE >> 6;
   1063 
   1064         while ($mlen > 0) {
   1065             $blockSize = $mlen > self::BUFFER_SIZE
   1066                 ? self::BUFFER_SIZE
   1067                 : $mlen;
   1068             $ciphertext = fread($ifp, $blockSize);
   1069             if (!is_string($ciphertext)) {
   1070                 throw new SodiumException('Could not read input file');
   1071             }
   1072             $state->update($ciphertext);
   1073             $mlen -= $blockSize;
   1074             $iter += $incr;
   1075         }
   1076         $res = ParagonIE_Sodium_Core_Util::verify_16($tag, $state->finish());
   1077 
   1078         fseek($ifp, $pos, SEEK_SET);
   1079         return $res;
   1080     }
   1081 
   1082     /**
   1083      * Update a hash context with the contents of a file, without
   1084      * loading the entire file into memory.
   1085      *
   1086      * @param resource|HashContext $hash
   1087      * @param resource $fp
   1088      * @param int $size
   1089      * @return resource|object Resource on PHP < 7.2, HashContext object on PHP >= 7.2
   1090      * @throws SodiumException
   1091      * @throws TypeError
   1092      * @psalm-suppress PossiblyInvalidArgument
   1093      *                 PHP 7.2 changes from a resource to an object,
   1094      *                 which causes Psalm to complain about an error.
   1095      * @psalm-suppress TypeCoercion
   1096      *                 Ditto.
   1097      */
   1098     public static function updateHashWithFile($hash, $fp, $size = 0)
   1099     {
   1100         /* Type checks: */
   1101         if (PHP_VERSION_ID < 70200) {
   1102             if (!is_resource($hash)) {
   1103                 throw new TypeError('Argument 1 must be a resource, ' . gettype($hash) . ' given.');
   1104             }
   1105         } else {
   1106             if (!is_object($hash)) {
   1107                 throw new TypeError('Argument 1 must be an object (PHP 7.2+), ' . gettype($hash) . ' given.');
   1108             }
   1109         }
   1110 
   1111         if (!is_resource($fp)) {
   1112             throw new TypeError('Argument 2 must be a resource, ' . gettype($fp) . ' given.');
   1113         }
   1114         if (!is_int($size)) {
   1115             throw new TypeError('Argument 3 must be an integer, ' . gettype($size) . ' given.');
   1116         }
   1117 
   1118         /** @var int $originalPosition */
   1119         $originalPosition = self::ftell($fp);
   1120 
   1121         // Move file pointer to beginning of file
   1122         fseek($fp, 0, SEEK_SET);
   1123         for ($i = 0; $i < $size; $i += self::BUFFER_SIZE) {
   1124             /** @var string|bool $message */
   1125             $message = fread(
   1126                 $fp,
   1127                 ($size - $i) > self::BUFFER_SIZE
   1128                     ? $size - $i
   1129                     : self::BUFFER_SIZE
   1130             );
   1131             if (!is_string($message)) {
   1132                 throw new SodiumException('Unexpected error reading from file.');
   1133             }
   1134             /** @var string $message */
   1135             /** @psalm-suppress InvalidArgument */
   1136             self::hash_update($hash, $message);
   1137         }
   1138         // Reset file pointer's position
   1139         fseek($fp, $originalPosition, SEEK_SET);
   1140         return $hash;
   1141     }
   1142 
   1143     /**
   1144      * Sign a file (rather than a string). Uses less memory than
   1145      * ParagonIE_Sodium_Compat::crypto_sign_detached(), but produces
   1146      * the same result. (32-bit)
   1147      *
   1148      * @param string $filePath  Absolute path to a file on the filesystem
   1149      * @param string $secretKey Secret signing key
   1150      *
   1151      * @return string           Ed25519 signature
   1152      * @throws SodiumException
   1153      * @throws TypeError
   1154      */
   1155     private static function sign_core32($filePath, $secretKey)
   1156     {
   1157         /** @var int|bool $size */
   1158         $size = filesize($filePath);
   1159         if (!is_int($size)) {
   1160             throw new SodiumException('Could not obtain the file size');
   1161         }
   1162         /** @var int $size */
   1163 
   1164         /** @var resource|bool $fp */
   1165         $fp = fopen($filePath, 'rb');
   1166         if (!is_resource($fp)) {
   1167             throw new SodiumException('Could not open input file for reading');
   1168         }
   1169         /** @var resource $fp */
   1170 
   1171         /** @var string $az */
   1172         $az = hash('sha512', self::substr($secretKey, 0, 32), true);
   1173 
   1174         $az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
   1175         $az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);
   1176 
   1177         $hs = hash_init('sha512');
   1178         self::hash_update($hs, self::substr($az, 32, 32));
   1179         /** @var resource $hs */
   1180         $hs = self::updateHashWithFile($hs, $fp, $size);
   1181 
   1182         /** @var string $nonceHash */
   1183         $nonceHash = hash_final($hs, true);
   1184 
   1185         /** @var string $pk */
   1186         $pk = self::substr($secretKey, 32, 32);
   1187 
   1188         /** @var string $nonce */
   1189         $nonce = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($nonceHash) . self::substr($nonceHash, 32);
   1190 
   1191         /** @var string $sig */
   1192         $sig = ParagonIE_Sodium_Core32_Ed25519::ge_p3_tobytes(
   1193             ParagonIE_Sodium_Core32_Ed25519::ge_scalarmult_base($nonce)
   1194         );
   1195 
   1196         $hs = hash_init('sha512');
   1197         self::hash_update($hs, self::substr($sig, 0, 32));
   1198         self::hash_update($hs, self::substr($pk, 0, 32));
   1199         /** @var resource $hs */
   1200         $hs = self::updateHashWithFile($hs, $fp, $size);
   1201 
   1202         /** @var string $hramHash */
   1203         $hramHash = hash_final($hs, true);
   1204 
   1205         /** @var string $hram */
   1206         $hram = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($hramHash);
   1207 
   1208         /** @var string $sigAfter */
   1209         $sigAfter = ParagonIE_Sodium_Core32_Ed25519::sc_muladd($hram, $az, $nonce);
   1210 
   1211         /** @var string $sig */
   1212         $sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);
   1213 
   1214         try {
   1215             ParagonIE_Sodium_Compat::memzero($az);
   1216         } catch (SodiumException $ex) {
   1217             $az = null;
   1218         }
   1219         fclose($fp);
   1220         return $sig;
   1221     }
   1222 
   1223     /**
   1224      *
   1225      * Verify a file (rather than a string). Uses less memory than
   1226      * ParagonIE_Sodium_Compat::crypto_sign_verify_detached(), but
   1227      * produces the same result. (32-bit)
   1228      *
   1229      * @param string $sig       Ed25519 signature
   1230      * @param string $filePath  Absolute path to a file on the filesystem
   1231      * @param string $publicKey Signing public key
   1232      *
   1233      * @return bool
   1234      * @throws SodiumException
   1235      * @throws Exception
   1236      */
   1237     public static function verify_core32($sig, $filePath, $publicKey)
   1238     {
   1239         /* Security checks */
   1240         if (ParagonIE_Sodium_Core32_Ed25519::check_S_lt_L(self::substr($sig, 32, 32))) {
   1241             throw new SodiumException('S < L - Invalid signature');
   1242         }
   1243         if (ParagonIE_Sodium_Core32_Ed25519::small_order($sig)) {
   1244             throw new SodiumException('Signature is on too small of an order');
   1245         }
   1246         if ((self::chrToInt($sig[63]) & 224) !== 0) {
   1247             throw new SodiumException('Invalid signature');
   1248         }
   1249         $d = 0;
   1250         for ($i = 0; $i < 32; ++$i) {
   1251             $d |= self::chrToInt($publicKey[$i]);
   1252         }
   1253         if ($d === 0) {
   1254             throw new SodiumException('All zero public key');
   1255         }
   1256 
   1257         /** @var int|bool $size */
   1258         $size = filesize($filePath);
   1259         if (!is_int($size)) {
   1260             throw new SodiumException('Could not obtain the file size');
   1261         }
   1262         /** @var int $size */
   1263 
   1264         /** @var resource|bool $fp */
   1265         $fp = fopen($filePath, 'rb');
   1266         if (!is_resource($fp)) {
   1267             throw new SodiumException('Could not open input file for reading');
   1268         }
   1269         /** @var resource $fp */
   1270 
   1271         /** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
   1272         $orig = ParagonIE_Sodium_Compat::$fastMult;
   1273 
   1274         // Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
   1275         ParagonIE_Sodium_Compat::$fastMult = true;
   1276 
   1277         /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $A */
   1278         $A = ParagonIE_Sodium_Core32_Ed25519::ge_frombytes_negate_vartime($publicKey);
   1279 
   1280         $hs = hash_init('sha512');
   1281         self::hash_update($hs, self::substr($sig, 0, 32));
   1282         self::hash_update($hs, self::substr($publicKey, 0, 32));
   1283         /** @var resource $hs */
   1284         $hs = self::updateHashWithFile($hs, $fp, $size);
   1285         /** @var string $hDigest */
   1286         $hDigest = hash_final($hs, true);
   1287 
   1288         /** @var string $h */
   1289         $h = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($hDigest) . self::substr($hDigest, 32);
   1290 
   1291         /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P2 $R */
   1292         $R = ParagonIE_Sodium_Core32_Ed25519::ge_double_scalarmult_vartime(
   1293             $h,
   1294             $A,
   1295             self::substr($sig, 32)
   1296         );
   1297 
   1298         /** @var string $rcheck */
   1299         $rcheck = ParagonIE_Sodium_Core32_Ed25519::ge_tobytes($R);
   1300 
   1301         // Close the file handle
   1302         fclose($fp);
   1303 
   1304         // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
   1305         ParagonIE_Sodium_Compat::$fastMult = $orig;
   1306         return self::verify_32($rcheck, self::substr($sig, 0, 32));
   1307     }
   1308 
   1309     /**
   1310      * Encrypt a file (32-bit)
   1311      *
   1312      * @param resource $ifp
   1313      * @param resource $ofp
   1314      * @param int $mlen
   1315      * @param string $nonce
   1316      * @param string $key
   1317      * @return bool
   1318      * @throws SodiumException
   1319      * @throws TypeError
   1320      */
   1321     protected static function secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $key)
   1322     {
   1323         $plaintext = fread($ifp, 32);
   1324         if (!is_string($plaintext)) {
   1325             throw new SodiumException('Could not read input file');
   1326         }
   1327         $first32 = self::ftell($ifp);
   1328 
   1329         /** @var string $subkey */
   1330         $subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);
   1331 
   1332         /** @var string $realNonce */
   1333         $realNonce = ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);
   1334 
   1335         /** @var string $block0 */
   1336         $block0 = str_repeat("\x00", 32);
   1337 
   1338         /** @var int $mlen - Length of the plaintext message */
   1339         $mlen0 = $mlen;
   1340         if ($mlen0 > 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES) {
   1341             $mlen0 = 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES;
   1342         }
   1343         $block0 .= ParagonIE_Sodium_Core32_Util::substr($plaintext, 0, $mlen0);
   1344 
   1345         /** @var string $block0 */
   1346         $block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor(
   1347             $block0,
   1348             $realNonce,
   1349             $subkey
   1350         );
   1351 
   1352         $state = new ParagonIE_Sodium_Core32_Poly1305_State(
   1353             ParagonIE_Sodium_Core32_Util::substr(
   1354                 $block0,
   1355                 0,
   1356                 ParagonIE_Sodium_Crypto::onetimeauth_poly1305_KEYBYTES
   1357             )
   1358         );
   1359 
   1360         // Pre-write 16 blank bytes for the Poly1305 tag
   1361         $start = self::ftell($ofp);
   1362         fwrite($ofp, str_repeat("\x00", 16));
   1363 
   1364         /** @var string $c */
   1365         $cBlock = ParagonIE_Sodium_Core32_Util::substr(
   1366             $block0,
   1367             ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES
   1368         );
   1369         $state->update($cBlock);
   1370         fwrite($ofp, $cBlock);
   1371         $mlen -= 32;
   1372 
   1373         /** @var int $iter */
   1374         $iter = 1;
   1375 
   1376         /** @var int $incr */
   1377         $incr = self::BUFFER_SIZE >> 6;
   1378 
   1379         /*
   1380          * Set the cursor to the end of the first half-block. All future bytes will
   1381          * generated from salsa20_xor_ic, starting from 1 (second block).
   1382          */
   1383         fseek($ifp, $first32, SEEK_SET);
   1384 
   1385         while ($mlen > 0) {
   1386             $blockSize = $mlen > self::BUFFER_SIZE
   1387                 ? self::BUFFER_SIZE
   1388                 : $mlen;
   1389             $plaintext = fread($ifp, $blockSize);
   1390             if (!is_string($plaintext)) {
   1391                 throw new SodiumException('Could not read input file');
   1392             }
   1393             $cBlock = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
   1394                 $plaintext,
   1395                 $realNonce,
   1396                 $iter,
   1397                 $subkey
   1398             );
   1399             fwrite($ofp, $cBlock, $blockSize);
   1400             $state->update($cBlock);
   1401 
   1402             $mlen -= $blockSize;
   1403             $iter += $incr;
   1404         }
   1405         try {
   1406             ParagonIE_Sodium_Compat::memzero($block0);
   1407             ParagonIE_Sodium_Compat::memzero($subkey);
   1408         } catch (SodiumException $ex) {
   1409             $block0 = null;
   1410             $subkey = null;
   1411         }
   1412         $end = self::ftell($ofp);
   1413 
   1414         /*
   1415          * Write the Poly1305 authentication tag that provides integrity
   1416          * over the ciphertext (encrypt-then-MAC)
   1417          */
   1418         fseek($ofp, $start, SEEK_SET);
   1419         fwrite($ofp, $state->finish(), ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_MACBYTES);
   1420         fseek($ofp, $end, SEEK_SET);
   1421         unset($state);
   1422 
   1423         return true;
   1424     }
   1425 
   1426     /**
   1427      * Decrypt a file (32-bit)
   1428      *
   1429      * @param resource $ifp
   1430      * @param resource $ofp
   1431      * @param int $mlen
   1432      * @param string $nonce
   1433      * @param string $key
   1434      * @return bool
   1435      * @throws SodiumException
   1436      * @throws TypeError
   1437      */
   1438     protected static function secretbox_decrypt_core32($ifp, $ofp, $mlen, $nonce, $key)
   1439     {
   1440         $tag = fread($ifp, 16);
   1441         if (!is_string($tag)) {
   1442             throw new SodiumException('Could not read input file');
   1443         }
   1444 
   1445         /** @var string $subkey */
   1446         $subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);
   1447 
   1448         /** @var string $realNonce */
   1449         $realNonce = ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);
   1450 
   1451         /** @var string $block0 */
   1452         $block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20(
   1453             64,
   1454             ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
   1455             $subkey
   1456         );
   1457 
   1458         /* Verify the Poly1305 MAC -before- attempting to decrypt! */
   1459         $state = new ParagonIE_Sodium_Core32_Poly1305_State(self::substr($block0, 0, 32));
   1460         if (!self::onetimeauth_verify_core32($state, $ifp, $tag, $mlen)) {
   1461             throw new SodiumException('Invalid MAC');
   1462         }
   1463 
   1464         /*
   1465          * Set the cursor to the end of the first half-block. All future bytes will
   1466          * generated from salsa20_xor_ic, starting from 1 (second block).
   1467          */
   1468         $first32 = fread($ifp, 32);
   1469         if (!is_string($first32)) {
   1470             throw new SodiumException('Could not read input file');
   1471         }
   1472         $first32len = self::strlen($first32);
   1473         fwrite(
   1474             $ofp,
   1475             self::xorStrings(
   1476                 self::substr($block0, 32, $first32len),
   1477                 self::substr($first32, 0, $first32len)
   1478             )
   1479         );
   1480         $mlen -= 32;
   1481 
   1482         /** @var int $iter */
   1483         $iter = 1;
   1484 
   1485         /** @var int $incr */
   1486         $incr = self::BUFFER_SIZE >> 6;
   1487 
   1488         /* Decrypts ciphertext, writes to output file. */
   1489         while ($mlen > 0) {
   1490             $blockSize = $mlen > self::BUFFER_SIZE
   1491                 ? self::BUFFER_SIZE
   1492                 : $mlen;
   1493             $ciphertext = fread($ifp, $blockSize);
   1494             if (!is_string($ciphertext)) {
   1495                 throw new SodiumException('Could not read input file');
   1496             }
   1497             $pBlock = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
   1498                 $ciphertext,
   1499                 $realNonce,
   1500                 $iter,
   1501                 $subkey
   1502             );
   1503             fwrite($ofp, $pBlock, $blockSize);
   1504             $mlen -= $blockSize;
   1505             $iter += $incr;
   1506         }
   1507         return true;
   1508     }
   1509 
   1510     /**
   1511      * One-time message authentication for 32-bit systems
   1512      *
   1513      * @param ParagonIE_Sodium_Core32_Poly1305_State $state
   1514      * @param resource $ifp
   1515      * @param string $tag
   1516      * @param int $mlen
   1517      * @return bool
   1518      * @throws SodiumException
   1519      * @throws TypeError
   1520      */
   1521     protected static function onetimeauth_verify_core32(
   1522         ParagonIE_Sodium_Core32_Poly1305_State $state,
   1523         $ifp,
   1524         $tag = '',
   1525         $mlen = 0
   1526     ) {
   1527         /** @var int $pos */
   1528         $pos = self::ftell($ifp);
   1529 
   1530         while ($mlen > 0) {
   1531             $blockSize = $mlen > self::BUFFER_SIZE
   1532                 ? self::BUFFER_SIZE
   1533                 : $mlen;
   1534             $ciphertext = fread($ifp, $blockSize);
   1535             if (!is_string($ciphertext)) {
   1536                 throw new SodiumException('Could not read input file');
   1537             }
   1538             $state->update($ciphertext);
   1539             $mlen -= $blockSize;
   1540         }
   1541         $res = ParagonIE_Sodium_Core32_Util::verify_16($tag, $state->finish());
   1542 
   1543         fseek($ifp, $pos, SEEK_SET);
   1544         return $res;
   1545     }
   1546 
   1547     /**
   1548      * @param resource $resource
   1549      * @return int
   1550      * @throws SodiumException
   1551      */
   1552     private static function ftell($resource)
   1553     {
   1554         $return = ftell($resource);
   1555         if (!is_int($return)) {
   1556             throw new SodiumException('ftell() returned false');
   1557         }
   1558         return (int) $return;
   1559     }
   1560 }