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