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