[ 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          if (ParagonIE_Sodium_Core_Ed25519::small_order($publicKey)) {
 790              throw new SodiumException('Public key has small order');
 791          }
 792          /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A */
 793          $A = ParagonIE_Sodium_Core_Ed25519::ge_frombytes_negate_vartime($publicKey);
 794          if (!ParagonIE_Sodium_Core_Ed25519::is_on_main_subgroup($A)) {
 795              throw new SodiumException('Public key is not on a member of the main subgroup');
 796          }
 797  
 798          $hs = hash_init('sha512');
 799          self::hash_update($hs, self::substr($sig, 0, 32));
 800          self::hash_update($hs, self::substr($publicKey, 0, 32));
 801          /** @var resource $hs */
 802          $hs = self::updateHashWithFile($hs, $fp, $size);
 803          /** @var string $hDigest */
 804          $hDigest = hash_final($hs, true);
 805  
 806          /** @var string $h */
 807          $h = ParagonIE_Sodium_Core_Ed25519::sc_reduce($hDigest) . self::substr($hDigest, 32);
 808  
 809          /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P2 $R */
 810          $R = ParagonIE_Sodium_Core_Ed25519::ge_double_scalarmult_vartime(
 811              $h,
 812              $A,
 813              self::substr($sig, 32)
 814          );
 815  
 816          /** @var string $rcheck */
 817          $rcheck = ParagonIE_Sodium_Core_Ed25519::ge_tobytes($R);
 818  
 819          // Close the file handle
 820          fclose($fp);
 821  
 822          // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
 823          ParagonIE_Sodium_Compat::$fastMult = $orig;
 824          return self::verify_32($rcheck, self::substr($sig, 0, 32));
 825      }
 826  
 827      /**
 828       * @param resource $ifp
 829       * @param resource $ofp
 830       * @param int      $mlen
 831       * @param string   $nonce
 832       * @param string   $boxKeypair
 833       * @return bool
 834       * @throws SodiumException
 835       * @throws TypeError
 836       */
 837      protected static function box_encrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
 838      {
 839          if (PHP_INT_SIZE === 4) {
 840              return self::secretbox_encrypt(
 841                  $ifp,
 842                  $ofp,
 843                  $mlen,
 844                  $nonce,
 845                  ParagonIE_Sodium_Crypto32::box_beforenm(
 846                      ParagonIE_Sodium_Crypto32::box_secretkey($boxKeypair),
 847                      ParagonIE_Sodium_Crypto32::box_publickey($boxKeypair)
 848                  )
 849              );
 850          }
 851          return self::secretbox_encrypt(
 852              $ifp,
 853              $ofp,
 854              $mlen,
 855              $nonce,
 856              ParagonIE_Sodium_Crypto::box_beforenm(
 857                  ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
 858                  ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
 859              )
 860          );
 861      }
 862  
 863  
 864      /**
 865       * @param resource $ifp
 866       * @param resource $ofp
 867       * @param int      $mlen
 868       * @param string   $nonce
 869       * @param string   $boxKeypair
 870       * @return bool
 871       * @throws SodiumException
 872       * @throws TypeError
 873       */
 874      protected static function box_decrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
 875      {
 876          if (PHP_INT_SIZE === 4) {
 877              return self::secretbox_decrypt(
 878                  $ifp,
 879                  $ofp,
 880                  $mlen,
 881                  $nonce,
 882                  ParagonIE_Sodium_Crypto32::box_beforenm(
 883                      ParagonIE_Sodium_Crypto32::box_secretkey($boxKeypair),
 884                      ParagonIE_Sodium_Crypto32::box_publickey($boxKeypair)
 885                  )
 886              );
 887          }
 888          return self::secretbox_decrypt(
 889              $ifp,
 890              $ofp,
 891              $mlen,
 892              $nonce,
 893              ParagonIE_Sodium_Crypto::box_beforenm(
 894                  ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
 895                  ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
 896              )
 897          );
 898      }
 899  
 900      /**
 901       * Encrypt a file
 902       *
 903       * @param resource $ifp
 904       * @param resource $ofp
 905       * @param int $mlen
 906       * @param string $nonce
 907       * @param string $key
 908       * @return bool
 909       * @throws SodiumException
 910       * @throws TypeError
 911       */
 912      protected static function secretbox_encrypt($ifp, $ofp, $mlen, $nonce, $key)
 913      {
 914          if (PHP_INT_SIZE === 4) {
 915              return self::secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $key);
 916          }
 917  
 918          $plaintext = fread($ifp, 32);
 919          if (!is_string($plaintext)) {
 920              throw new SodiumException('Could not read input file');
 921          }
 922          $first32 = self::ftell($ifp);
 923  
 924          /** @var string $subkey */
 925          $subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);
 926  
 927          /** @var string $realNonce */
 928          $realNonce = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
 929  
 930          /** @var string $block0 */
 931          $block0 = str_repeat("\x00", 32);
 932  
 933          /** @var int $mlen - Length of the plaintext message */
 934          $mlen0 = $mlen;
 935          if ($mlen0 > 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES) {
 936              $mlen0 = 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES;
 937          }
 938          $block0 .= ParagonIE_Sodium_Core_Util::substr($plaintext, 0, $mlen0);
 939  
 940          /** @var string $block0 */
 941          $block0 = ParagonIE_Sodium_Core_Salsa20::salsa20_xor(
 942              $block0,
 943              $realNonce,
 944              $subkey
 945          );
 946  
 947          $state = new ParagonIE_Sodium_Core_Poly1305_State(
 948              ParagonIE_Sodium_Core_Util::substr(
 949                  $block0,
 950                  0,
 951                  ParagonIE_Sodium_Crypto::onetimeauth_poly1305_KEYBYTES
 952              )
 953          );
 954  
 955          // Pre-write 16 blank bytes for the Poly1305 tag
 956          $start = self::ftell($ofp);
 957          fwrite($ofp, str_repeat("\x00", 16));
 958  
 959          /** @var string $c */
 960          $cBlock = ParagonIE_Sodium_Core_Util::substr(
 961              $block0,
 962              ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES
 963          );
 964          $state->update($cBlock);
 965          fwrite($ofp, $cBlock);
 966          $mlen -= 32;
 967  
 968          /** @var int $iter */
 969          $iter = 1;
 970  
 971          /** @var int $incr */
 972          $incr = self::BUFFER_SIZE >> 6;
 973  
 974          /*
 975           * Set the cursor to the end of the first half-block. All future bytes will
 976           * generated from salsa20_xor_ic, starting from 1 (second block).
 977           */
 978          fseek($ifp, $first32, SEEK_SET);
 979  
 980          while ($mlen > 0) {
 981              $blockSize = $mlen > self::BUFFER_SIZE
 982                  ? self::BUFFER_SIZE
 983                  : $mlen;
 984              $plaintext = fread($ifp, $blockSize);
 985              if (!is_string($plaintext)) {
 986                  throw new SodiumException('Could not read input file');
 987              }
 988              $cBlock = ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
 989                  $plaintext,
 990                  $realNonce,
 991                  $iter,
 992                  $subkey
 993              );
 994              fwrite($ofp, $cBlock, $blockSize);
 995              $state->update($cBlock);
 996  
 997              $mlen -= $blockSize;
 998              $iter += $incr;
 999          }
