/htdocs/wp-includes/sodium_compat/src/File.php

https://gitlab.com/VTTE/sitios-vtte · PHP · 1452 lines · 875 code · 167 blank · 410 comment · 127 complexity · 8d64b79b41ac0117af502644b56d37d8 MD5 · raw file

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