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


Generated : Thu Nov 21 08:20:01 2024 Cross-referenced by PHPXref