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