[ Index ]

PHP Cross Reference of WordPress

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


Generated: Tue Mar 19 01:00:02 2024 Cross-referenced by PHPXref 0.7.1