/vendor/laravel/framework/src/Illuminate/Encryption/Encrypter.php

https://gitlab.com/jjpa2018/dashboard · PHP · 267 lines · 120 code · 38 blank · 109 comment · 11 complexity · 854cd7b665686b63357740e32514f2d3 MD5 · raw file

  1. <?php
  2. namespace Illuminate\Encryption;
  3. use Illuminate\Contracts\Encryption\DecryptException;
  4. use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;
  5. use Illuminate\Contracts\Encryption\EncryptException;
  6. use Illuminate\Contracts\Encryption\StringEncrypter;
  7. use RuntimeException;
  8. class Encrypter implements EncrypterContract, StringEncrypter
  9. {
  10. /**
  11. * The encryption key.
  12. *
  13. * @var string
  14. */
  15. protected $key;
  16. /**
  17. * The algorithm used for encryption.
  18. *
  19. * @var string
  20. */
  21. protected $cipher;
  22. /**
  23. * The supported cipher algorithms and their properties.
  24. *
  25. * @var array
  26. */
  27. private static $supportedCiphers = [
  28. 'aes-128-cbc' => ['size' => 16, 'aead' => false],
  29. 'aes-256-cbc' => ['size' => 32, 'aead' => false],
  30. 'aes-128-gcm' => ['size' => 16, 'aead' => true],
  31. 'aes-256-gcm' => ['size' => 32, 'aead' => true],
  32. ];
  33. /**
  34. * Create a new encrypter instance.
  35. *
  36. * @param string $key
  37. * @param string $cipher
  38. * @return void
  39. *
  40. * @throws \RuntimeException
  41. */
  42. public function __construct($key, $cipher = 'aes-128-cbc')
  43. {
  44. $key = (string) $key;
  45. if (! static::supported($key, $cipher)) {
  46. $ciphers = implode(', ', array_keys(self::$supportedCiphers));
  47. throw new RuntimeException("Unsupported cipher or incorrect key length. Supported ciphers are: {$ciphers}.");
  48. }
  49. $this->key = $key;
  50. $this->cipher = $cipher;
  51. }
  52. /**
  53. * Determine if the given key and cipher combination is valid.
  54. *
  55. * @param string $key
  56. * @param string $cipher
  57. * @return bool
  58. */
  59. public static function supported($key, $cipher)
  60. {
  61. if (! isset(self::$supportedCiphers[strtolower($cipher)])) {
  62. return false;
  63. }
  64. return mb_strlen($key, '8bit') === self::$supportedCiphers[strtolower($cipher)]['size'];
  65. }
  66. /**
  67. * Create a new encryption key for the given cipher.
  68. *
  69. * @param string $cipher
  70. * @return string
  71. */
  72. public static function generateKey($cipher)
  73. {
  74. return random_bytes(self::$supportedCiphers[strtolower($cipher)]['size'] ?? 32);
  75. }
  76. /**
  77. * Encrypt the given value.
  78. *
  79. * @param mixed $value
  80. * @param bool $serialize
  81. * @return string
  82. *
  83. * @throws \Illuminate\Contracts\Encryption\EncryptException
  84. */
  85. public function encrypt($value, $serialize = true)
  86. {
  87. $iv = random_bytes(openssl_cipher_iv_length(strtolower($this->cipher)));
  88. $tag = '';
  89. $value = self::$supportedCiphers[strtolower($this->cipher)]['aead']
  90. ? \openssl_encrypt(
  91. $serialize ? serialize($value) : $value,
  92. strtolower($this->cipher), $this->key, 0, $iv, $tag
  93. )
  94. : \openssl_encrypt(
  95. $serialize ? serialize($value) : $value,
  96. strtolower($this->cipher), $this->key, 0, $iv
  97. );
  98. if ($value === false) {
  99. throw new EncryptException('Could not encrypt the data.');
  100. }
  101. $iv = base64_encode($iv);
  102. $tag = base64_encode($tag);
  103. $mac = self::$supportedCiphers[strtolower($this->cipher)]['aead']
  104. ? '' // For AEAD-algoritms, the tag / MAC is returned by openssl_encrypt...
  105. : $this->hash($iv, $value);
  106. $json = json_encode(compact('iv', 'value', 'mac', 'tag'), JSON_UNESCAPED_SLASHES);
  107. if (json_last_error() !== JSON_ERROR_NONE) {
  108. throw new EncryptException('Could not encrypt the data.');
  109. }
  110. return base64_encode($json);
  111. }
  112. /**
  113. * Encrypt a string without serialization.
  114. *
  115. * @param string $value
  116. * @return string
  117. *
  118. * @throws \Illuminate\Contracts\Encryption\EncryptException
  119. */
  120. public function encryptString($value)
  121. {
  122. return $this->encrypt($value, false);
  123. }
  124. /**
  125. * Decrypt the given value.
  126. *
  127. * @param string $payload
  128. * @param bool $unserialize
  129. * @return mixed
  130. *
  131. * @throws \Illuminate\Contracts\Encryption\DecryptException
  132. */
  133. public function decrypt($payload, $unserialize = true)
  134. {
  135. $payload = $this->getJsonPayload($payload);
  136. $iv = base64_decode($payload['iv']);
  137. $tag = empty($payload['tag']) ? null : base64_decode($payload['tag']);
  138. if (self::$supportedCiphers[strtolower($this->cipher)]['aead'] && strlen($tag) !== 16) {
  139. throw new DecryptException('Could not decrypt the data.');
  140. }
  141. // Here we will decrypt the value. If we are able to successfully decrypt it
  142. // we will then unserialize it and return it out to the caller. If we are
  143. // unable to decrypt this value we will throw out an exception message.
  144. $decrypted = \openssl_decrypt(
  145. $payload['value'], strtolower($this->cipher), $this->key, 0, $iv, $tag ?? ''
  146. );
  147. if ($decrypted === false) {
  148. throw new DecryptException('Could not decrypt the data.');
  149. }
  150. return $unserialize ? unserialize($decrypted) : $decrypted;
  151. }
  152. /**
  153. * Decrypt the given string without unserialization.
  154. *
  155. * @param string $payload
  156. * @return string
  157. *
  158. * @throws \Illuminate\Contracts\Encryption\DecryptException
  159. */
  160. public function decryptString($payload)
  161. {
  162. return $this->decrypt($payload, false);
  163. }
  164. /**
  165. * Create a MAC for the given value.
  166. *
  167. * @param string $iv
  168. * @param mixed $value
  169. * @return string
  170. */
  171. protected function hash($iv, $value)
  172. {
  173. return hash_hmac('sha256', $iv.$value, $this->key);
  174. }
  175. /**
  176. * Get the JSON array from the given payload.
  177. *
  178. * @param string $payload
  179. * @return array
  180. *
  181. * @throws \Illuminate\Contracts\Encryption\DecryptException
  182. */
  183. protected function getJsonPayload($payload)
  184. {
  185. $payload = json_decode(base64_decode($payload), true);
  186. // If the payload is not valid JSON or does not have the proper keys set we will
  187. // assume it is invalid and bail out of the routine since we will not be able
  188. // to decrypt the given value. We'll also check the MAC for this encryption.
  189. if (! $this->validPayload($payload)) {
  190. throw new DecryptException('The payload is invalid.');
  191. }
  192. if (! self::$supportedCiphers[strtolower($this->cipher)]['aead'] && ! $this->validMac($payload)) {
  193. throw new DecryptException('The MAC is invalid.');
  194. }
  195. return $payload;
  196. }
  197. /**
  198. * Verify that the encryption payload is valid.
  199. *
  200. * @param mixed $payload
  201. * @return bool
  202. */
  203. protected function validPayload($payload)
  204. {
  205. return is_array($payload) && isset($payload['iv'], $payload['value'], $payload['mac']) &&
  206. strlen(base64_decode($payload['iv'], true)) === openssl_cipher_iv_length(strtolower($this->cipher));
  207. }
  208. /**
  209. * Determine if the MAC for the given payload is valid.
  210. *
  211. * @param array $payload
  212. * @return bool
  213. */
  214. protected function validMac(array $payload)
  215. {
  216. return hash_equals(
  217. $this->hash($payload['iv'], $payload['value']), $payload['mac']
  218. );
  219. }
  220. /**
  221. * Get the encryption key.
  222. *
  223. * @return string
  224. */
  225. public function getKey()
  226. {
  227. return $this->key;
  228. }
  229. }