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: /**
30: * Valid cipher names for encrypted cookies.
31: *
32: * @var array
33: */
34: protected $_validCiphers = ['aes', 'rijndael'];
35:
36: /**
37: * Returns the encryption key to be used.
38: *
39: * @return string
40: */
41: abstract protected function _getCookieEncryptionKey();
42:
43: /**
44: * Encrypts $value using public $type method in Security class
45: *
46: * @param string $value Value to encrypt
47: * @param string|bool $encrypt Encryption mode to use. False
48: * disabled encryption.
49: * @param string|null $key Used as the security salt if specified.
50: * @return string Encoded values
51: */
52: protected function _encrypt($value, $encrypt, $key = null)
53: {
54: if (is_array($value)) {
55: $value = $this->_implode($value);
56: }
57: if ($encrypt === false) {
58: return $value;
59: }
60: $this->_checkCipher($encrypt);
61: $prefix = 'Q2FrZQ==.';
62: $cipher = null;
63: if ($key === null) {
64: $key = $this->_getCookieEncryptionKey();
65: }
66: if ($encrypt === 'rijndael') {
67: $cipher = Security::rijndael($value, $key, 'encrypt');
68: }
69: if ($encrypt === 'aes') {
70: $cipher = Security::encrypt($value, $key);
71: }
72:
73: return $prefix . base64_encode($cipher);
74: }
75:
76: /**
77: * Helper method for validating encryption cipher names.
78: *
79: * @param string $encrypt The cipher name.
80: * @return void
81: * @throws \RuntimeException When an invalid cipher is provided.
82: */
83: protected function _checkCipher($encrypt)
84: {
85: if (!in_array($encrypt, $this->_validCiphers)) {
86: $msg = sprintf(
87: 'Invalid encryption cipher. Must be one of %s or false.',
88: implode(', ', $this->_validCiphers)
89: );
90: throw new RuntimeException($msg);
91: }
92: }
93:
94: /**
95: * Decrypts $value using public $type method in Security class
96: *
97: * @param array $values Values to decrypt
98: * @param string|bool $mode Encryption mode
99: * @param string|null $key Used as the security salt if specified.
100: * @return string|array Decrypted values
101: */
102: protected function _decrypt($values, $mode, $key = null)
103: {
104: if (is_string($values)) {
105: return $this->_decode($values, $mode, $key);
106: }
107:
108: $decrypted = [];
109: foreach ($values as $name => $value) {
110: $decrypted[$name] = $this->_decode($value, $mode, $key);
111: }
112:
113: return $decrypted;
114: }
115:
116: /**
117: * Decodes and decrypts a single value.
118: *
119: * @param string $value The value to decode & decrypt.
120: * @param string|false $encrypt The encryption cipher to use.
121: * @param string|null $key Used as the security salt if specified.
122: * @return string|array Decoded values.
123: */
124: protected function _decode($value, $encrypt, $key)
125: {
126: if (!$encrypt) {
127: return $this->_explode($value);
128: }
129: $this->_checkCipher($encrypt);
130: $prefix = 'Q2FrZQ==.';
131: $prefixLength = strlen($prefix);
132:
133: if (strncmp($value, $prefix, $prefixLength) !== 0) {
134: return '';
135: }
136:
137: $value = base64_decode(substr($value, $prefixLength), true);
138:
139: if ($value === false || $value === '') {
140: return '';
141: }
142:
143: if ($key === null) {
144: $key = $this->_getCookieEncryptionKey();
145: }
146: if ($encrypt === 'rijndael') {
147: $value = Security::rijndael($value, $key, 'decrypt');
148: }
149: if ($encrypt === 'aes') {
150: $value = Security::decrypt($value, $key);
151: }
152:
153: if ($value === false) {
154: return '';
155: }
156:
157: return $this->_explode($value);
158: }
159:
160: /**
161: * Implode method to keep keys are multidimensional arrays
162: *
163: * @param array $array Map of key and values
164: * @return string A json encoded string.
165: */
166: protected function _implode(array $array)
167: {
168: return json_encode($array);
169: }
170:
171: /**
172: * Explode method to return array from string set in CookieComponent::_implode()
173: * Maintains reading backwards compatibility with 1.x CookieComponent::_implode().
174: *
175: * @param string $string A string containing JSON encoded data, or a bare string.
176: * @return string|array Map of key and values
177: */
178: protected function _explode($string)
179: {
180: $first = substr($string, 0, 1);
181: if ($first === '{' || $first === '[') {
182: $ret = json_decode($string, true);
183:
184: return ($ret !== null) ? $ret : $string;
185: }
186: $array = [];
187: foreach (explode(',', $string) as $pair) {
188: $key = explode('|', $pair);
189: if (!isset($key[1])) {
190: return $key[0];
191: }
192: $array[$key[0]] = $key[1];
193: }
194:
195: return $array;
196: }
197: }
198: