Repository navigation
Expand file tree
/
Copy pathEncryption.php
More file actions
141 lines (113 loc) · 4.22 KB
/
Copy pathEncryption.php
File metadata and controls
141 lines (113 loc) · 4.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
<?php
/**
* Matomo - free/libre analytics platform
*
* @link https://matomo.org
* @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\Slack;
use Piwik\Piwik;
use Piwik\Plugins\Slack\Exceptions\SecretConfigurationException;
class Encryption
{
public const ENCRYPTED_PREFIX = 'enc:v1:';
private const CIPHER = 'AES-256-CBC';
/**
* @var Configuration
*/
private $configuration;
/**
* @var string|null
*/
private $key;
public function __construct(?Configuration $configuration = null)
{
$this->configuration = $configuration ?: new Configuration();
}
/**
* Returns an instance that uses the given key instead of the one in the config, eg while the key is rotated.
*/
public static function withKey(
#[\SensitiveParameter]
string $key
): self {
$encryption = new self();
$encryption->key = $key;
return $encryption;
}
public function isEncrypted($value): bool
{
return is_string($value) && strpos($value, self::ENCRYPTED_PREFIX) === 0;
}
public function encryptString(
#[\SensitiveParameter]
string $value
): string {
if (!extension_loaded('openssl')) {
throw new SecretConfigurationException(Piwik::translate('Slack_EncryptionOpenSslRequired'));
}
$key = $this->getEncryptionKey(true);
$ivLength = (int) openssl_cipher_iv_length(self::CIPHER);
$iv = random_bytes($ivLength);
$ciphertext = openssl_encrypt($value, self::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
if (!is_string($ciphertext)) {
throw new SecretConfigurationException(Piwik::translate('Slack_EncryptionFailed'));
}
$payload = [
'iv' => base64_encode($iv),
'value' => base64_encode($ciphertext),
'mac' => base64_encode(hash_hmac('sha256', self::ENCRYPTED_PREFIX . $iv . $ciphertext, $key, true)),
];
return self::ENCRYPTED_PREFIX . base64_encode(json_encode($payload));
}
public function decryptString(
#[\SensitiveParameter]
string $value
): string {
if (!$this->isEncrypted($value)) {
return $value;
}
if (!extension_loaded('openssl')) {
throw new SecretConfigurationException(Piwik::translate('Slack_EncryptionOpenSslRequired'));
}
$key = $this->getEncryptionKey(false);
$encodedPayload = substr($value, strlen(self::ENCRYPTED_PREFIX));
$payload = json_decode(base64_decode($encodedPayload, true) ?: '', true);
if (empty($payload['iv']) || empty($payload['value']) || empty($payload['mac'])) {
throw new SecretConfigurationException($this->getInvalidKeyMessage());
}
$iv = base64_decode($payload['iv'], true);
$ciphertext = base64_decode($payload['value'], true);
$mac = base64_decode($payload['mac'], true);
if (!is_string($iv) || !is_string($ciphertext) || !is_string($mac)) {
throw new SecretConfigurationException($this->getInvalidKeyMessage());
}
$expectedMac = hash_hmac('sha256', self::ENCRYPTED_PREFIX . $iv . $ciphertext, $key, true);
if (!hash_equals($expectedMac, $mac)) {
throw new SecretConfigurationException($this->getInvalidKeyMessage());
}
$plaintext = openssl_decrypt($ciphertext, self::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
if (!is_string($plaintext)) {
throw new SecretConfigurationException($this->getInvalidKeyMessage());
}
return $plaintext;
}
private function getEncryptionKey(bool $createIfMissing): string
{
if ($this->key !== null) {
$key = $this->key;
} else {
$key = $createIfMissing
? $this->configuration->getOrCreateEncryptionKey()
: $this->configuration->getEncryptionKey();
}
if ($key === '') {
throw new SecretConfigurationException($this->getInvalidKeyMessage());
}
return hash('sha256', $key, true);
}
private function getInvalidKeyMessage(): string
{
return Piwik::translate('Slack_EncryptionKeyInvalid');
}
}