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