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