PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Crypt/BlockCipher.php

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