DefuseCryptoFile

The WooCommerce Germanized Defuse Crypto File class.

Defined (1)

The class is defined in the following location(s).

/includes/gateways/direct-debit/libraries/php-encryption/File.php  
  1. final class File 
  2. /** 
  3. * Encrypts the input file, saving the ciphertext to the output file. 
  4. * @param string $inputFilename 
  5. * @param string $outputFilename 
  6. * @param Key $key 
  7. * @throws Defuse\Crypto\Exception\EnvironmentIsBrokenException 
  8. * @throws Defuse\Crypto\Exception\IOException 
  9. */ 
  10. public static function encryptFile($inputFilename, $outputFilename, Key $key) 
  11. self::encryptFileInternal( 
  12. $inputFilename,  
  13. $outputFilename,  
  14. KeyOrPassword::createFromKey($key) 
  15. ); 
  16.  
  17. /** 
  18. * Encrypts a file with a password, using a slow key derivation function to 
  19. * make password cracking more expensive. 
  20. * @param string $inputFilename 
  21. * @param string $outputFilename 
  22. * @param string $password 
  23. * @throws Defuse\Crypto\Exception\EnvironmentIsBrokenException 
  24. * @throws Defuse\Crypto\Exception\IOException 
  25. */ 
  26. public static function encryptFileWithPassword($inputFilename, $outputFilename, $password) 
  27. self::encryptFileInternal( 
  28. $inputFilename,  
  29. $outputFilename,  
  30. KeyOrPassword::createFromPassword($password) 
  31. ); 
  32.  
  33. /** 
  34. * Decrypts the input file, saving the plaintext to the output file. 
  35. * @param string $inputFilename 
  36. * @param string $outputFilename 
  37. * @param Key $key 
  38. * @throws Defuse\Crypto\Exception\EnvironmentIsBrokenException 
  39. * @throws Defuse\Crypto\Exception\IOException 
  40. * @throws Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException 
  41. */ 
  42. public static function decryptFile($inputFilename, $outputFilename, Key $key) 
  43. self::decryptFileInternal( 
  44. $inputFilename,  
  45. $outputFilename,  
  46. KeyOrPassword::createFromKey($key) 
  47. ); 
  48.  
  49. /** 
  50. * Decrypts a file with a password, using a slow key derivation function to 
  51. * make password cracking more expensive. 
  52. * @param string $inputFilename 
  53. * @param string $outputFilename 
  54. * @param string $password 
  55. * @throws Defuse\Crypto\Exception\EnvironmentIsBrokenException 
  56. * @throws Defuse\Crypto\Exception\IOException 
  57. * @throws Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException 
  58. */ 
  59. public static function decryptFileWithPassword($inputFilename, $outputFilename, $password) 
  60. self::decryptFileInternal( 
  61. $inputFilename,  
  62. $outputFilename,  
  63. KeyOrPassword::createFromPassword($password) 
  64. ); 
  65.  
  66. /** 
  67. * Takes two resource handles and encrypts the contents of the first,  
  68. * writing the ciphertext into the second. 
  69. * @param resource $inputHandle 
  70. * @param resource $outputHandle 
  71. * @param Key $key 
  72. * @throws Defuse\Crypto\Exception\EnvironmentIsBrokenException 
  73. * @throws Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException 
  74. */ 
  75. public static function encryptResource($inputHandle, $outputHandle, Key $key) 
  76. self::encryptResourceInternal( 
  77. $inputHandle,  
  78. $outputHandle,  
  79. KeyOrPassword::createFromKey($key) 
  80. ); 
  81.  
  82. /** 
  83. * Encrypts the contents of one resource handle into another with a 
  84. * password, using a slow key derivation function to make password cracking 
  85. * more expensive. 
  86. * @param resource $inputHandle 
  87. * @param resource $outputHandle 
  88. * @param string $password 
  89. * @throws Defuse\Crypto\Exception\EnvironmentIsBrokenException 
  90. * @throws Defuse\Crypto\Exception\IOException 
  91. * @throws Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException 
  92. */ 
  93. public static function encryptResourceWithPassword($inputHandle, $outputHandle, $password) 
  94. self::encryptResourceInternal( 
  95. $inputHandle,  
  96. $outputHandle,  
  97. KeyOrPassword::createFromPassword($password) 
  98. ); 
  99.  
  100. /** 
  101. * Takes two resource handles and decrypts the contents of the first,  
  102. * writing the plaintext into the second. 
  103. * @param resource $inputHandle 
  104. * @param resource $outputHandle 
  105. * @param Key $key 
  106. * @throws Defuse\Crypto\Exception\EnvironmentIsBrokenException 
  107. * @throws Defuse\Crypto\Exception\IOException 
  108. * @throws Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException 
  109. */ 
  110. public static function decryptResource($inputHandle, $outputHandle, Key $key) 
  111. self::decryptResourceInternal( 
  112. $inputHandle,  
  113. $outputHandle,  
  114. KeyOrPassword::createFromKey($key) 
  115. ); 
  116.  
  117. /** 
  118. * Decrypts the contents of one resource into another with a password, using 
  119. * a slow key derivation function to make password cracking more expensive. 
  120. * @param resource $inputHandle 
  121. * @param resource $outputHandle 
  122. * @param string $password 
  123. * @throws Defuse\Crypto\Exception\EnvironmentIsBrokenException 
  124. * @throws Defuse\Crypto\Exception\IOException 
  125. * @throws Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException 
  126. */ 
  127. public static function decryptResourceWithPassword($inputHandle, $outputHandle, $password) 
  128. self::decryptResourceInternal( 
  129. $inputHandle,  
  130. $outputHandle,  
  131. KeyOrPassword::createFromPassword($password) 
  132. ); 
  133.  
  134. /** 
  135. * Encrypts a file with either a key or a password. 
  136. * @param string $inputFilename 
  137. * @param string $outputFilename 
  138. * @param KeyOrPassword $secret 
  139. * @throws Defuse\Crypto\Exception\EnvironmentIsBrokenException 
  140. * @throws Defuse\Crypto\Exception\IOException 
  141. */ 
  142. private static function encryptFileInternal($inputFilename, $outputFilename, KeyOrPassword $secret) 
  143. /** Open the input file. */ 
  144. $if = @\fopen($inputFilename, 'rb'); 
  145. if ($if === false) { 
  146. throw new Ex\IOException( 
  147. 'Cannot open input file for encrypting: ' . 
  148. self::getLastErrorMessage() 
  149. ); 
  150. /** This call can fail, but the only consequence is performance. */ 
  151. \stream_set_read_buffer($if, 0); 
  152.  
  153. /** Open the output file. */ 
  154. $of = @\fopen($outputFilename, 'wb'); 
  155. if ($of === false) { 
  156. \fclose($if); 
  157. throw new Ex\IOException( 
  158. 'Cannot open output file for encrypting: ' . 
  159. self::getLastErrorMessage() 
  160. ); 
  161. /** This call can fail, but the only consequence is performance. */ 
  162. \stream_set_write_buffer($of, 0); 
  163.  
  164. /** Perform the encryption. */ 
  165. try { 
  166. self::encryptResourceInternal($if, $of, $secret); 
  167. } catch (Ex\CryptoException $ex) { 
  168. \fclose($if); 
  169. \fclose($of); 
  170. throw $ex; 
  171.  
  172. /** Close the input file. */ 
  173. if (\fclose($if) === false) { 
  174. \fclose($of); 
  175. throw new Ex\IOException( 
  176. 'Cannot close input file after encrypting' 
  177. ); 
  178.  
  179. /** Close the output file. */ 
  180. if (\fclose($of) === false) { 
  181. throw new Ex\IOException( 
  182. 'Cannot close output file after encrypting' 
  183. ); 
  184.  
  185. /** 
  186. * Decrypts a file with either a key or a password. 
  187. * @param string $inputFilename 
  188. * @param string $outputFilename 
  189. * @param KeyOrPassword $secret 
  190. * @throws Defuse\Crypto\Exception\EnvironmentIsBrokenException 
  191. * @throws Defuse\Crypto\Exception\IOException 
  192. * @throws Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException 
  193. */ 
  194. private static function decryptFileInternal($inputFilename, $outputFilename, KeyOrPassword $secret) 
  195. /** Open the input file. */ 
  196. $if = @\fopen($inputFilename, 'rb'); 
  197. if ($if === false) { 
  198. throw new Ex\IOException( 
  199. 'Cannot open input file for decrypting: ' . 
  200. self::getLastErrorMessage() 
  201. ); 
  202. /** This call can fail, but the only consequence is performance. */ 
  203. \stream_set_read_buffer($if, 0); 
  204.  
  205. /** Open the output file. */ 
  206. $of = @\fopen($outputFilename, 'wb'); 
  207. if ($of === false) { 
  208. \fclose($if); 
  209. throw new Ex\IOException( 
  210. 'Cannot open output file for decrypting: ' . 
  211. self::getLastErrorMessage() 
  212. ); 
  213. /** This call can fail, but the only consequence is performance. */ 
  214. \stream_set_write_buffer($of, 0); 
  215.  
  216. /** Perform the decryption. */ 
  217. try { 
  218. self::decryptResourceInternal($if, $of, $secret); 
  219. } catch (Ex\CryptoException $ex) { 
  220. \fclose($if); 
  221. \fclose($of); 
  222. throw $ex; 
  223.  
  224. /** Close the input file. */ 
  225. if (\fclose($if) === false) { 
  226. \fclose($of); 
  227. throw new Ex\IOException( 
  228. 'Cannot close input file after decrypting' 
  229. ); 
  230.  
  231. /** Close the output file. */ 
  232. if (\fclose($of) === false) { 
  233. throw new Ex\IOException( 
  234. 'Cannot close output file after decrypting' 
  235. ); 
  236.  
  237. /** 
  238. * Encrypts a resource with either a key or a password. 
  239. * @param resource $inputHandle 
  240. * @param resource $outputHandle 
  241. * @param KeyOrPassword $secret 
  242. * @throws Defuse\Crypto\Exception\EnvironmentIsBrokenException 
  243. * @throws Defuse\Crypto\Exception\IOException 
  244. */ 
  245. private static function encryptResourceInternal($inputHandle, $outputHandle, KeyOrPassword $secret) 
  246. if (! \is_resource($inputHandle)) { 
  247. throw new Ex\IOException( 
  248. 'Input handle must be a resource!' 
  249. ); 
  250. if (! \is_resource($outputHandle)) { 
  251. throw new Ex\IOException( 
  252. 'Output handle must be a resource!' 
  253. ); 
  254.  
  255. $inputStat = \fstat($inputHandle); 
  256. $inputSize = $inputStat['size']; 
  257.  
  258. $file_salt = Core::secureRandom(Core::SALT_BYTE_SIZE); 
  259. $keys = $secret->deriveKeys($file_salt); 
  260. $ekey = $keys->getEncryptionKey(); 
  261. $akey = $keys->getAuthenticationKey(); 
  262.  
  263. $ivsize = Core::BLOCK_BYTE_SIZE; 
  264. $iv = Core::secureRandom($ivsize); 
  265.  
  266. /** Initialize a streaming HMAC state. */ 
  267. $hmac = \hash_init(Core::HASH_FUNCTION_NAME, HASH_HMAC, $akey); 
  268. if ($hmac === false) { 
  269. throw new Ex\EnvironmentIsBrokenException( 
  270. 'Cannot initialize a hash context' 
  271. ); 
  272.  
  273. /** Write the header, salt, and IV. */ 
  274. self::writeBytes( 
  275. $outputHandle,  
  276. Core::CURRENT_VERSION . $file_salt . $iv,  
  277. Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + $ivsize 
  278. ); 
  279.  
  280. /** Add the header, salt, and IV to the HMAC. */ 
  281. \hash_update($hmac, Core::CURRENT_VERSION); 
  282. \hash_update($hmac, $file_salt); 
  283. \hash_update($hmac, $iv); 
  284.  
  285. /** $thisIv will be incremented after each call to the encryption. */ 
  286. $thisIv = $iv; 
  287.  
  288. /** How many blocks do we encrypt at a time? We increment by this value. */ 
  289. $inc = Core::BUFFER_BYTE_SIZE / Core::BLOCK_BYTE_SIZE; 
  290.  
  291. /** Loop until we reach the end of the input file. */ 
  292. $at_file_end = false; 
  293. while (! (\feof($inputHandle) || $at_file_end)) { 
  294. /** Find out if we can read a full buffer, or only a partial one. */ 
  295. $pos = \ftell($inputHandle); 
  296. if ($pos === false) { 
  297. throw new Ex\IOException( 
  298. 'Could not get current position in input file during encryption' 
  299. ); 
  300. if ($pos + Core::BUFFER_BYTE_SIZE >= $inputSize) { 
  301. /** We're at the end of the file, so we need to break out of the loop. */ 
  302. $at_file_end = true; 
  303. $read = self::readBytes( 
  304. $inputHandle,  
  305. $inputSize - $pos 
  306. ); 
  307. } else { 
  308. $read = self::readBytes( 
  309. $inputHandle,  
  310. Core::BUFFER_BYTE_SIZE 
  311. ); 
  312.  
  313. /** Encrypt this buffer. */ 
  314. $encrypted = \openssl_encrypt( 
  315. $read,  
  316. Core::CIPHER_METHOD,  
  317. $ekey,  
  318. OPENSSL_RAW_DATA,  
  319. $thisIv 
  320. ); 
  321.  
  322. if ($encrypted === false) { 
  323. throw new Ex\EnvironmentIsBrokenException( 
  324. 'OpenSSL encryption error' 
  325. ); 
  326.  
  327. /** Write this buffer's ciphertext. */ 
  328. self::writeBytes($outputHandle, $encrypted, Core::ourStrlen($encrypted)); 
  329. /** Add this buffer's ciphertext to the HMAC. */ 
  330. \hash_update($hmac, $encrypted); 
  331.  
  332. /** Increment the counter by the number of blocks in a buffer. */ 
  333. $thisIv = Core::incrementCounter($thisIv, $inc); 
  334. /** WARNING: Usually, unless the file is a multiple of the buffer 
  335. * size, $thisIv will contain an incorrect value here on the last 
  336. * iteration of this loop. */ 
  337.  
  338. /** Get the HMAC and append it to the ciphertext. */ 
  339. $final_mac = \hash_final($hmac, true); 
  340. self::writeBytes($outputHandle, $final_mac, CORE::MAC_BYTE_SIZE); 
  341.  
  342. /** 
  343. * Decrypts a file-backed resource with either a key or a password. 
  344. * @param resource $inputHandle 
  345. * @param resource $outputHandle 
  346. * @param KeyOrPassword $secret 
  347. * @throws Defuse\Crypto\Exception\EnvironmentIsBrokenException 
  348. * @throws Defuse\Crypto\Exception\IOException 
  349. * @throws Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException 
  350. */ 
  351. public static function decryptResourceInternal($inputHandle, $outputHandle, KeyOrPassword $secret) 
  352. if (! \is_resource($inputHandle)) { 
  353. throw new Ex\IOException( 
  354. 'Input handle must be a resource!' 
  355. ); 
  356. if (! \is_resource($outputHandle)) { 
  357. throw new Ex\IOException( 
  358. 'Output handle must be a resource!' 
  359. ); 
  360.  
  361. /** Make sure the file is big enough for all the reads we need to do. */ 
  362. $stat = \fstat($inputHandle); 
  363. if ($stat['size'] < Core::MINIMUM_CIPHERTEXT_SIZE) { 
  364. throw new Ex\WrongKeyOrModifiedCiphertextException( 
  365. 'Input file is too small to have been created by this library.' 
  366. ); 
  367.  
  368. /** Check the version header. */ 
  369. $header = self::readBytes($inputHandle, Core::HEADER_VERSION_SIZE); 
  370. if ($header !== Core::CURRENT_VERSION) { 
  371. throw new Ex\WrongKeyOrModifiedCiphertextException( 
  372. 'Bad version header.' 
  373. ); 
  374.  
  375. /** Get the salt. */ 
  376. $file_salt = self::readBytes($inputHandle, Core::SALT_BYTE_SIZE); 
  377.  
  378. /** Get the IV. */ 
  379. $ivsize = Core::BLOCK_BYTE_SIZE; 
  380. $iv = self::readBytes($inputHandle, $ivsize); 
  381.  
  382. /** Derive the authentication and encryption keys. */ 
  383. $keys = $secret->deriveKeys($file_salt); 
  384. $ekey = $keys->getEncryptionKey(); 
  385. $akey = $keys->getAuthenticationKey(); 
  386.  
  387. /** We'll store the MAC of each buffer-sized chunk as we verify the 
  388. * actual MAC, so that we can check them again when decrypting. */ 
  389. $macs = []; 
  390.  
  391. /** $thisIv will be incremented after each call to the decryption. */ 
  392. $thisIv = $iv; 
  393.  
  394. /** How many blocks do we encrypt at a time? We increment by this value. */ 
  395. $inc = Core::BUFFER_BYTE_SIZE / Core::BLOCK_BYTE_SIZE; 
  396.  
  397. /** Get the HMAC. */ 
  398. if (\fseek($inputHandle, (-1 * Core::MAC_BYTE_SIZE), SEEK_END) === false) { 
  399. throw new Ex\IOException( 
  400. 'Cannot seek to beginning of MAC within input file' 
  401. ); 
  402.  
  403. /** Get the position of the last byte in the actual ciphertext. */ 
  404. $cipher_end = \ftell($inputHandle); 
  405. if ($cipher_end === false) { 
  406. throw new Ex\IOException( 
  407. 'Cannot read input file' 
  408. ); 
  409. /** We have the position of the first byte of the HMAC. Go back by one. */ 
  410. --$cipher_end; 
  411.  
  412. /** Read the HMAC. */ 
  413. $stored_mac = self::readBytes($inputHandle, Core::MAC_BYTE_SIZE); 
  414.  
  415. /** Initialize a streaming HMAC state. */ 
  416. $hmac = \hash_init(Core::HASH_FUNCTION_NAME, HASH_HMAC, $akey); 
  417. if ($hmac === false) { 
  418. throw new Ex\EnvironmentIsBrokenException( 
  419. 'Cannot initialize a hash context' 
  420. ); 
  421.  
  422. /** Reset file pointer to the beginning of the file after the header */ 
  423. if (\fseek($inputHandle, Core::HEADER_VERSION_SIZE, SEEK_SET) === false) { 
  424. throw new Ex\IOException( 
  425. 'Cannot read seek within input file' 
  426. ); 
  427.  
  428. /** Seek to the start of the actual ciphertext. */ 
  429. if (\fseek($inputHandle, Core::SALT_BYTE_SIZE + $ivsize, SEEK_CUR) === false) { 
  430. throw new Ex\IOException( 
  431. 'Cannot seek input file to beginning of ciphertext' 
  432. ); 
  433.  
  434. /** PASS #1: Calculating the HMAC. */ 
  435.  
  436. \hash_update($hmac, $header); 
  437. \hash_update($hmac, $file_salt); 
  438. \hash_update($hmac, $iv); 
  439. $hmac2 = \hash_copy($hmac); 
  440.  
  441. $break = false; 
  442. while (! $break) { 
  443. $pos = \ftell($inputHandle); 
  444. if ($pos === false) { 
  445. throw new Ex\IOException( 
  446. 'Could not get current position in input file during decryption' 
  447. ); 
  448.  
  449. /** Read the next buffer-sized chunk (or less). */ 
  450. if ($pos + Core::BUFFER_BYTE_SIZE >= $cipher_end) { 
  451. $break = true; 
  452. $read = self::readBytes( 
  453. $inputHandle,  
  454. $cipher_end - $pos + 1 
  455. ); 
  456. } else { 
  457. $read = self::readBytes( 
  458. $inputHandle,  
  459. Core::BUFFER_BYTE_SIZE 
  460. ); 
  461.  
  462. /** Update the HMAC. */ 
  463. \hash_update($hmac, $read); 
  464.  
  465. /** Remember this buffer-sized chunk's HMAC. */ 
  466. $chunk_mac = \hash_copy($hmac); 
  467. if ($chunk_mac === false) { 
  468. throw new Ex\EnvironmentIsBrokenException( 
  469. 'Cannot duplicate a hash context' 
  470. ); 
  471. $macs []= \hash_final($chunk_mac); 
  472.  
  473. /** Get the final HMAC, which should match the stored one. */ 
  474. $final_mac = \hash_final($hmac, true); 
  475.  
  476. /** Verify the HMAC. */ 
  477. if (! Core::hashEquals($final_mac, $stored_mac)) { 
  478. throw new Ex\WrongKeyOrModifiedCiphertextException( 
  479. 'Integrity check failed.' 
  480. ); 
  481.  
  482. /** PASS #2: Decrypt and write output. */ 
  483.  
  484. /** Rewind to the start of the actual ciphertext. */ 
  485. if (\fseek($inputHandle, Core::SALT_BYTE_SIZE + $ivsize + Core::HEADER_VERSION_SIZE, SEEK_SET) === false) { 
  486. throw new Ex\IOException( 
  487. 'Could not move the input file pointer during decryption' 
  488. ); 
  489.  
  490. $at_file_end = false; 
  491. while (! $at_file_end) { 
  492. $pos = \ftell($inputHandle); 
  493. if ($pos === false) { 
  494. throw new Ex\IOException( 
  495. 'Could not get current position in input file during decryption' 
  496. ); 
  497.  
  498. /** Read the next buffer-sized chunk (or less). */ 
  499. if ($pos + Core::BUFFER_BYTE_SIZE >= $cipher_end) { 
  500. $at_file_end = true; 
  501. $read = self::readBytes( 
  502. $inputHandle,  
  503. $cipher_end - $pos + 1 
  504. ); 
  505. } else { 
  506. $read = self::readBytes( 
  507. $inputHandle,  
  508. Core::BUFFER_BYTE_SIZE 
  509. ); 
  510.  
  511. /** Recalculate the MAC (so far) and compare it with the one we 
  512. * remembered from pass #1 to ensure attackers didn't change the 
  513. * ciphertext after MAC verification. */ 
  514. \hash_update($hmac2, $read); 
  515. $calc_mac = \hash_copy($hmac2); 
  516. if ($calc_mac === false) { 
  517. throw new Ex\EnvironmentIsBrokenException( 
  518. 'Cannot duplicate a hash context' 
  519. ); 
  520. $calc = \hash_final($calc_mac); 
  521.  
  522. if (empty($macs)) { 
  523. throw new Ex\WrongKeyOrModifiedCiphertextException( 
  524. 'File was modified after MAC verification' 
  525. ); 
  526. } elseif (! Core::hashEquals(\array_shift($macs), $calc)) { 
  527. throw new Ex\WrongKeyOrModifiedCiphertextException( 
  528. 'File was modified after MAC verification' 
  529. ); 
  530.  
  531. /** Decrypt this buffer-sized chunk. */ 
  532. $decrypted = \openssl_decrypt( 
  533. $read,  
  534. Core::CIPHER_METHOD,  
  535. $ekey,  
  536. OPENSSL_RAW_DATA,  
  537. $thisIv 
  538. ); 
  539. if ($decrypted === false) { 
  540. throw new Ex\EnvironmentIsBrokenException( 
  541. 'OpenSSL decryption error' 
  542. ); 
  543.  
  544. /** Write the plaintext to the output file. */ 
  545. self::writeBytes( 
  546. $outputHandle,  
  547. $decrypted,  
  548. Core::ourStrlen($decrypted) 
  549. ); 
  550.  
  551. /** Increment the IV by the amount of blocks in a buffer. */ 
  552. $thisIv = Core::incrementCounter($thisIv, $inc); 
  553. /** WARNING: Usually, unless the file is a multiple of the buffer 
  554. * size, $thisIv will contain an incorrect value here on the last 
  555. * iteration of this loop. */ 
  556.  
  557. /** 
  558. * Read from a stream; prevent partial reads. 
  559. * @param resource $stream 
  560. * @param int $num_bytes 
  561. * @throws Defuse\Crypto\Exception\IOException 
  562. * @throws Defuse\Crypto\Exception\EnvironmentIsBrokenException 
  563. * @return string 
  564. */ 
  565. public static function readBytes($stream, $num_bytes) 
  566. if ($num_bytes < 0) { 
  567. throw new Ex\EnvironmentIsBrokenException( 
  568. 'Tried to read less than 0 bytes' 
  569. ); 
  570. } elseif ($num_bytes === 0) { 
  571. return ''; 
  572. $buf = ''; 
  573. $remaining = $num_bytes; 
  574. while ($remaining > 0 && ! \feof($stream)) { 
  575. $read = \fread($stream, $remaining); 
  576.  
  577. if ($read === false) { 
  578. throw new Ex\IOException( 
  579. 'Could not read from the file' 
  580. ); 
  581. $buf .= $read; 
  582. $remaining -= Core::ourStrlen($read); 
  583. if (Core::ourStrlen($buf) !== $num_bytes) { 
  584. throw new Ex\IOException( 
  585. 'Tried to read past the end of the file' 
  586. ); 
  587. return $buf; 
  588.  
  589. /** 
  590. * Write to a stream; prevents partial writes. 
  591. * @param resource $stream 
  592. * @param string $buf 
  593. * @param int $num_bytes 
  594. * @throws Defuse\Crypto\Exception\IOException 
  595. * @return string 
  596. */ 
  597. public static function writeBytes($stream, $buf, $num_bytes = null) 
  598. $bufSize = Core::ourStrlen($buf); 
  599. if ($num_bytes === null) { 
  600. $num_bytes = $bufSize; 
  601. if ($num_bytes > $bufSize) { 
  602. throw new Ex\IOException( 
  603. 'Trying to write more bytes than the buffer contains.' 
  604. ); 
  605. if ($num_bytes < 0) { 
  606. throw new Ex\IOException( 
  607. 'Tried to write less than 0 bytes' 
  608. ); 
  609. $remaining = $num_bytes; 
  610. while ($remaining > 0) { 
  611. $written = \fwrite($stream, $buf, $remaining); 
  612. if ($written === false) { 
  613. throw new Ex\IOException( 
  614. 'Could not write to the file' 
  615. ); 
  616. $buf = Core::ourSubstr($buf, $written, null); 
  617. $remaining -= $written; 
  618. return $num_bytes; 
  619.  
  620. /** 
  621. * Returns the last PHP error's or warning's message string. 
  622. * @return string 
  623. */ 
  624. private static function getLastErrorMessage() 
  625. $error = error_get_last(); 
  626. if ($error === null) { 
  627. return '[no PHP error]'; 
  628. } else { 
  629. return $error['message'];