[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/sodium_compat/src/ -> File.php (source)

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


Generated : Fri Oct 10 08:20:03 2025 Cross-referenced by PHPXref