1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Psr7Middlewares\Utils; |
4
|
|
|
|
5
|
|
|
use RuntimeException; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* Trait used by all middlewares that needs encrypt/decrypt functions |
9
|
|
|
* Most of code is from https://github.com/illuminate/encryption. |
10
|
|
|
*/ |
11
|
|
|
trait CryptTrait |
12
|
|
|
{ |
13
|
|
|
protected $cipher; |
14
|
|
|
protected $key; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Set the key and cipher used by the crypt. |
18
|
|
|
* |
19
|
|
|
* @param string $key |
20
|
|
|
* @param string $cipher |
21
|
|
|
* |
22
|
|
|
* @return self |
23
|
|
|
*/ |
24
|
|
|
public function crypt($key, $cipher = 'AES-128-CBC') |
25
|
|
|
{ |
26
|
|
|
$length = mb_strlen($key, '8bit'); |
27
|
|
|
|
28
|
|
|
if (!(($cipher === 'AES-128-CBC' && $length === 16) || ($cipher === 'AES-256-CBC' && $length === 32))) { |
29
|
|
|
throw new RuntimeException('The only supported ciphers are AES-128-CBC and AES-256-CBC with the correct key lengths.'); |
30
|
|
|
} |
31
|
|
|
|
32
|
|
|
$this->key = $key; |
33
|
|
|
$this->cipher = $cipher; |
34
|
|
|
|
35
|
|
|
return $this; |
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* Generates the default cipher and key. |
40
|
|
|
*/ |
41
|
|
|
protected function generateCryptKey() |
42
|
|
|
{ |
43
|
|
|
$this->crypt(substr(md5(__DIR__), 0, 16)); |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Encrypt the given value. |
48
|
|
|
* |
49
|
|
|
* @param string $value |
50
|
|
|
* |
51
|
|
|
* @return string |
52
|
|
|
*/ |
53
|
|
|
protected function encrypt($value) |
54
|
|
|
{ |
55
|
|
|
$iv = random_bytes(16); |
|
|
|
|
56
|
|
|
$value = openssl_encrypt(serialize($value), $this->cipher, $this->key, 0, $iv); |
57
|
|
|
|
58
|
|
|
if ($value === false) { |
59
|
|
|
throw new RuntimeException('Could not encrypt the data.'); |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
$iv = base64_encode($iv); |
63
|
|
|
$mac = hash_hmac('sha256', $iv.$value, $this->key); |
64
|
|
|
|
65
|
|
|
return base64_encode(json_encode(compact('iv', 'value', 'mac'))); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Decrypt the given value. |
70
|
|
|
* |
71
|
|
|
* @param string $payload |
72
|
|
|
* |
73
|
|
|
* @return string |
74
|
|
|
*/ |
75
|
|
|
protected function decrypt($payload) |
76
|
|
|
{ |
77
|
|
|
$payload = json_decode(base64_decode($payload), true); |
78
|
|
|
|
79
|
|
|
if (!$payload || $this->invalidPayload($payload)) { |
80
|
|
|
throw new RuntimeException('The payload is invalid.'); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
if (!$this->validMac($payload)) { |
84
|
|
|
throw new RuntimeException('The MAC is invalid.'); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
$iv = base64_decode($payload['iv']); |
88
|
|
|
$decrypted = openssl_decrypt($payload['value'], $this->cipher, $this->key, 0, $iv); |
89
|
|
|
|
90
|
|
|
if ($decrypted === false) { |
91
|
|
|
throw new RuntimeException('Could not decrypt the data.'); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
return unserialize($decrypted); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Create a MAC for the given value. |
99
|
|
|
* |
100
|
|
|
* @param string $iv |
101
|
|
|
* @param string $value |
102
|
|
|
* |
103
|
|
|
* @return string |
104
|
|
|
*/ |
105
|
|
|
protected function hash($iv, $value) |
106
|
|
|
{ |
107
|
|
|
return hash_hmac('sha256', $iv.$value, $this->key); |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Verify that the encryption payload is valid. |
112
|
|
|
* |
113
|
|
|
* @param array|mixed $data |
114
|
|
|
* |
115
|
|
|
* @return bool |
116
|
|
|
*/ |
117
|
|
|
protected function invalidPayload($data) |
118
|
|
|
{ |
119
|
|
|
return !is_array($data) || !isset($data['iv']) || !isset($data['value']) || !isset($data['mac']); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Determine if the MAC for the given payload is valid. |
124
|
|
|
* |
125
|
|
|
* @param array $payload |
126
|
|
|
* |
127
|
|
|
* @throws \RuntimeException |
128
|
|
|
* |
129
|
|
|
* @return bool |
130
|
|
|
*/ |
131
|
|
|
protected function validMac(array $payload) |
132
|
|
|
{ |
133
|
|
|
$bytes = random_bytes(16); |
|
|
|
|
134
|
|
|
$calcMac = hash_hmac('sha256', $this->hash($payload['iv'], $payload['value']), $bytes, true); |
135
|
|
|
|
136
|
|
|
return hash_equals(hash_hmac('sha256', $payload['mac'], $bytes, true), $calcMac); |
137
|
|
|
} |
138
|
|
|
} |
139
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.