EncryptorTest.php 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. declare(strict_types=1);
  7. namespace Magento\Framework\Encryption\Test\Unit;
  8. use Magento\Framework\Encryption\Adapter\SodiumChachaIetf;
  9. use Magento\Framework\Encryption\Encryptor;
  10. use Magento\Framework\Encryption\Crypt;
  11. use Magento\Framework\Encryption\KeyValidator;
  12. use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
  13. class EncryptorTest extends \PHPUnit\Framework\TestCase
  14. {
  15. const CRYPT_KEY_1 = 'g9mY9KLrcuAVJfsmVUSRkKFLDdUPVkaZ';
  16. const CRYPT_KEY_2 = '7wEjmrliuqZQ1NQsndSa8C8WHvddeEbN';
  17. /**
  18. * @var \Magento\Framework\Encryption\Encryptor
  19. */
  20. private $encryptor;
  21. /**
  22. * @var \PHPUnit_Framework_MockObject_MockObject
  23. */
  24. private $randomGeneratorMock;
  25. /**
  26. * @var KeyValidator|\PHPUnit_Framework_MockObject_MockObject
  27. */
  28. private $keyValidatorMock;
  29. protected function setUp()
  30. {
  31. $this->randomGeneratorMock = $this->createMock(\Magento\Framework\Math\Random::class);
  32. $deploymentConfigMock = $this->createMock(\Magento\Framework\App\DeploymentConfig::class);
  33. $deploymentConfigMock->expects($this->any())
  34. ->method('get')
  35. ->with(Encryptor::PARAM_CRYPT_KEY)
  36. ->will($this->returnValue(self::CRYPT_KEY_1));
  37. $this->keyValidatorMock = $this->createMock(KeyValidator::class);
  38. $this->encryptor = (new ObjectManager($this))->getObject(
  39. \Magento\Framework\Encryption\Encryptor::class,
  40. [
  41. 'random' => $this->randomGeneratorMock,
  42. 'deploymentConfig' => $deploymentConfigMock,
  43. 'keyValidator' => $this->keyValidatorMock
  44. ]
  45. );
  46. }
  47. public function testGetHashNoSalt()
  48. {
  49. $this->randomGeneratorMock->expects($this->never())->method('getRandomString');
  50. $expected = '5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8';
  51. $actual = $this->encryptor->getHash('password');
  52. $this->assertEquals($expected, $actual);
  53. }
  54. public function testGetHashSpecifiedSalt()
  55. {
  56. $this->randomGeneratorMock->expects($this->never())->method('getRandomString');
  57. $expected = '13601bda4ea78e55a07b98866d2be6be0744e3866f13c00c811cab608a28f322:salt:1';
  58. $actual = $this->encryptor->getHash('password', 'salt');
  59. $this->assertEquals($expected, $actual);
  60. }
  61. public function testGetHashRandomSaltDefaultLength()
  62. {
  63. $salt = '-----------random_salt----------';
  64. $this->randomGeneratorMock
  65. ->expects($this->once())
  66. ->method('getRandomString')
  67. ->with(32)
  68. ->will($this->returnValue($salt));
  69. $expected = 'a1c7fc88037b70c9be84d3ad12522c7888f647915db78f42eb572008422ba2fa:' . $salt . ':1';
  70. $actual = $this->encryptor->getHash('password', true);
  71. $this->assertEquals($expected, $actual);
  72. }
  73. public function testGetHashRandomSaltSpecifiedLength()
  74. {
  75. $this->randomGeneratorMock
  76. ->expects($this->once())
  77. ->method('getRandomString')
  78. ->with(11)
  79. ->will($this->returnValue('random_salt'));
  80. $expected = '4c5cab8dd00137d11258f8f87b93fd17bd94c5026fc52d3c5af911dd177a2611:random_salt:1';
  81. $actual = $this->encryptor->getHash('password', 11);
  82. $this->assertEquals($expected, $actual);
  83. }
  84. /**
  85. * @param string $password
  86. * @param string $hash
  87. * @param bool $expected
  88. *
  89. * @dataProvider validateHashDataProvider
  90. */
  91. public function testValidateHash($password, $hash, $expected)
  92. {
  93. $actual = $this->encryptor->validateHash($password, $hash);
  94. $this->assertEquals($expected, $actual);
  95. }
  96. /**
  97. * @return array
  98. */
  99. public function validateHashDataProvider()
  100. {
  101. return [
  102. ['password', 'hash:salt:1', false],
  103. ['password', '67a1e09bb1f83f5007dc119c14d663aa:salt:0', true],
  104. ['password', '13601bda4ea78e55a07b98866d2be6be0744e3866f13c00c811cab608a28f322:salt:1', true],
  105. ];
  106. }
  107. /**
  108. * @param mixed $key
  109. *
  110. * @dataProvider encryptWithEmptyKeyDataProvider
  111. * @expectedException \SodiumException
  112. */
  113. public function testEncryptWithEmptyKey($key)
  114. {
  115. $deploymentConfigMock = $this->createMock(\Magento\Framework\App\DeploymentConfig::class);
  116. $deploymentConfigMock->expects($this->any())
  117. ->method('get')
  118. ->with(Encryptor::PARAM_CRYPT_KEY)
  119. ->will($this->returnValue($key));
  120. $model = new Encryptor($this->randomGeneratorMock, $deploymentConfigMock);
  121. $value = 'arbitrary_string';
  122. $this->assertEquals($value, $model->encrypt($value));
  123. }
  124. /**
  125. * @return array
  126. */
  127. public function encryptWithEmptyKeyDataProvider()
  128. {
  129. return [[null], [0], [''], ['0']];
  130. }
  131. /**
  132. * @param mixed $key
  133. *
  134. * @dataProvider decryptWithEmptyKeyDataProvider
  135. */
  136. public function testDecryptWithEmptyKey($key)
  137. {
  138. $deploymentConfigMock = $this->createMock(\Magento\Framework\App\DeploymentConfig::class);
  139. $deploymentConfigMock->expects($this->any())
  140. ->method('get')
  141. ->with(Encryptor::PARAM_CRYPT_KEY)
  142. ->will($this->returnValue($key));
  143. $model = new Encryptor($this->randomGeneratorMock, $deploymentConfigMock);
  144. $value = 'arbitrary_string';
  145. $this->assertEquals('', $model->decrypt($value));
  146. }
  147. /**
  148. * @return array
  149. */
  150. public function decryptWithEmptyKeyDataProvider()
  151. {
  152. return [[null], [0], [''], ['0']];
  153. }
  154. public function testEncrypt()
  155. {
  156. // sample data to encrypt
  157. $data = 'Mares eat oats and does eat oats, but little lambs eat ivy.';
  158. $actual = $this->encryptor->encrypt($data);
  159. // Extract the initialization vector and encrypted data
  160. $parts = explode(':', $actual, 3);
  161. list(, , $encryptedData) = $parts;
  162. $crypt = new SodiumChachaIetf(self::CRYPT_KEY_1);
  163. // Verify decrypted matches original data
  164. $this->assertEquals($data, $crypt->decrypt(base64_decode((string)$encryptedData)));
  165. }
  166. public function testDecrypt()
  167. {
  168. $message = 'Mares eat oats and does eat oats, but little lambs eat ivy.';
  169. $encrypted = $this->encryptor->encrypt($message);
  170. $this->assertEquals($message, $this->encryptor->decrypt($encrypted));
  171. }
  172. public function testLegacyDecrypt()
  173. {
  174. // sample data to encrypt
  175. $data = '0:2:z3a4ACpkU35W6pV692U4ueCVQP0m0v0p:' .
  176. 'DhEG8/uKGGq92ZusqrGb6X/9+2Ng0QZ9z2UZwljgJbs5/A3LaSnqcK0oI32yjHY49QJi+Z7q1EKu2yVqB8EMpA==';
  177. $actual = $this->encryptor->decrypt($data);
  178. // Extract the initialization vector and encrypted data
  179. $parts = explode(':', $data, 4);
  180. list(, , $iv, $encrypted) = $parts;
  181. // Decrypt returned data with RIJNDAEL_256 cipher, cbc mode
  182. $crypt = new Crypt(self::CRYPT_KEY_1, MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC, $iv);
  183. // Verify decrypted matches original data
  184. $this->assertEquals($encrypted, base64_encode($crypt->encrypt($actual)));
  185. }
  186. public function testEncryptDecryptNewKeyAdded()
  187. {
  188. $deploymentConfigMock = $this->createMock(\Magento\Framework\App\DeploymentConfig::class);
  189. $deploymentConfigMock->expects($this->at(0))
  190. ->method('get')
  191. ->with(Encryptor::PARAM_CRYPT_KEY)
  192. ->will($this->returnValue(self::CRYPT_KEY_1));
  193. $deploymentConfigMock->expects($this->at(1))
  194. ->method('get')
  195. ->with(Encryptor::PARAM_CRYPT_KEY)
  196. ->will($this->returnValue(self::CRYPT_KEY_1 . "\n" . self::CRYPT_KEY_2));
  197. $model1 = new Encryptor($this->randomGeneratorMock, $deploymentConfigMock);
  198. // simulate an encryption key is being added
  199. $model2 = new Encryptor($this->randomGeneratorMock, $deploymentConfigMock);
  200. // sample data to encrypt
  201. $data = 'Mares eat oats and does eat oats, but little lambs eat ivy.';
  202. // encrypt with old key
  203. $encryptedData = $model1->encrypt($data);
  204. $decryptedData = $model2->decrypt($encryptedData);
  205. $this->assertSame($data, $decryptedData, 'Encryptor failed to decrypt data encrypted by old keys.');
  206. }
  207. public function testValidateKey()
  208. {
  209. $this->keyValidatorMock->method('isValid')->willReturn(true);
  210. $this->encryptor->validateKey(self::CRYPT_KEY_1);
  211. }
  212. /**
  213. * @expectedException \Exception
  214. */
  215. public function testValidateKeyInvalid()
  216. {
  217. $this->keyValidatorMock->method('isValid')->willReturn(false);
  218. $this->encryptor->validateKey('----- ');
  219. }
  220. /**
  221. * @return array
  222. */
  223. public function useSpecifiedHashingAlgoDataProvider()
  224. {
  225. return [
  226. ['password', 'salt', Encryptor::HASH_VERSION_MD5,
  227. '67a1e09bb1f83f5007dc119c14d663aa:salt:0'],
  228. ['password', 'salt', Encryptor::HASH_VERSION_SHA256,
  229. '13601bda4ea78e55a07b98866d2be6be0744e3866f13c00c811cab608a28f322:salt:1'],
  230. ['password', false, Encryptor::HASH_VERSION_MD5,
  231. '5f4dcc3b5aa765d61d8327deb882cf99'],
  232. ['password', false, Encryptor::HASH_VERSION_SHA256,
  233. '5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8']
  234. ];
  235. }
  236. /**
  237. * @dataProvider useSpecifiedHashingAlgoDataProvider
  238. *
  239. * @param $password
  240. * @param $salt
  241. * @param $hashAlgo
  242. * @param $expected
  243. */
  244. public function testGetHashMustUseSpecifiedHashingAlgo($password, $salt, $hashAlgo, $expected)
  245. {
  246. $hash = $this->encryptor->getHash($password, $salt, $hashAlgo);
  247. $this->assertEquals($expected, $hash);
  248. }
  249. }