1000          try {
1001              ParagonIE_Sodium_Compat::memzero($block0);
1002              ParagonIE_Sodium_Compat::memzero($subkey);
1003          } catch (SodiumException $ex) {
1004              $block0 = null;
1005              $subkey = null;
1006          }
1007          $end = self::ftell($ofp);
1008  
1009          /*
1010           * Write the Poly1305 authentication tag that provides integrity
1011           * over the ciphertext (encrypt-then-MAC)
1012           */
1013          fseek($ofp, $start, SEEK_SET);
1014          fwrite($ofp, $state->finish(), ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_MACBYTES);
1015          fseek($ofp, $end, SEEK_SET);
1016          unset($state);
1017  
1018          return true;
1019      }
1020  
1021      /**
1022       * Decrypt a file
1023       *
1024       * @param resource $ifp
1025       * @param resource $ofp
1026       * @param int $mlen
1027       * @param string $nonce
1028       * @param string $key
1029       * @return bool
1030       * @throws SodiumException
1031       * @throws TypeError
1032       */
1033      protected static function secretbox_decrypt($ifp, $ofp, $mlen, $nonce, $key)
1034      {
1035          if (PHP_INT_SIZE === 4) {
1036              return self::secretbox_decrypt_core32($ifp, $ofp, $mlen, $nonce, $key);
1037          }
1038          $tag = fread($ifp, 16);
1039          if (!is_string($tag)) {
1040              throw new SodiumException('Could not read input file');
1041          }
1042  
1043          /** @var string $subkey */
1044          $subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);
1045  
1046          /** @var string $realNonce */
1047          $realNonce = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
1048  
1049          /** @var string $block0 */
1050          $block0 = ParagonIE_Sodium_Core_Salsa20::salsa20(
1051              64,
1052              ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
1053              $subkey
1054          );
1055  
1056          /* Verify the Poly1305 MAC -before- attempting to decrypt! */
1057          $state = new ParagonIE_Sodium_Core_Poly1305_State(self::substr($block0, 0, 32));
1058          if (!self::onetimeauth_verify($state, $ifp, $tag, $mlen)) {
1059              throw new SodiumException('Invalid MAC');
1060          }
1061  
1062          /*
1063           * Set the cursor to the end of the first half-block. All future bytes will
1064           * generated from salsa20_xor_ic, starting from 1 (second block).
1065           */
1066          $first32 = fread($ifp, 32);
1067          if (!is_string($first32)) {
1068              throw new SodiumException('Could not read input file');
1069          }
1070          $first32len = self::strlen($first32);
1071          fwrite(
1072              $ofp,
1073              self::xorStrings(
1074                  self::substr($block0, 32, $first32len),
1075                  self::substr($first32, 0, $first32len)
1076              )
1077          );
1078          $mlen -= 32;
1079  
1080          /** @var int $iter */
1081          $iter = 1;
1082  
1083          /** @var int $incr */
1084          $incr = self::BUFFER_SIZE >> 6;
1085  
1086          /* Decrypts ciphertext, writes to output file. */
1087          while ($mlen > 0) {
1088              $blockSize = $mlen > self::BUFFER_SIZE
1089                  ? self::BUFFER_SIZE
1090                  : $mlen;
1091              $ciphertext = fread($ifp, $blockSize);
1092              if (!is_string($ciphertext)) {
1093                  throw new SodiumException('Could not read input file');
1094              }
1095              $pBlock = ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
1096                  $ciphertext,
1097                  $realNonce,
1098                  $iter,
1099                  $subkey
1100              );
1101              fwrite($ofp, $pBlock, $blockSize);
1102              $mlen -= $blockSize;
1103              $iter += $incr;
1104          }
1105          return true;
1106      }
1107  
1108      /**
1109       * @param ParagonIE_Sodium_Core_Poly1305_State $state
1110       * @param resource $ifp
1111       * @param string $tag
1112       * @param int $mlen
1113       * @return bool
1114       * @throws SodiumException
1115       * @throws TypeError
1116       */
1117      protected static function onetimeauth_verify(
1118          ParagonIE_Sodium_Core_Poly1305_State $state,
1119          $ifp,
1120          $tag = '',
1121          $mlen = 0
1122      ) {
1123          /** @var int $pos */
1124          $pos = self::ftell($ifp);
1125  
1126          /** @var int $iter */
1127          $iter = 1;
1128  
1129          /** @var int $incr */
1130          $incr = self::BUFFER_SIZE >> 6;
1131  
1132          while ($mlen > 0) {
1133              $blockSize = $mlen > self::BUFFER_SIZE
1134                  ? self::BUFFER_SIZE
1135                  : $mlen;
1136              $ciphertext = fread($ifp, $blockSize);
1137              if (!is_string($ciphertext)) {
1138                  throw new SodiumException('Could not read input file');
1139              }
1140              $state->update($ciphertext);
1141              $mlen -= $blockSize;
1142              $iter += $incr;
1143          }
1144          $res = ParagonIE_Sodium_Core_Util::verify_16($tag, $state->finish());
1145  
1146          fseek($ifp, $pos, SEEK_SET);
1147          return $res;
1148      }
1149  
1150      /**
1151       * Update a hash context with the contents of a file, without
1152       * loading the entire file into memory.
1153       *
1154       * @param resource|HashContext $hash
1155       * @param resource $fp
1156       * @param int $size
1157       * @return resource|object Resource on PHP < 7.2, HashContext object on PHP >= 7.2
1158       * @throws SodiumException
1159       * @throws TypeError
1160       * @psalm-suppress PossiblyInvalidArgument
1161       *                 PHP 7.2 changes from a resource to an object,
1162       *                 which causes Psalm to complain about an error.
1163       * @psalm-suppress TypeCoercion
1164       *                 Ditto.
1165       */
1166      public static function updateHashWithFile($hash, $fp, $size = 0)
1167      {
1168          /* Type checks: */
1169          if (PHP_VERSION_ID < 70200) {
1170              if (!is_resource($hash)) {
1171                  throw new TypeError('Argument 1 must be a resource, ' . gettype($hash) . ' given.');
1172              }
1173          } else {
1174              if (!is_object($hash)) {
1175                  throw new TypeError('Argument 1 must be an object (PHP 7.2+), ' . gettype($hash) . ' given.');
1176              }
1177          }
1178  
1179          if (!is_resource($fp)) {
1180              throw new TypeError('Argument 2 must be a resource, ' . gettype($fp) . ' given.');
1181          }
1182          if (!is_int($size)) {
1183              throw new TypeError('Argument 3 must be an integer, ' . gettype($size) . ' given.');
1184          }
1185  
1186          /** @var int $originalPosition */
1187          $originalPosition = self::ftell($fp);
1188  
1189          // Move file pointer to beginning of file
1190          fseek($fp, 0, SEEK_SET);
1191          for ($i = 0; $i < $size; $i += self::BUFFER_SIZE) {
1192              /** @var string|bool $message */
1193              $message = fread(
1194                  $fp,
1195                  ($size - $i) > self::BUFFER_SIZE
1196                      ? $size - $i
1197                      : self::BUFFER_SIZE
1198              );
1199              if (!is_string($message)) {
1200                  throw new SodiumException('Unexpected error reading from file.');
1201              }
1202              /** @var string $message */
1203              /** @psalm-suppress InvalidArgument */
1204              self::hash_update($hash, $message);
1205          }
1206          // Reset file pointer's position
1207          fseek($fp, $originalPosition, SEEK_SET);
1208          return $hash;
1209      }
1210  
1211      /**
1212       * Sign a file (rather than a string). Uses less memory than
1213       * ParagonIE_Sodium_Compat::crypto_sign_detached(), but produces
1214       * the same result. (32-bit)
1215       *
1216       * @param string $filePath  Absolute path to a file on the filesystem
1217       * @param string $secretKey Secret signing key
1218       *
1219       * @return string           Ed25519 signature
1220       * @throws SodiumException
1221       * @throws TypeError
1222       */
1223      private static function sign_core32($filePath, $secretKey)
1224      {
1225          $size = filesize($filePath);
1226          if (!is_int($size)) {
1227              throw new SodiumException('Could not obtain the file size');
1228          }
1229  
1230          $fp = fopen($filePath, 'rb');
1231          if (!is_resource($fp)) {
1232              throw new SodiumException('Could not open input file for reading');
1233          }
1234  
1235          /** @var string $az */
1236          $az = hash('sha512', self::substr($secretKey, 0, 32), true);
1237  
1238          $az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
1239          $az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);
1240  
1241          $hs = hash_init('sha512');
1242          self::hash_update($hs, self::substr($az, 32, 32));
1243          /** @var resource $hs */
1244          $hs = self::updateHashWithFile($hs, $fp, $size);
1245  
1246          $nonceHash = hash_final($hs, true);
1247          $pk = self::substr($secretKey, 32, 32);
1248          $nonce = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($nonceHash) . self::substr($nonceHash, 32);
1249          $sig = ParagonIE_Sodium_Core32_Ed25519::ge_p3_tobytes(
1250              ParagonIE_Sodium_Core32_Ed25519::ge_scalarmult_base($nonce)
1251          );
1252  
1253          $hs = hash_init('sha512');
1254          self::hash_update($hs, self::substr($sig, 0, 32));
1255          self::hash_update($hs, self::substr($pk, 0, 32));
1256          /** @var resource $hs */
1257          $hs = self::updateHashWithFile($hs, $fp, $size);
1258  
1259          $hramHash = hash_final($hs, true);
1260  
1261          $hram = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($hramHash);
1262  
1263          $sigAfter = ParagonIE_Sodium_Core32_Ed25519::sc_muladd($hram, $az, $nonce);
1264  
1265          /** @var string $sig */
1266          $sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);
1267  
1268          try {
1269              ParagonIE_Sodium_Compat::memzero($az);
1270          } catch (SodiumException $ex) {
1271              $az = null;
1272          }
1273          fclose($fp);
1274          return $sig;
1275      }
1276  
1277      /**
1278       *
1279       * Verify a file (rather than a string). Uses less memory than
1280       * ParagonIE_Sodium_Compat::crypto_sign_verify_detached(), but
1281       * produces the same result. (32-bit)
1282       *
1283       * @param string $sig       Ed25519 signature
1284       * @param string $filePath  Absolute path to a file on the filesystem
1285       * @param string $publicKey Signing public key
1286       *
1287       * @return bool
1288       * @throws SodiumException
1289       * @throws Exception
1290       */
1291      public static function verify_core32($sig, $filePath, $publicKey)
1292      {
1293          /* Security checks */
1294          if (ParagonIE_Sodium_Core32_Ed25519::check_S_lt_L(self::substr($sig, 32, 32))) {
1295              throw new SodiumException('S < L - Invalid signature');
1296          }
1297          if (ParagonIE_Sodium_Core32_Ed25519::small_order($sig)) {
1298              throw new SodiumException('Signature is on too small of an order');
1299          }
1300  
1301          if ((self::chrToInt($sig[63]) & 224) !== 0) {
1302              throw new SodiumException('Invalid signature');
1303          }
1304          $d = 0;
1305          for ($i = 0; $i < 32; ++$i) {
1306              $d |= self::chrToInt($publicKey[$i]);
1307          }
1308          if ($d === 0) {
1309              throw new SodiumException('All zero public key');
1310          }
1311  
1312          /** @var int|bool $size */
1313          $size = filesize($filePath);
1314          if (!is_int($size)) {
1315              throw new SodiumException('Could not obtain the file size');
1316          }
1317          /** @var int $size */
1318  
1319          /** @var resource|bool $fp */
1320          $fp = fopen($filePath, 'rb');
1321          if (!is_resource($fp)) {
1322              throw new SodiumException('Could not open input file for reading');
1323          }
1324          /** @var resource $fp */
1325  
1326          /** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
1327          $orig = ParagonIE_Sodium_Compat::$fastMult;
1328  
1329          // Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
1330          ParagonIE_Sodium_Compat::$fastMult = true;
1331  
1332          /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $A */
1333          $A = ParagonIE_Sodium_Core32_Ed25519::ge_frombytes_negate_vartime($publicKey);
1334  
1335          $hs = hash_init('sha512');
1336          self::hash_update($hs, self::substr($sig, 0, 32));
1337          self::hash_update($hs, self::substr($publicKey, 0, 32));
1338          /** @var resource $hs */
1339          $hs = self::updateHashWithFile($hs, $fp, $size);
1340          /** @var string $hDigest */
1341          $hDigest = hash_final($hs, true);
1342  
1343          /** @var string $h */
1344          $h = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($hDigest) . self::substr($hDigest, 32);
1345  
1346          /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P2 $R */
1347          $R = ParagonIE_Sodium_Core32_Ed25519::ge_double_scalarmult_vartime(
1348              $h,
1349              $A,
1350              self::substr($sig, 32)
1351          );
1352  
1353          /** @var string $rcheck */
1354          $rcheck = ParagonIE_Sodium_Core32_Ed25519::ge_tobytes($R);
1355  
1356          // Close the file handle
1357          fclose($fp);
1358  
1359          // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
1360          ParagonIE_Sodium_Compat::$fastMult = $orig;
1361          return self::verify_32($rcheck, self::substr($sig, 0, 32));
1362      }
1363  
1364      /**
1365       * Encrypt a file (32-bit)
1366       *
1367       * @param resource $ifp
1368       * @param resource $ofp
1369       * @param int $mlen
1370       * @param string $nonce
1371       * @param string $key
1372       * @return bool
1373       * @throws SodiumException
1374       * @throws TypeError
1375       */
1376      protected static function secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $key)
1377      {
1378          $plaintext = fread($ifp, 32);
1379          if (!is_string($plaintext)) {
1380              throw new SodiumException('Could not read input file');
1381          }
1382          $first32 = self::ftell($ifp);
1383  
1384          /** @var string $subkey */
1385          $subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);
1386  
1387          /** @var string $realNonce */
1388          $realNonce = ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);
1389  
1390          /** @var string $block0 */
1391          $block0 = str_repeat("\x00", 32);
1392  
1393          /** @var int $mlen - Length of the plaintext message */
1394          $mlen0 = $mlen;
1395          if ($mlen0 > 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES) {
1396              $mlen0 = 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES;
1397          }
1398          $block0 .= ParagonIE_Sodium_Core32_Util::substr($plaintext, 0, $mlen0);
1399  
1400          /** @var string $block0 */
1401          $block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor(
1402              $block0,
1403              $realNonce,
1404              $subkey
1405          );
1406  
1407          $state = new ParagonIE_Sodium_Core32_Poly1305_State(
1408              ParagonIE_Sodium_Core32_Util::substr(
1409                  $block0,
1410                  0,
1411                  ParagonIE_Sodium_Crypto::onetimeauth_poly1305_KEYBYTES
1412              )
1413          );
1414  
1415          // Pre-write 16 blank bytes for the Poly1305 tag
1416          $start = self::ftell($ofp);
1417          fwrite($ofp, str_repeat("\x00", 16));
1418  
1419          /** @var string $c */
1420          $cBlock = ParagonIE_Sodium_Core32_Util::substr(
1421              $block0,
1422              ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES
1423          );
1424          $state->update($cBlock);
1425          fwrite($ofp, $cBlock);
1426          $mlen -= 32;
1427  
1428          /** @var int $iter */
1429          $iter = 1;
1430  
1431          /** @var int $incr */
1432          $incr = self::BUFFER_SIZE >> 6;
1433  
1434          /*
1435           * Set the cursor to the end of the first half-block. All future bytes will
1436           * generated from salsa20_xor_ic, starting from 1 (second block).
1437           */
1438          fseek($ifp, $first32, SEEK_SET);
1439  
1440          while ($mlen > 0) {
1441              $blockSize = $mlen > self::BUFFER_SIZE
1442                  ? self::BUFFER_SIZE
1443                  : $mlen;
1444              $plaintext = fread($ifp, $blockSize);
1445              if (!is_string($plaintext)) {
1446                  throw new SodiumException('Could not read input file');
1447              }
1448              $cBlock = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
1449                  $plaintext,
1450                  $realNonce,
1451                  $iter,
1452                  $subkey
1453              );
1454              fwrite($ofp, $cBlock, $blockSize);
1455              $state->update($cBlock);
1456  
1457              $mlen -= $blockSize;
1458              $iter += $incr;
1459          }
1460          try {
1461              ParagonIE_Sodium_Compat::memzero($block0);
1462              ParagonIE_Sodium_Compat::memzero($subkey);
1463          } catch (SodiumException $ex) {
1464              $block0 = null;
1465              $subkey = null;
1466          }
1467          $end = self::ftell($ofp);
1468  
1469          /*
1470           * Write the Poly1305 authentication tag that provides integrity
1471           * over the ciphertext (encrypt-then-MAC)
1472           */
1473          fseek($ofp, $start, SEEK_SET);
1474          fwrite($ofp, $state->finish(), ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_MACBYTES);
1475          fseek($ofp, $end, SEEK_SET);
1476          unset($state);
1477  
1478          return true;
1479      }
1480  
1481      /**
1482       * Decrypt a file (32-bit)
1483       *
1484       * @param resource $ifp
1485       * @param resource $ofp
1486       * @param int $mlen
1487       * @param string $nonce
1488       * @param string $key
1489       * @return bool
1490       * @throws SodiumException
1491       * @throws TypeError
1492       */
1493      protected static function secretbox_decrypt_core32($ifp, $ofp, $mlen, $nonce, $key)
1494      {
1495          $tag = fread($ifp, 16);
1496          if (!is_string($tag)) {
1497              throw new SodiumException('Could not read input file');
1498          }
1499  
1500          /** @var string $subkey */
1501          $subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);
1502  
1503          /** @var string $realNonce */
1504          $realNonce = ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);
1505  
1506          /** @var string $block0 */
1507          $block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20(
1508              64,
1509              ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
1510              $subkey
1511          );
1512  
1513          /* Verify the Poly1305 MAC -before- attempting to decrypt! */
1514          $state = new ParagonIE_Sodium_Core32_Poly1305_State(self::substr($block0, 0, 32));
1515          if (!self::onetimeauth_verify_core32($state, $ifp, $tag, $mlen)) {
1516              throw new SodiumException('Invalid MAC');
1517          }
1518  
1519          /*
1520           * Set the cursor to the end of the first half-block. All future bytes will
1521           * generated from salsa20_xor_ic, starting from 1 (second block).
1522           */
1523          $first32 = fread($ifp, 32);
1524          if (!is_string($first32)) {
1525              throw new SodiumException('Could not read input file');
1526          }
1527          $first32len = self::strlen($first32);
1528          fwrite(
1529              $ofp,
1530              self::xorStrings(
1531                  self::substr($block0, 32, $first32len),
1532                  self::substr($first32, 0, $first32len)
1533              )
1534          );
1535          $mlen -= 32;
1536  
1537          /** @var int $iter */
1538          $iter = 1;
1539  
1540          /** @var int $incr */
1541          $incr = self::BUFFER_SIZE >> 6;
1542  
1543          /* Decrypts ciphertext, writes to output file. */
1544          while ($mlen > 0) {
1545              $blockSize = $mlen > self::BUFFER_SIZE
1546                  ? self::BUFFER_SIZE
1547                  : $mlen;
1548              $ciphertext = fread($ifp, $blockSize);
1549              if (!is_string($ciphertext)) {
1550                  throw new SodiumException('Could not read input file');
1551              }
1552              $pBlock = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
1553                  $ciphertext,
1554                  $realNonce,
1555                  $iter,
1556                  $subkey
1557              );
1558              fwrite($ofp, $pBlock, $blockSize);
1559              $mlen -= $blockSize;
1560              $iter += $incr;
1561          }
1562          return true;
1563      }
1564  
1565      /**
1566       * One-time message authentication for 32-bit systems
1567       *
1568       * @param ParagonIE_Sodium_Core32_Poly1305_State $state
1569       * @param resource $ifp
1570       * @param string $tag
1571       * @param int $mlen
1572       * @return bool
1573       * @throws SodiumException
1574       * @throws TypeError
1575       */
1576      protected static function onetimeauth_verify_core32(
1577          ParagonIE_Sodium_Core32_Poly1305_State $state,
1578          $ifp,
1579          $tag = '',
1580          $mlen = 0
1581      ) {
1582          /** @var int $pos */
1583          $pos = self::ftell($ifp);
1584  
1585          while ($mlen > 0) {
1586              $blockSize = $mlen > self::BUFFER_SIZE
1587                  ? self::BUFFER_SIZE
1588                  : $mlen;
1589              $ciphertext = fread($ifp, $blockSize);
1590              if (!is_string($ciphertext)) {
1591                  throw new SodiumException('Could not read input file');
1592              }
1593              $state->update($ciphertext);
1594              $mlen -= $blockSize;
1595          }
1596          $res = ParagonIE_Sodium_Core32_Util::verify_16($tag, $state->finish());
1597  
1598          fseek($ifp, $pos, SEEK_SET);
1599          return $res;
1600      }
1601  
1602      /**
1603       * @param resource $resource
1604       * @return int
1605       * @throws SodiumException
1606       */
1607      private static function ftell($resource)
1608      {
1609          $return = ftell($resource);
1610          if (!is_int($return)) {
1611              throw new SodiumException('ftell() returned false');
1612          }
1613          return (int) $return;
1614      }
1615  }


Generated : Wed Apr 22 08:20:11 2026 Cross-referenced by PHPXref