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


Generated : Thu Oct 22 08:20:02 2020 Cross-referenced by PHPXref