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