1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5: *
6: * Licensed under The MIT License
7: * For full copyright and license information, please see the LICENSE.txt
8: * Redistributions of files must retain the above copyright notice.
9: *
10: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11: * @link https://cakephp.org CakePHP(tm) Project
12: * @since 3.1.6
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\Utility;
16:
17: use RuntimeException;
18:
19: /**
20: * Cookie Crypt Trait.
21: *
22: * Provides the encrypt/decrypt logic for the CookieComponent.
23: *
24: * @link https://book.cakephp.org/3.0/en/controllers/components/cookie.html
25: */
26: trait CookieCryptTrait
27: {
28: /**
29: * Valid cipher names for encrypted cookies.
30: *
31: * @var array
32: */
33: protected $_validCiphers = ['aes', 'rijndael'];
34:
35: /**
36: * Returns the encryption key to be used.
37: *
38: * @return string
39: */
40: abstract protected function _getCookieEncryptionKey();
41:
42: /**
43: * Encrypts $value using public $type method in Security class
44: *
45: * @param string $value Value to encrypt
46: * @param string|bool $encrypt Encryption mode to use. False
47: * disabled encryption.
48: * @param string|null $key Used as the security salt if specified.
49: * @return string Encoded values
50: */
51: protected function _encrypt($value, $encrypt, $key = null)
52: {
53: if (is_array($value)) {
54: $value = $this->_implode($value);
55: }
56: if ($encrypt === false) {
57: return $value;
58: }
59: $this->_checkCipher($encrypt);
60: $prefix = 'Q2FrZQ==.';
61: $cipher = null;
62: if ($key === null) {
63: $key = $this->_getCookieEncryptionKey();
64: }
65: if ($encrypt === 'rijndael') {
66: $cipher = Security::rijndael($value, $key, 'encrypt');
67: }
68: if ($encrypt === 'aes') {
69: $cipher = Security::encrypt($value, $key);
70: }
71:
72: return $prefix . base64_encode($cipher);
73: }
74:
75: /**
76: * Helper method for validating encryption cipher names.
77: *
78: * @param string $encrypt The cipher name.
79: * @return void
80: * @throws \RuntimeException When an invalid cipher is provided.
81: */
82: protected function _checkCipher($encrypt)
83: {
84: if (!in_array($encrypt, $this->_validCiphers)) {
85: $msg = sprintf(
86: 'Invalid encryption cipher. Must be one of %s or false.',
87: implode(', ', $this->_validCiphers)
88: );
89: throw new RuntimeException($msg);
90: }
91: }
92:
93: /**
94: * Decrypts $value using public $type method in Security class
95: *
96: * @param array $values Values to decrypt
97: * @param string|bool $mode Encryption mode
98: * @param string|null $key Used as the security salt if specified.
99: * @return string|array Decrypted values
100: */
101: protected function _decrypt($values, $mode, $key = null)
102: {
103: if (is_string($values)) {
104: return $this->_decode($values, $mode, $key);
105: }
106:
107: $decrypted = [];
108: foreach ($values as $name => $value) {
109: $decrypted[$name] = $this->_decode($value, $mode, $key);
110: }
111:
112: return $decrypted;
113: }
114:
115: /**
116: * Decodes and decrypts a single value.
117: *
118: * @param string $value The value to decode & decrypt.
119: * @param string|false $encrypt The encryption cipher to use.
120: * @param string|null $key Used as the security salt if specified.
121: * @return string|array Decoded values.
122: */
123: protected function _decode($value, $encrypt, $key)
124: {
125: if (!$encrypt) {
126: return $this->_explode($value);
127: }
128: $this->_checkCipher($encrypt);
129: $prefix = 'Q2FrZQ==.';
130: $prefixLength = strlen($prefix);
131:
132: if (strncmp($value, $prefix, $prefixLength) !== 0) {
133: return '';
134: }
135:
136: $value = base64_decode(substr($value, $prefixLength), true);
137:
138: if ($value === false || $value === '') {
139: return '';
140: }
141:
142: if ($key === null) {
143: $key = $this->_getCookieEncryptionKey();
144: }
145: if ($encrypt === 'rijndael') {
146: $value = Security::rijndael($value, $key, 'decrypt');
147: }
148: if ($encrypt === 'aes') {
149: $value = Security::decrypt($value, $key);
150: }
151:
152: if ($value === false) {
153: return '';
154: }
155:
156: return $this->_explode($value);
157: }
158:
159: /**
160: * Implode method to keep keys are multidimensional arrays
161: *
162: * @param array $array Map of key and values
163: * @return string A json encoded string.
164: */
165: protected function _implode(array $array)
166: {
167: return json_encode($array);
168: }
169:
170: /**
171: * Explode method to return array from string set in CookieComponent::_implode()
172: * Maintains reading backwards compatibility with 1.x CookieComponent::_implode().
173: *
174: * @param string $string A string containing JSON encoded data, or a bare string.
175: * @return string|array Map of key and values
176: */
177: protected function _explode($string)
178: {
179: $first = substr($string, 0, 1);
180: if ($first === '{' || $first === '[') {
181: $ret = json_decode($string, true);
182:
183: return ($ret !== null) ? $ret : $string;
184: }
185: $array = [];
186: foreach (explode(',', $string) as $pair) {
187: $key = explode('|', $pair);
188: if (!isset($key[1])) {
189: return $key[0];
190: }
191: $array[$key[0]] = $key[1];
192: }
193:
194: return $array;
195: }
196: }
197: