/vendor/zendframework/zend-crypt/src/FileCipher.php

https://gitlab.com/yousafsyed/easternglamor · PHP · 374 lines · 201 code · 35 blank · 138 comment · 18 complexity · e8d294b6fb78b39eeb2a5333a70dfca1 MD5 · raw file

  1. <?php
  2. /**
  3. * Zend Framework (http://framework.zend.com/)
  4. *
  5. * @link http://github.com/zendframework/zf2 for the canonical source repository
  6. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  7. * @license http://framework.zend.com/license/new-bsd New BSD License
  8. */
  9. namespace Zend\Crypt;
  10. use Zend\Crypt\Key\Derivation\Pbkdf2;
  11. use Zend\Crypt\Symmetric\Mcrypt;
  12. use Zend\Crypt\Symmetric\SymmetricInterface;
  13. use Zend\Math\Rand;
  14. /**
  15. * Encrypt/decrypt a file using a symmetric cipher in CBC mode
  16. * then authenticate using HMAC
  17. */
  18. class FileCipher
  19. {
  20. const BUFFER_SIZE = 1048576; // 16 * 65536 bytes = 1 Mb
  21. /**
  22. * Hash algorithm for Pbkdf2
  23. *
  24. * @var string
  25. */
  26. protected $pbkdf2Hash = 'sha256';
  27. /**
  28. * Hash algorithm for HMAC
  29. *
  30. * @var string
  31. */
  32. protected $hash = 'sha256';
  33. /**
  34. * Number of iterations for Pbkdf2
  35. *
  36. * @var int
  37. */
  38. protected $keyIteration = 10000;
  39. /**
  40. * Key
  41. *
  42. * @var string
  43. */
  44. protected $key;
  45. /**
  46. * Cipher
  47. *
  48. * @var SymmetricInterface
  49. */
  50. protected $cipher;
  51. /**
  52. * Constructor
  53. *
  54. * @param SymmetricInterface $cipher
  55. */
  56. public function __construct()
  57. {
  58. $this->cipher = new Mcrypt;
  59. }
  60. /**
  61. * Set the cipher object
  62. *
  63. * @param SymmetricInterface $cipher
  64. */
  65. public function setCipher(SymmetricInterface $cipher)
  66. {
  67. $this->cipher = $cipher;
  68. }
  69. /**
  70. * Get the cipher object
  71. *
  72. * @return SymmetricInterface
  73. */
  74. public function getCipher()
  75. {
  76. return $this->cipher;
  77. }
  78. /**
  79. * Set the number of iterations for Pbkdf2
  80. *
  81. * @param int $num
  82. */
  83. public function setKeyIteration($num)
  84. {
  85. $this->keyIteration = (int) $num;
  86. }
  87. /**
  88. * Get the number of iterations for Pbkdf2
  89. *
  90. * @return int
  91. */
  92. public function getKeyIteration()
  93. {
  94. return $this->keyIteration;
  95. }
  96. /**
  97. * Set the encryption/decryption key
  98. *
  99. * @param string $key
  100. * @throws Exception\InvalidArgumentException
  101. */
  102. public function setKey($key)
  103. {
  104. if (empty($key)) {
  105. throw new Exception\InvalidArgumentException('The key cannot be empty');
  106. }
  107. $this->key = (string) $key;
  108. }
  109. /**
  110. * Get the key
  111. *
  112. * @return string|null
  113. */
  114. public function getKey()
  115. {
  116. return $this->key;
  117. }
  118. /**
  119. * Set algorithm of the symmetric cipher
  120. *
  121. * @param string $algo
  122. */
  123. public function setCipherAlgorithm($algo)
  124. {
  125. $this->cipher->setAlgorithm($algo);
  126. }
  127. /**
  128. * Get the cipher algorithm
  129. *
  130. * @return string|bool
  131. */
  132. public function getCipherAlgorithm()
  133. {
  134. return $this->cipher->getAlgorithm();
  135. }
  136. /**
  137. * Get the supported algorithms of the symmetric cipher
  138. *
  139. * @return array
  140. */
  141. public function getCipherSupportedAlgorithms()
  142. {
  143. return $this->cipher->getSupportedAlgorithms();
  144. }
  145. /**
  146. * Set the hash algorithm for HMAC authentication
  147. *
  148. * @param string $hash
  149. * @throws Exception\InvalidArgumentException
  150. */
  151. public function setHashAlgorithm($hash)
  152. {
  153. if (!Hash::isSupported($hash)) {
  154. throw new Exception\InvalidArgumentException(
  155. "The specified hash algorithm '{$hash}' is not supported by Zend\Crypt\Hash"
  156. );
  157. }
  158. $this->hash = (string) $hash;
  159. }
  160. /**
  161. * Get the hash algorithm for HMAC authentication
  162. *
  163. * @return string
  164. */
  165. public function getHashAlgorithm()
  166. {
  167. return $this->hash;
  168. }
  169. /**
  170. * Set the hash algorithm for the Pbkdf2
  171. *
  172. * @param string $hash
  173. * @throws Exception\InvalidArgumentException
  174. */
  175. public function setPbkdf2HashAlgorithm($hash)
  176. {
  177. if (!Hash::isSupported($hash)) {
  178. throw new Exception\InvalidArgumentException(
  179. "The specified hash algorithm '{$hash}' is not supported by Zend\Crypt\Hash"
  180. );
  181. }
  182. $this->pbkdf2Hash = (string) $hash;
  183. }
  184. /**
  185. * Get the Pbkdf2 hash algorithm
  186. *
  187. * @return string
  188. */
  189. public function getPbkdf2HashAlgorithm()
  190. {
  191. return $this->pbkdf2Hash;
  192. }
  193. /**
  194. * Encrypt then authenticate a file using HMAC
  195. *
  196. * @param string $fileIn
  197. * @param string $fileOut
  198. * @return bool
  199. * @throws Exception\InvalidArgumentException
  200. */
  201. public function encrypt($fileIn, $fileOut)
  202. {
  203. $this->checkFileInOut($fileIn, $fileOut);
  204. if (empty($this->key)) {
  205. throw new Exception\InvalidArgumentException('No key specified for encryption');
  206. }
  207. $read = fopen($fileIn, "r");
  208. $write = fopen($fileOut, "w");
  209. $iv = Rand::getBytes($this->cipher->getSaltSize(), true);
  210. $keys = Pbkdf2::calc($this->getPbkdf2HashAlgorithm(),
  211. $this->getKey(),
  212. $iv,
  213. $this->getKeyIteration(),
  214. $this->cipher->getKeySize() * 2);
  215. $hmac = '';
  216. $size = 0;
  217. $tot = filesize($fileIn);
  218. $padding = $this->cipher->getPadding();
  219. $this->cipher->setKey(substr($keys, 0, $this->cipher->getKeySize()));
  220. $this->cipher->setPadding(new Symmetric\Padding\NoPadding);
  221. $this->cipher->setSalt($iv);
  222. $this->cipher->setMode('cbc');
  223. $hashAlgo = $this->getHashAlgorithm();
  224. $saltSize = $this->cipher->getSaltSize();
  225. $algorithm = $this->cipher->getAlgorithm();
  226. $keyHmac = substr($keys, $this->cipher->getKeySize());
  227. while ($data = fread($read, self::BUFFER_SIZE)) {
  228. $size += strlen($data);
  229. // Padding if last block
  230. if ($size == $tot) {
  231. $this->cipher->setPadding($padding);
  232. }
  233. $result = $this->cipher->encrypt($data);
  234. if ($size <= self::BUFFER_SIZE) {
  235. // Write a placeholder for the HMAC and write the IV
  236. fwrite($write, str_repeat(0, Hmac::getOutputSize($hashAlgo)));
  237. } else {
  238. $result = substr($result, $saltSize);
  239. }
  240. $hmac = Hmac::compute($keyHmac,
  241. $hashAlgo,
  242. $algorithm . $hmac . $result);
  243. $this->cipher->setSalt(substr($result, -1 * $saltSize));
  244. if (fwrite($write, $result) !== strlen($result)) {
  245. return false;
  246. }
  247. }
  248. $result = true;
  249. // write the HMAC at the beginning of the file
  250. fseek($write, 0);
  251. if (fwrite($write, $hmac) !== strlen($hmac)) {
  252. $result = false;
  253. }
  254. fclose($write);
  255. fclose($read);
  256. return $result;
  257. }
  258. /**
  259. * Decrypt a file
  260. *
  261. * @param string $fileIn
  262. * @param string $fileOut
  263. * @param bool $compress
  264. * @return bool
  265. * @throws Exception\InvalidArgumentException
  266. */
  267. public function decrypt($fileIn, $fileOut)
  268. {
  269. $this->checkFileInOut($fileIn, $fileOut);
  270. if (empty($this->key)) {
  271. throw new Exception\InvalidArgumentException('No key specified for decryption');
  272. }
  273. $read = fopen($fileIn, "r");
  274. $write = fopen($fileOut, "w");
  275. $hmacRead = fread($read, Hmac::getOutputSize($this->getHashAlgorithm()));
  276. $iv = fread($read, $this->cipher->getSaltSize());
  277. $tot = filesize($fileIn);
  278. $hmac = $iv;
  279. $size = strlen($iv) + strlen($hmacRead);
  280. $keys = Pbkdf2::calc($this->getPbkdf2HashAlgorithm(),
  281. $this->getKey(),
  282. $iv,
  283. $this->getKeyIteration(),
  284. $this->cipher->getKeySize() * 2);
  285. $padding = $this->cipher->getPadding();
  286. $this->cipher->setPadding(new Symmetric\Padding\NoPadding);
  287. $this->cipher->setKey(substr($keys, 0, $this->cipher->getKeySize()));
  288. $this->cipher->setMode('cbc');
  289. $blockSize = $this->cipher->getBlockSize();
  290. $hashAlgo = $this->getHashAlgorithm();
  291. $algorithm = $this->cipher->getAlgorithm();
  292. $saltSize = $this->cipher->getSaltSize();
  293. $keyHmac = substr($keys, $this->cipher->getKeySize());
  294. while ($data = fread($read, self::BUFFER_SIZE)) {
  295. $size += strlen($data);
  296. // Unpadding if last block
  297. if ($size + $blockSize >= $tot) {
  298. $this->cipher->setPadding($padding);
  299. $data .= fread($read, $blockSize);
  300. }
  301. $result = $this->cipher->decrypt($iv . $data);
  302. $hmac = Hmac::compute($keyHmac,
  303. $hashAlgo,
  304. $algorithm . $hmac . $data);
  305. $iv = substr($data, -1 * $saltSize);
  306. if (fwrite($write, $result) !== strlen($result)) {
  307. return false;
  308. }
  309. }
  310. fclose($write);
  311. fclose($read);
  312. // check for data integrity
  313. if (!Utils::compareStrings($hmac, $hmacRead)) {
  314. unlink($fileOut);
  315. return false;
  316. }
  317. return true;
  318. }
  319. /**
  320. * Check that input file exists and output file dont
  321. *
  322. * @param string $fileIn
  323. * @param string $fileOut
  324. * @throws Exception\InvalidArgumentException
  325. */
  326. protected function checkFileInOut($fileIn, $fileOut)
  327. {
  328. if (!file_exists($fileIn)) {
  329. throw new Exception\InvalidArgumentException(sprintf(
  330. "I cannot open the %s file", $fileIn
  331. ));
  332. }
  333. if (file_exists($fileOut)) {
  334. throw new Exception\InvalidArgumentException(sprintf(
  335. "The file %s already exists", $fileOut
  336. ));
  337. }
  338. }
  339. }