PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 4.9.1
WP STAGING – WordPress Backup, Restore, Migration & Clone v4.9.1
4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Framework / Security / DataEncryption.php
wp-staging / Framework / Security Last commit date
Otp 6 months ago AccessToken.php 11 months ago Auth.php 2 years ago Capabilities.php 1 year ago DataEncryption.php 1 month ago EncryptionNoticeService.php 1 month ago Nonce.php 3 years ago UniqueIdentifier.php 3 years ago
DataEncryption.php
417 lines
1 <?php
2
3 namespace WPStaging\Framework\Security;
4
5 use WPStaging\Framework\Facades\Hooks;
6 use WPStaging\Vendor\phpseclib3\Crypt\RSA;
7 use WPStaging\Vendor\phpseclib3\Crypt\PublicKeyLoader;
8
9 use function WPStaging\functions\debug_log;
10
11 /**
12 * Class Data Encryption
13 *
14 * Class responsible for encrypting and decrypting data
15 *
16 * @package WPStaging\Framework\Security
17 */
18 class DataEncryption
19 {
20 /** @var string */
21 const FILTER_FRAMEWORK_SECURITY_DATA_ENCRYPTION_USE_SSL = 'wpstg.framework.security.dataEncryption.useSsl';
22
23 /** @var bool */
24 private $hasSsl;
25
26 /** @var string */
27 private $prefix;
28
29 /** @var string */
30 private $key;
31
32 /** @var string */
33 private $salt;
34
35 public function __construct()
36 {
37 if (!Hooks::applyFilters(self::FILTER_FRAMEWORK_SECURITY_DATA_ENCRYPTION_USE_SSL, true)) {
38 $this->hasSsl = false;
39 } else {
40 $this->hasSsl = extension_loaded('openssl') && function_exists('openssl_encrypt') && function_exists('openssl_decrypt') && (bool)in_array('aes-256-ctr', openssl_get_cipher_methods());
41 }
42
43 $this->prefix = '!wpstg!';
44 $this->key = $this->getDefaultKey();
45 $this->salt = $this->getDefaultSalt();
46 }
47
48 public function isPhpSecLibAvailable(): bool
49 {
50 return class_exists(\WPStaging\Vendor\phpseclib3\Crypt\PublicKeyLoader::class);
51 }
52
53 /**
54 * @param string|int $value
55 * @return string
56 */
57 public function encrypt($value): string
58 {
59 if ($this->hasSsl) {
60 return $this->sslEncrypt($value);
61 }
62
63 return $this->base64Encrypt($value);
64 }
65
66 /**
67 * @param string $value
68 * @return string
69 */
70 public function decrypt(string $value): string
71 {
72 if ($this->verifyPrefix($value, 'ssl')) {
73 return $this->sslDecrypt($value);
74 }
75
76 return $this->base64Decrypt($value);
77 }
78
79 /**
80 * @param string|int $value
81 * @return string
82 */
83 protected function base64Encrypt($value): string
84 {
85 if (!$this->isValidKeySalt() || $value === '' || $this->isEncrypted($value)) {
86 return (string)$value;
87 }
88
89 $mykey = $this->key . $this->salt;
90 $encpad = substr($mykey, 0, 12);
91 $value = $encpad . $value;
92
93 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
94 $pad = base64_decode($mykey);
95 $valueLen = strlen($value);
96 $encrypted = '';
97 $x = 0;
98 for ($i = 0; $i < $valueLen; $i++, $x++) {
99 if (!isset($pad[$x])) {
100 $x = 0;
101 }
102
103 $padi = $pad[$x];
104 $encrypted .= chr(ord($value[$i]) ^ ord($padi));
105 }
106
107 return $this->setPrefixType('b64') . $this->normalizeBase64Encode($encrypted);
108 }
109
110 /**
111 * @param string $inputValue
112 * @return string
113 */
114 protected function base64Decrypt(string $inputValue): string
115 {
116 if (!$this->isValidKeySalt() || !is_string($inputValue) || $inputValue === '' || !$this->isEncrypted($inputValue) || !$this->verifyPrefix($inputValue, 'b64')) {
117 return $inputValue;
118 }
119
120 $value = $this->stripPrefix($inputValue, 'b64');
121 $mykey = $this->key . $this->salt;
122
123 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
124 $pad = base64_decode($mykey);
125 $encrypted = $this->normalizeBase64Decode($value);
126 $encryptedLen = strlen($encrypted);
127 $decrypted = '';
128 $x = 0;
129 for ($i = 0; $i < $encryptedLen; $i++, $x++) {
130 if (!isset($pad[$x])) {
131 $x = 0;
132 }
133
134 $padi = $pad[$x];
135 $decrypted .= chr(ord($encrypted[$i]) ^ ord($padi));
136 }
137
138 $encpad = substr($mykey, 0, 12);
139 $enclen = strlen($encpad);
140 $envpad = substr($decrypted, 0, $enclen);
141
142 if ($encpad !== $envpad) {
143 return $inputValue;
144 }
145
146 $decrypted = substr($decrypted, $enclen);
147 return $decrypted;
148 }
149
150 /**
151 * @param string|int $value
152 * @return string
153 */
154 protected function sslEncrypt($value): string
155 {
156 if (!$this->isValidKeySalt() || $value === '' || !$this->hasSsl || $this->isEncrypted($value)) {
157 return (string)$value;
158 }
159
160 $method = 'aes-256-ctr';
161 $ivlen = openssl_cipher_iv_length($method);
162 $iv = openssl_random_pseudo_bytes($ivlen);
163
164 $rawValue = openssl_encrypt($value . $this->salt, $method, $this->key, 0, $iv);
165 if (!$rawValue) {
166 return (string)$value;
167 }
168
169 return $this->setPrefixType('ssl') . $this->normalizeBase64Encode($iv . $rawValue);
170 }
171
172 /**
173 * @param string $inputValue
174 * @return string
175 */
176 protected function sslDecrypt(string $inputValue): string
177 {
178 if (!$this->isValidKeySalt() || !is_string($inputValue) || $inputValue === '' || !$this->hasSsl || !$this->isEncrypted($inputValue) || !$this->verifyPrefix($inputValue, 'ssl')) {
179 return $inputValue;
180 }
181
182 $rawValue = $this->stripPrefix($inputValue, 'ssl');
183 $rawValue = $this->normalizeBase64Decode($rawValue);
184
185 $method = 'aes-256-ctr';
186 $ivlen = openssl_cipher_iv_length($method);
187 $iv = substr($rawValue, 0, $ivlen);
188
189 $rawValue = substr($rawValue, $ivlen);
190
191 $value = openssl_decrypt($rawValue, $method, $this->key, 0, $iv);
192 if (!$value || $this->salt !== substr($value, - strlen($this->salt))) {
193 return $inputValue;
194 }
195
196 return substr($value, 0, - strlen($this->salt));
197 }
198
199 /** @return true */
200 protected function disableUseSssl()
201 {
202 $this->hasSsl = false;
203 return true;
204 }
205
206 /**
207 * @param string $value
208 * @return string
209 */
210 public function setKey(string $value): string
211 {
212 $this->key = (string)$value;
213 return $this->key;
214 }
215
216 /**
217 * @param string $value
218 * @return string
219 */
220 public function setSalt(string $value): string
221 {
222 $this->salt = (string)$value;
223 return $this->salt;
224 }
225
226 /** @return string */
227 public function getKey(): string
228 {
229 return $this->key;
230 }
231
232 /** @return string */
233 public function getSalt(): string
234 {
235 return $this->salt;
236 }
237
238 /**
239 * @param string $value
240 * @param string $type
241 * @return bool
242 */
243 protected function verifyPrefix(string $value, string $type): bool
244 {
245 $type = '!' . $type . '!';
246 return substr($value, 0, strlen($this->prefix . $type)) === $this->prefix . $type;
247 }
248
249 /**
250 * @param string $value
251 * @return bool
252 */
253 public function isEncrypted(string $value): bool
254 {
255 return preg_match('@^' . preg_quote($this->prefix, '@') . '!(b64|ssl|rsa)!([a-zA-Z0-9\-_]+)$@', $value);
256 }
257
258 /** @return bool */
259 protected function isValidKeySalt(): bool
260 {
261 $key = $this->getKey();
262 $salt = $this->getSalt();
263 return (!empty($key) && is_string($key)) && (!empty($salt) && is_string($salt));
264 }
265
266 /** @return string */
267 protected function getPrefix(): string
268 {
269 return $this->prefix;
270 }
271
272 /**
273 * @param string $value
274 * @return string
275 */
276 protected function setPrefix(string $value): string
277 {
278 $this->prefix = $value;
279 return $this->prefix;
280 }
281
282 /** @return string|false */
283 private function getDefaultKey()
284 {
285 if (defined('WPSTG_ENCRYPTION_KEY') && !empty(WPSTG_ENCRYPTION_KEY) && is_string(WPSTG_ENCRYPTION_KEY)) {
286 return WPSTG_ENCRYPTION_KEY;
287 }
288
289 if (defined('AUTH_KEY') && !empty(AUTH_KEY) && is_string(AUTH_KEY)) {
290 return AUTH_KEY;
291 }
292
293 return false;
294 }
295
296 /** @return string|false */
297 private function getDefaultSalt()
298 {
299 if (defined('WPSTG_ENCRYPTION_SALT') && !empty(WPSTG_ENCRYPTION_SALT) && is_string(WPSTG_ENCRYPTION_SALT)) {
300 return WPSTG_ENCRYPTION_SALT;
301 }
302
303 if (defined('AUTH_SALT') && !empty(AUTH_SALT) && is_string(AUTH_SALT)) {
304 return AUTH_SALT;
305 }
306
307 return false;
308 }
309
310 /**
311 * @param string $value
312 * @param string $type
313 * @return string
314 */
315 private function stripPrefix(string $value, string $type): string
316 {
317 $type = '!' . $type . '!';
318 return substr($value, strlen($this->prefix . $type));
319 }
320
321 /**
322 * @param string $type
323 * @return string
324 */
325 private function setPrefixType(string $type): string
326 {
327 return $this->prefix . '!' . $type . '!';
328 }
329
330 /**
331 * @param string $value
332 * @return string
333 */
334 private function normalizeBase64Encode(string $value): string
335 {
336 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
337 return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($value));
338 }
339
340 /**
341 * @param string $value
342 * @return string
343 */
344 private function normalizeBase64Decode(string $value)
345 {
346 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
347 return base64_decode(str_replace(['-', '_'], ['+', '/'], $value));
348 }
349
350 public function getPublicKey(): string
351 {
352 $keyPath = WPSTG_RESOURCES_DIR . '/wpstg-public-key.pem';
353
354 if (!file_exists($keyPath)) {
355 return '';
356 }
357
358 $keyData = file_get_contents($keyPath);
359 return $keyData;
360 }
361
362 public function generateKeys(): array
363 {
364 $privateKey = RSA::createKey();
365 $publicKey = $privateKey->getPublicKey();
366
367 return [
368 'publicKey' => $publicKey,
369 'privateKey' => $privateKey,
370 ];
371 }
372
373 /**
374 * @param string|int $value
375 * @param string $publicKey
376 * @return string
377 */
378 public function rsaEncrypt($value, string $publicKey): string
379 {
380 if ($value === '' || $this->isEncrypted($value)) {
381 return (string)$value;
382 }
383
384 $keyHandle = PublicKeyLoader::load($publicKey);
385 if (!is_object($keyHandle) || !strpos(get_class($keyHandle), 'RSA\PublicKey') || !method_exists($keyHandle, 'encrypt')) {
386 debug_log(sprintf('[DataEncryption] Failed to load public key: %s', get_class($keyHandle)), 'info', false);
387 return (string)$value;
388 }
389
390 $cipherText = $keyHandle->encrypt($value);
391 return $this->setPrefixType('rsa') . $this->normalizeBase64Encode($cipherText);
392 }
393
394 /**
395 * @param string $inputValue
396 * @param string $privateKey
397 * @return string
398 */
399 public function rsaDecrypt(string $inputValue, string $privateKey): string
400 {
401 if (!is_string($inputValue) || $inputValue === '' || !$this->isEncrypted($inputValue) || !$this->verifyPrefix($inputValue, 'rsa')) {
402 return $inputValue;
403 }
404
405 $keyHandle = PublicKeyLoader::load($privateKey);
406 if (!is_object($keyHandle) || !strpos(get_class($keyHandle), 'RSA\PrivateKey') || !method_exists($keyHandle, 'decrypt')) {
407 debug_log(sprintf('[DataEncryption] Failed to load private key: %s', get_class($keyHandle)), 'info', false);
408 return $inputValue;
409 }
410
411 $value = $this->stripPrefix($inputValue, 'rsa');
412 $value = $this->normalizeBase64Decode($value);
413
414 return $keyHandle->decrypt($value);
415 }
416 }
417