/library/Zend/Crypt/BlockCipher.php

https://github.com/cgmartin/zf2 · PHP · 444 lines · 218 code · 44 blank · 182 comment · 20 complexity · a7f505e1c372185b20634f817d74b87e 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-2014 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\SymmetricInterface;
  12. use Zend\Math\Rand;
  13. /**
  14. * Encrypt using a symmetric cipher then authenticate using HMAC (SHA-256)
  15. */
  16. class BlockCipher
  17. {
  18. const KEY_DERIV_HMAC = 'sha256';
  19. /**
  20. * Symmetric cipher
  21. *
  22. * @var SymmetricInterface
  23. */
  24. protected $cipher;
  25. /**
  26. * Symmetric cipher plugin manager
  27. *
  28. * @var SymmetricPluginManager
  29. */
  30. protected static $symmetricPlugins = null;
  31. /**
  32. * Hash algorithm fot HMAC
  33. *
  34. * @var string
  35. */
  36. protected $hash = 'sha256';
  37. /**
  38. * Check if the salt has been set
  39. *
  40. * @var bool
  41. */
  42. protected $saltSetted = false;
  43. /**
  44. * The output is binary?
  45. *
  46. * @var bool
  47. */
  48. protected $binaryOutput = false;
  49. /**
  50. * Number of iterations for Pbkdf2
  51. *
  52. * @var string
  53. */
  54. protected $keyIteration = 5000;
  55. /**
  56. * Key
  57. *
  58. * @var string
  59. */
  60. protected $key;
  61. /**
  62. * Constructor
  63. *
  64. * @param SymmetricInterface $cipher
  65. */
  66. public function __construct(SymmetricInterface $cipher)
  67. {
  68. $this->cipher = $cipher;
  69. }
  70. /**
  71. * Factory.
  72. *
  73. * @param string $adapter
  74. * @param array $options
  75. * @return BlockCipher
  76. */
  77. public static function factory($adapter, $options = array())
  78. {
  79. $plugins = static::getSymmetricPluginManager();
  80. $adapter = $plugins->get($adapter, (array) $options);
  81. return new static($adapter);
  82. }
  83. /**
  84. * Returns the symmetric cipher plugin manager. If it doesn't exist it's created.
  85. *
  86. * @return SymmetricPluginManager
  87. */
  88. public static function getSymmetricPluginManager()
  89. {
  90. if (static::$symmetricPlugins === null) {
  91. static::setSymmetricPluginManager(new SymmetricPluginManager());
  92. }
  93. return static::$symmetricPlugins;
  94. }
  95. /**
  96. * Set the symmetric cipher plugin manager
  97. *
  98. * @param string|SymmetricPluginManager $plugins
  99. * @throws Exception\InvalidArgumentException
  100. */
  101. public static function setSymmetricPluginManager($plugins)
  102. {
  103. if (is_string($plugins)) {
  104. if (!class_exists($plugins)) {
  105. throw new Exception\InvalidArgumentException(sprintf(
  106. 'Unable to locate symmetric cipher plugins using class "%s"; class does not exist',
  107. $plugins
  108. ));
  109. }
  110. $plugins = new $plugins();
  111. }
  112. if (!$plugins instanceof SymmetricPluginManager) {
  113. throw new Exception\InvalidArgumentException(sprintf(
  114. 'Expected an instance or extension of %s\SymmetricPluginManager; received "%s"',
  115. __NAMESPACE__,
  116. (is_object($plugins) ? get_class($plugins) : gettype($plugins))
  117. ));
  118. }
  119. static::$symmetricPlugins = $plugins;
  120. }
  121. /**
  122. * Set the symmetric cipher
  123. *
  124. * @param SymmetricInterface $cipher
  125. * @return BlockCipher
  126. */
  127. public function setCipher(SymmetricInterface $cipher)
  128. {
  129. $this->cipher = $cipher;
  130. return $this;
  131. }
  132. /**
  133. * Get symmetric cipher
  134. *
  135. * @return SymmetricInterface
  136. */
  137. public function getCipher()
  138. {
  139. return $this->cipher;
  140. }
  141. /**
  142. * Set the number of iterations for Pbkdf2
  143. *
  144. * @param int $num
  145. * @return BlockCipher
  146. */
  147. public function setKeyIteration($num)
  148. {
  149. $this->keyIteration = (int) $num;
  150. return $this;
  151. }
  152. /**
  153. * Get the number of iterations for Pbkdf2
  154. *
  155. * @return int
  156. */
  157. public function getKeyIteration()
  158. {
  159. return $this->keyIteration;
  160. }
  161. /**
  162. * Set the salt (IV)
  163. *
  164. * @param string $salt
  165. * @return BlockCipher
  166. * @throws Exception\InvalidArgumentException
  167. */
  168. public function setSalt($salt)
  169. {
  170. try {
  171. $this->cipher->setSalt($salt);
  172. } catch (Symmetric\Exception\InvalidArgumentException $e) {
  173. throw new Exception\InvalidArgumentException("The salt is not valid: " . $e->getMessage());
  174. }
  175. $this->saltSetted = true;
  176. return $this;
  177. }
  178. /**
  179. * Get the salt (IV) according to the size requested by the algorithm
  180. *
  181. * @return string
  182. */
  183. public function getSalt()
  184. {
  185. return $this->cipher->getSalt();
  186. }
  187. /**
  188. * Get the original salt value
  189. *
  190. * @return string
  191. */
  192. public function getOriginalSalt()
  193. {
  194. return $this->cipher->getOriginalSalt();
  195. }
  196. /**
  197. * Enable/disable the binary output
  198. *
  199. * @param bool $value
  200. * @return BlockCipher
  201. */
  202. public function setBinaryOutput($value)
  203. {
  204. $this->binaryOutput = (bool) $value;
  205. return $this;
  206. }
  207. /**
  208. * Get the value of binary output
  209. *
  210. * @return bool
  211. */
  212. public function getBinaryOutput()
  213. {
  214. return $this->binaryOutput;
  215. }
  216. /**
  217. * Set the encryption/decryption key
  218. *
  219. * @param string $key
  220. * @return BlockCipher
  221. * @throws Exception\InvalidArgumentException
  222. */
  223. public function setKey($key)
  224. {
  225. if (empty($key)) {
  226. throw new Exception\InvalidArgumentException('The key cannot be empty');
  227. }
  228. $this->key = $key;
  229. return $this;
  230. }
  231. /**
  232. * Get the key
  233. *
  234. * @return string
  235. */
  236. public function getKey()
  237. {
  238. return $this->key;
  239. }
  240. /**
  241. * Set algorithm of the symmetric cipher
  242. *
  243. * @param string $algo
  244. * @return BlockCipher
  245. * @throws Exception\InvalidArgumentException
  246. */
  247. public function setCipherAlgorithm($algo)
  248. {
  249. if (empty($this->cipher)) {
  250. throw new Exception\InvalidArgumentException('No symmetric cipher specified');
  251. }
  252. try {
  253. $this->cipher->setAlgorithm($algo);
  254. } catch (Symmetric\Exception\InvalidArgumentException $e) {
  255. throw new Exception\InvalidArgumentException($e->getMessage());
  256. }
  257. return $this;
  258. }
  259. /**
  260. * Get the cipher algorithm
  261. *
  262. * @return string|bool
  263. */
  264. public function getCipherAlgorithm()
  265. {
  266. if (!empty($this->cipher)) {
  267. return $this->cipher->getAlgorithm();
  268. }
  269. return false;
  270. }
  271. /**
  272. * Get the supported algorithms of the symmetric cipher
  273. *
  274. * @return array
  275. */
  276. public function getCipherSupportedAlgorithms()
  277. {
  278. if (!empty($this->cipher)) {
  279. return $this->cipher->getSupportedAlgorithms();
  280. }
  281. return array();
  282. }
  283. /**
  284. * Set the hash algorithm for HMAC authentication
  285. *
  286. * @param string $hash
  287. * @return BlockCipher
  288. * @throws Exception\InvalidArgumentException
  289. */
  290. public function setHashAlgorithm($hash)
  291. {
  292. if (!Hash::isSupported($hash)) {
  293. throw new Exception\InvalidArgumentException(
  294. "The specified hash algorithm '{$hash}' is not supported by Zend\Crypt\Hash"
  295. );
  296. }
  297. $this->hash = $hash;
  298. return $this;
  299. }
  300. /**
  301. * Get the hash algorithm for HMAC authentication
  302. *
  303. * @return string
  304. */
  305. public function getHashAlgorithm()
  306. {
  307. return $this->hash;
  308. }
  309. /**
  310. * Encrypt then authenticate using HMAC
  311. *
  312. * @param string $data
  313. * @return string
  314. * @throws Exception\InvalidArgumentException
  315. */
  316. public function encrypt($data)
  317. {
  318. if (empty($data)) {
  319. throw new Exception\InvalidArgumentException('The data to encrypt cannot be empty');
  320. }
  321. if (empty($this->cipher)) {
  322. throw new Exception\InvalidArgumentException('No symmetric cipher specified');
  323. }
  324. if (empty($this->key)) {
  325. throw new Exception\InvalidArgumentException('No key specified for the encryption');
  326. }
  327. $keySize = $this->cipher->getKeySize();
  328. // generate a random salt (IV) if the salt has not been set
  329. if (!$this->saltSetted) {
  330. $this->cipher->setSalt(Rand::getBytes($this->cipher->getSaltSize(), true));
  331. }
  332. // generate the encryption key and the HMAC key for the authentication
  333. $hash = Pbkdf2::calc(self::KEY_DERIV_HMAC,
  334. $this->getKey(),
  335. $this->getSalt(),
  336. $this->keyIteration,
  337. $keySize * 2);
  338. // set the encryption key
  339. $this->cipher->setKey(substr($hash, 0, $keySize));
  340. // set the key for HMAC
  341. $keyHmac = substr($hash, $keySize);
  342. // encryption
  343. $ciphertext = $this->cipher->encrypt($data);
  344. // HMAC
  345. $hmac = Hmac::compute($keyHmac,
  346. $this->hash,
  347. $this->cipher->getAlgorithm() . $ciphertext);
  348. if (!$this->binaryOutput) {
  349. $ciphertext = base64_encode($ciphertext);
  350. }
  351. return $hmac . $ciphertext;
  352. }
  353. /**
  354. * Decrypt
  355. *
  356. * @param string $data
  357. * @return string|bool
  358. * @throws Exception\InvalidArgumentException
  359. */
  360. public function decrypt($data)
  361. {
  362. if (!is_string($data)) {
  363. throw new Exception\InvalidArgumentException('The data to decrypt must be a string');
  364. }
  365. if ('' === $data) {
  366. throw new Exception\InvalidArgumentException('The data to decrypt cannot be empty');
  367. }
  368. if (empty($this->key)) {
  369. throw new Exception\InvalidArgumentException('No key specified for the decryption');
  370. }
  371. if (empty($this->cipher)) {
  372. throw new Exception\InvalidArgumentException('No symmetric cipher specified');
  373. }
  374. $hmacSize = Hmac::getOutputSize($this->hash);
  375. $hmac = substr($data, 0, $hmacSize);
  376. $ciphertext = substr($data, $hmacSize);
  377. if (!$this->binaryOutput) {
  378. $ciphertext = base64_decode($ciphertext);
  379. }
  380. $iv = substr($ciphertext, 0, $this->cipher->getSaltSize());
  381. $keySize = $this->cipher->getKeySize();
  382. // generate the encryption key and the HMAC key for the authentication
  383. $hash = Pbkdf2::calc(self::KEY_DERIV_HMAC,
  384. $this->getKey(),
  385. $iv,
  386. $this->keyIteration,
  387. $keySize * 2);
  388. // set the decryption key
  389. $this->cipher->setKey(substr($hash, 0, $keySize));
  390. // set the key for HMAC
  391. $keyHmac = substr($hash, $keySize);
  392. $hmacNew = Hmac::compute($keyHmac,
  393. $this->hash,
  394. $this->cipher->getAlgorithm() . $ciphertext);
  395. if (!Utils::compareStrings($hmacNew, $hmac)) {
  396. return false;
  397. }
  398. return $this->cipher->decrypt($ciphertext);
  399. }
  400. }