Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
Encryptor.php
Go to the documentation of this file.
1 <?php
7 declare(strict_types=1);
8 
10 
17 
21 class Encryptor implements EncryptorInterface
22 {
26  const HASH_VERSION_MD5 = 0;
27 
32 
37 
41  const DEFAULT_SALT_LENGTH = 32;
42 
46  const PASSWORD_HASH = 0;
47  const PASSWORD_SALT = 1;
48  const PASSWORD_VERSION = 2;
54  const PARAM_CRYPT_KEY = 'crypt/key';
55 
59  const CIPHER_BLOWFISH = 0;
60 
62 
64 
66 
67  const CIPHER_LATEST = 3;
73  const DELIMITER = ':';
74 
78  private $hashVersionMap = [
79  self::HASH_VERSION_MD5 => 'md5',
80  self::HASH_VERSION_SHA256 => 'sha256'
81  ];
82 
86  private $passwordHashMap = [
87  self::PASSWORD_HASH => '',
88  self::PASSWORD_SALT => '',
89  self::PASSWORD_VERSION => self::HASH_VERSION_LATEST
90  ];
91 
98 
104  protected $keyVersion;
105 
111  protected $keys = [];
112 
116  private $random;
117 
123  public function __construct(
124  Random $random,
126  ) {
127  $this->random = $random;
128 
129  // load all possible keys
130  $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get(self::PARAM_CRYPT_KEY)));
131  $this->keyVersion = count($this->keys) - 1;
132  }
133 
143  public function validateCipher($version)
144  {
145  $types = [
150  ];
151 
152  $version = (int)$version;
153  if (!in_array($version, $types, true)) {
154  throw new \Exception((string)new \Magento\Framework\Phrase('Not supported cipher version'));
155  }
156  return $version;
157  }
158 
162  public function getHash($password, $salt = false, $version = self::HASH_VERSION_LATEST)
163  {
164  if ($salt === false) {
165  return $this->hash($password, $version);
166  }
167  if ($salt === true) {
169  }
170  if (is_integer($salt)) {
171  $salt = $this->random->getRandomString($salt);
172  }
173 
174  return implode(
175  self::DELIMITER,
176  [
177  $this->hash($salt . $password, $version),
178  $salt,
179  $version
180  ]
181  );
182  }
183 
187  public function hash($data, $version = self::HASH_VERSION_LATEST)
188  {
189  return hash($this->hashVersionMap[$version], (string)$data);
190  }
191 
195  public function validateHash($password, $hash)
196  {
197  return $this->isValidHash($password, $hash);
198  }
199 
203  public function isValidHash($password, $hash)
204  {
205  $this->explodePasswordHash($hash);
206 
207  foreach ($this->getPasswordVersion() as $hashVersion) {
208  $password = $this->hash($this->getPasswordSalt() . $password, $hashVersion);
209  }
210 
212  $password,
213  $this->getPasswordHash()
214  );
215  }
216 
220  public function validateHashVersion($hash, $validateCount = false)
221  {
222  $this->explodePasswordHash($hash);
223  $hashVersions = $this->getPasswordVersion();
224 
225  return $validateCount
226  ? end($hashVersions) === self::HASH_VERSION_LATEST && count($hashVersions) === 1
227  : end($hashVersions) === self::HASH_VERSION_LATEST;
228  }
229 
236  private function explodePasswordHash($hash)
237  {
238  $explodedPassword = explode(self::DELIMITER, $hash, 3);
239 
240  foreach ($this->passwordHashMap as $key => $defaultValue) {
241  $this->passwordHashMap[$key] = (isset($explodedPassword[$key])) ? $explodedPassword[$key] : $defaultValue;
242  }
243 
244  return $this->passwordHashMap;
245  }
246 
252  private function getPasswordHash()
253  {
254  return (string)$this->passwordHashMap[self::PASSWORD_HASH];
255  }
256 
262  private function getPasswordSalt()
263  {
264  return (string)$this->passwordHashMap[self::PASSWORD_SALT];
265  }
266 
272  private function getPasswordVersion()
273  {
274  return array_map('intval', explode(self::DELIMITER, $this->passwordHashMap[self::PASSWORD_VERSION]));
275  }
276 
283  public function encrypt($data)
284  {
285  $crypt = new SodiumChachaIetf($this->keys[$this->keyVersion]);
286 
287  return $this->keyVersion .
288  ':' . self::CIPHER_AEAD_CHACHA20POLY1305 .
289  ':' . base64_encode($crypt->encrypt($data));
290  }
291 
299  {
300  $crypt = $this->getCrypt();
301  if (null === $crypt) {
302  return $data;
303  }
304  return $this->keyVersion .
305  ':' . $this->getCipherVersion() .
306  ':' . base64_encode($crypt->encrypt($data));
307  }
319  public function decrypt($data)
320  {
321  if ($data) {
322  $parts = explode(':', $data, 4);
323  $partsCount = count($parts);
324 
325  $initVector = null;
326  // specified key, specified crypt, specified iv
327  if (4 === $partsCount) {
328  list($keyVersion, $cryptVersion, $iv, $data) = $parts;
329  $initVector = $iv ? $iv : null;
330  $keyVersion = (int)$keyVersion;
331  $cryptVersion = self::CIPHER_RIJNDAEL_256;
332  // specified key, specified crypt
333  } elseif (3 === $partsCount) {
334  list($keyVersion, $cryptVersion, $data) = $parts;
335  $keyVersion = (int)$keyVersion;
336  $cryptVersion = (int)$cryptVersion;
337  // no key version = oldest key, specified crypt
338  } elseif (2 === $partsCount) {
339  list($cryptVersion, $data) = $parts;
340  $keyVersion = 0;
341  $cryptVersion = (int)$cryptVersion;
342  // no key version = oldest key, no crypt version = oldest crypt
343  } elseif (1 === $partsCount) {
344  $keyVersion = 0;
345  $cryptVersion = self::CIPHER_BLOWFISH;
346  // not supported format
347  } else {
348  return '';
349  }
350  // no key for decryption
351  if (!isset($this->keys[$keyVersion])) {
352  return '';
353  }
354  $crypt = $this->getCrypt($this->keys[$keyVersion], $cryptVersion, $initVector);
355  if (null === $crypt) {
356  return '';
357  }
358  return trim($crypt->decrypt(base64_decode((string)$data)));
359  }
360  return '';
361  }
362 
369  public function validateKey($key)
370  {
371  if (preg_match('/\s/s', $key)) {
372  throw new \Exception((string)new \Magento\Framework\Phrase('The encryption key format is invalid.'));
373  }
374  }
375 
383  public function setNewKey($key)
384  {
385  $this->validateKey($key);
386  $this->keys[] = $key;
387  $this->keyVersion += 1;
388  return $this;
389  }
390 
396  public function exportKeys()
397  {
398  return implode("\n", $this->keys);
399  }
400 
412  private function getCrypt(
413  string $key = null,
414  int $cipherVersion = null,
415  string $initVector = null
417  if (null === $key && null === $cipherVersion) {
418  $cipherVersion = $this->getCipherVersion();
419  }
420 
421  if (null === $key) {
422  $key = $this->keys[$this->keyVersion];
423  }
424 
425  if (!$key) {
426  return null;
427  }
428 
429  if (null === $cipherVersion) {
430  $cipherVersion = $this->cipher;
431  }
432  $cipherVersion = $this->validateCipher($cipherVersion);
433 
434  if ($cipherVersion >= self::CIPHER_AEAD_CHACHA20POLY1305) {
435  return new SodiumChachaIetf($key);
436  }
437 
438  if ($cipherVersion === self::CIPHER_RIJNDAEL_128) {
439  $cipher = MCRYPT_RIJNDAEL_128;
440  $mode = MCRYPT_MODE_ECB;
441  } elseif ($cipherVersion === self::CIPHER_RIJNDAEL_256) {
442  $cipher = MCRYPT_RIJNDAEL_256;
443  $mode = MCRYPT_MODE_CBC;
444  } else {
445  $cipher = MCRYPT_BLOWFISH;
446  $mode = MCRYPT_MODE_ECB;
447  }
448 
449  return new Mcrypt($key, $cipher, $mode, $initVector);
450  }
451 
457  private function getCipherVersion()
458  {
459  if (extension_loaded('sodium')) {
460  return $this->cipher;
461  } else {
463  }
464  }
465 }
$initVector
elseif(isset( $params[ 'redirect_parent']))
Definition: iframe.phtml:17
$deploymentConfig
if($exist=($block->getProductCollection() && $block->getProductCollection() ->getSize())) $mode
Definition: grid.phtml:15
hash($data, $version=self::HASH_VERSION_LATEST)
Definition: Encryptor.php:187
__construct(Random $random, DeploymentConfig $deploymentConfig)
Definition: Encryptor.php:123
static compareStrings($expected, $actual)
Definition: Security.php:26
validateHashVersion($hash, $validateCount=false)
Definition: Encryptor.php:220
getHash($password, $salt=false, $version=self::HASH_VERSION_LATEST)
Definition: Encryptor.php:162