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