PluginProbe ʕ •ᴥ•ʔ
Download Manager / trunk
Download Manager vtrunk
3.3.62 3.3.61 3.3.60 3.3.59 3.3.58 3.3.57 3.3.56 trunk 2.1.3 2.3.0 2.5.96 2.5.97 2.6.2 2.6.96 2.8.3 2.9.99 3.0.4 3.1.05 3.1.07 3.1.08 3.1.11 3.1.12 3.1.14 3.1.17 3.1.18 3.1.22 3.1.23 3.1.24 3.1.25 3.1.26 3.1.27 3.1.28 3.2.04 3.2.13 3.2.14 3.2.16 3.2.18 3.2.19 3.2.21 3.2.22 3.2.23 3.2.24 3.2.25 3.2.27 3.2.28 3.2.29 3.2.30 3.2.31 3.2.32 3.2.33 3.2.34 3.2.35 3.2.37 3.2.38 3.2.39 3.2.40 3.2.41 3.2.42 3.2.43 3.2.44 3.2.45 3.2.46 3.2.47 3.2.48 3.2.49 3.2.50 3.2.51 3.2.52 3.2.53 3.2.54 3.2.55 3.2.56 3.2.57 3.2.58 3.2.59 3.2.60 3.2.61 3.2.63 3.2.64 3.2.65 3.2.66 3.2.67 3.2.68 3.2.69 3.2.70 3.2.71 3.2.72 3.2.73 3.2.74 3.2.75 3.2.76 3.2.77 3.2.78 3.2.79 3.2.80 3.2.81 3.2.82 3.2.83 3.2.84 3.2.85 3.2.86 3.2.87 3.2.88 3.2.89 3.2.90 3.2.91 3.2.92 3.2.93 3.2.94 3.2.95 3.2.96 3.2.97 3.2.98 3.2.99 3.3.00 3.3.01 3.3.02 3.3.03 3.3.04 3.3.05 3.3.06 3.3.07 3.3.08 3.3.09 3.3.10 3.3.11 3.3.12 3.3.13 3.3.14 3.3.15 3.3.16 3.3.17 3.3.18 3.3.19 3.3.20 3.3.21 3.3.22 3.3.23 3.3.24 3.3.25 3.3.26 3.3.27 3.3.28 3.3.29 3.3.30 3.3.31 3.3.32 3.3.33 3.3.34 3.3.35 3.3.36 3.3.37 3.3.38 3.3.39 3.3.40 3.3.41 3.3.42 3.3.43 3.3.44 3.3.45 3.3.46 3.3.47 3.3.48 3.3.49 3.3.50 3.3.51 3.3.52 3.3.53 3.3.54 3.3.55
download-manager / src / __ / Crypt.php
download-manager / src / __ Last commit date
HTML 1 year ago views 5 months ago Apply.php 6 months ago Cron.php 1 year ago CronJob.php 7 months ago CronJobs.php 2 months ago Crypt.php 1 month ago DownloadStats.php 5 months ago Email.php 4 days ago EmailCron.php 1 year ago FileSystem.php 1 year ago Installer.php 11 hours ago Messages.php 1 year ago Query.php 4 months ago Session.php 11 hours ago Settings.php 4 years ago SimpleMath.php 4 years ago TempStorage.php 11 hours ago Template.php 5 months ago UI.php 6 months ago Updater.php 4 years ago UserAgent.php 2 years ago __.php 1 month ago __MailUI.php 3 years ago
Crypt.php
589 lines
1 <?php
2
3 /**
4 * Class Crypt
5 * From v4.1.9
6 * Updated in v4.7.5
7 * Security fix in v7.x - Fixed CBC bit-flipping vulnerability (HMAC now includes IV)
8 * - Added Sodium fallback for environments without OpenSSL
9 * - Added pure PHP fallback for maximum compatibility
10 * - Replaced mt_rand() with cryptographically secure random
11 */
12
13 namespace WPDM\__;
14
15
16 class Crypt
17 {
18 /**
19 * Encryption method identifiers (prepended to ciphertext for decryption routing)
20 */
21 private const METHOD_OPENSSL = 'O'; // OpenSSL AES-128-CBC
22 private const METHOD_SODIUM = 'S'; // Sodium XSalsa20-Poly1305
23 private const METHOD_PHP = 'P'; // Pure PHP fallback
24
25 /**
26 * Flag to track if we've logged the PHP fallback warning
27 */
28 private static bool $phpFallbackWarningLogged = false;
29
30 function __construct()
31 {
32 }
33
34 /**
35 * Check if OpenSSL is available
36 */
37 private static function hasOpenSSL(): bool
38 {
39 return function_exists('openssl_cipher_iv_length') && function_exists('openssl_encrypt');
40 }
41
42 /**
43 * Check if Sodium is available
44 */
45 private static function hasSodium(): bool
46 {
47 return function_exists('sodium_crypto_secretbox') && defined('SODIUM_CRYPTO_SECRETBOX_KEYBYTES');
48 }
49
50 /**
51 * Get the encryption key
52 */
53 private static function getKey(): string
54 {
55 $encKey = defined('WPDM_ENC_KEY') ? WPDM_ENC_KEY : get_option('__wpdm_enc_key');
56
57 if (!$encKey) {
58 $encKey = self::encKey();
59 update_option('__wpdm_enc_key', $encKey);
60 }
61
62 return $encKey;
63 }
64
65 /**
66 * Encrypt data using the best available method
67 *
68 * Priority: OpenSSL > Sodium > Pure PHP
69 *
70 * @param mixed $text Data to encrypt (string, array, or object)
71 * @return string Encrypted data (URL-safe base64) or empty string on failure
72 */
73 public static function encrypt($text)
74 {
75 if ($text === '' || $text === null) return '';
76
77 $text = is_array($text) || is_object($text) ? json_encode($text) : $text;
78 $encKey = self::getKey();
79
80 // Try OpenSSL first (most secure, most common)
81 if (self::hasOpenSSL()) {
82 return self::encryptOpenSSL($text, $encKey);
83 }
84
85 // Fallback to Sodium (very secure, bundled in PHP 7.2+)
86 if (self::hasSodium()) {
87 return self::encryptSodium($text, $encKey);
88 }
89
90 // Final fallback: Pure PHP (works everywhere, basic security)
91 self::logPhpFallbackWarning();
92 return self::encryptPHP($text, $encKey);
93 }
94
95 /**
96 * Decrypt data (auto-detects encryption method)
97 *
98 * @param string $ciphertext Encrypted data
99 * @param bool $ARRAY If true, return associative array instead of object for JSON
100 * @return mixed Decrypted data or empty string on failure
101 */
102 public static function decrypt($ciphertext, $ARRAY = false)
103 {
104 if ($ciphertext === '' || $ciphertext === null) return $ciphertext;
105
106 $encKey = self::getKey();
107 if (!$encKey) return '';
108
109 // Restore base64 characters
110 $ciphertext = str_replace(array('-', '_'), array('+', '/'), $ciphertext);
111
112 // Detect encryption method from prefix
113 $method = substr($ciphertext, 0, 1);
114 $data = substr($ciphertext, 1);
115
116 $plaintext = '';
117
118 switch ($method) {
119 case self::METHOD_OPENSSL:
120 if (self::hasOpenSSL()) {
121 $plaintext = self::decryptOpenSSL($data, $encKey);
122 } else {
123 error_log('WPDM Crypt: Data was encrypted with OpenSSL but OpenSSL is not available.');
124 return '';
125 }
126 break;
127
128 case self::METHOD_SODIUM:
129 if (self::hasSodium()) {
130 $plaintext = self::decryptSodium($data, $encKey);
131 } else {
132 error_log('WPDM Crypt: Data was encrypted with Sodium but Sodium is not available.');
133 return '';
134 }
135 break;
136
137 case self::METHOD_PHP:
138 $plaintext = self::decryptPHP($data, $encKey);
139 break;
140
141 default:
142 // Legacy data (no method prefix) - try OpenSSL legacy format first
143 if (self::hasOpenSSL()) {
144 $plaintext = self::decryptOpenSSLLegacy($ciphertext, $encKey);
145 }
146 // If OpenSSL legacy failed or not available, try old base64 format
147 if ($plaintext === '' || $plaintext === false) {
148 $plaintext = self::decryptLegacyBase64($ciphertext, $encKey);
149 }
150 break;
151 }
152
153 if ($plaintext === '' || $plaintext === false) {
154 return '';
155 }
156
157 // Try to decode JSON
158 $plaintext = trim($plaintext);
159 $decoded = json_decode($plaintext, $ARRAY);
160
161 if (is_object($decoded) || is_array($decoded)) {
162 return $decoded;
163 }
164
165 return $plaintext;
166 }
167
168 /**
169 * Log warning about PHP fallback (only once per request)
170 */
171 private static function logPhpFallbackWarning(): void
172 {
173 if (!self::$phpFallbackWarningLogged) {
174 error_log('WPDM Crypt: Using pure PHP encryption fallback. For better security, please enable OpenSSL or Sodium PHP extension.');
175 self::$phpFallbackWarningLogged = true;
176 }
177 }
178
179 // =========================================================================
180 // OpenSSL Methods (Primary - Most Secure)
181 // =========================================================================
182
183 /**
184 * Encrypt using OpenSSL AES-128-CBC with HMAC
185 */
186 private static function encryptOpenSSL(string $text, string $key): string
187 {
188 $cipher = "AES-128-CBC";
189 $ivlen = openssl_cipher_iv_length($cipher);
190 $iv = openssl_random_pseudo_bytes($ivlen);
191
192 $ciphertext_raw = openssl_encrypt($text, $cipher, $key, OPENSSL_RAW_DATA, $iv);
193
194 if ($ciphertext_raw === false) {
195 return '';
196 }
197
198 // SECURITY FIX: HMAC includes IV to prevent CBC bit-flipping attacks
199 $hmac = hash_hmac('sha256', $iv . $ciphertext_raw, $key, true);
200
201 // Format: METHOD_PREFIX + base64(IV + HMAC + CIPHERTEXT)
202 $encoded = base64_encode($iv . $hmac . $ciphertext_raw);
203 $encoded = str_replace(array('+', '/', '='), array('-', '_', ''), $encoded);
204
205 return self::METHOD_OPENSSL . $encoded;
206 }
207
208 /**
209 * Decrypt OpenSSL encrypted data (new format with IV in HMAC)
210 */
211 private static function decryptOpenSSL(string $data, string $key): string
212 {
213 $c = base64_decode($data);
214 if ($c === false) return '';
215
216 $cipher = "AES-128-CBC";
217 $ivlen = openssl_cipher_iv_length($cipher);
218 $hmaclen = 32; // SHA-256
219
220 if (strlen($c) < $ivlen + $hmaclen + 1) return '';
221
222 $iv = substr($c, 0, $ivlen);
223 $hmac = substr($c, $ivlen, $hmaclen);
224 $ciphertext_raw = substr($c, $ivlen + $hmaclen);
225
226 if (empty($ciphertext_raw)) return '';
227
228 // SECURITY FIX: HMAC includes IV
229 $calcmac = hash_hmac('sha256', $iv . $ciphertext_raw, $key, true);
230
231 if (!hash_equals($hmac, $calcmac)) {
232 return '';
233 }
234
235 $plaintext = openssl_decrypt($ciphertext_raw, $cipher, $key, OPENSSL_RAW_DATA, $iv);
236
237 return $plaintext !== false ? $plaintext : '';
238 }
239
240 /**
241 * Decrypt legacy OpenSSL format (HMAC without IV - for backwards compatibility)
242 */
243 private static function decryptOpenSSLLegacy(string $ciphertext, string $key): string
244 {
245 $c = base64_decode($ciphertext);
246 if ($c === false) return '';
247
248 $cipher = "AES-128-CBC";
249 $ivlen = openssl_cipher_iv_length($cipher);
250 $hmaclen = 32;
251
252 if (strlen($c) < $ivlen + $hmaclen + 1) return '';
253
254 $iv = substr($c, 0, $ivlen);
255 $hmac = substr($c, $ivlen, $hmaclen);
256 $ciphertext_raw = substr($c, $ivlen + $hmaclen);
257
258 if (empty($ciphertext_raw)) return '';
259
260 // Legacy format: HMAC only covers ciphertext
261 $calcmac = hash_hmac('sha256', $ciphertext_raw, $key, true);
262
263 if (!hash_equals($hmac, $calcmac)) {
264 return '';
265 }
266
267 $plaintext = openssl_decrypt($ciphertext_raw, $cipher, $key, OPENSSL_RAW_DATA, $iv);
268
269 return $plaintext !== false ? $plaintext : '';
270 }
271
272 // =========================================================================
273 // Sodium Methods (Secondary - Very Secure)
274 // =========================================================================
275
276 /**
277 * Encrypt using Sodium (XSalsa20-Poly1305)
278 */
279 private static function encryptSodium(string $text, string $key): string
280 {
281 // Derive a proper 32-byte key from the variable-length key
282 $derivedKey = sodium_crypto_generichash($key, '', SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
283
284 // Generate random nonce
285 $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
286
287 // Encrypt with authenticated encryption
288 $ciphertext = sodium_crypto_secretbox($text, $nonce, $derivedKey);
289
290 // Clear sensitive data from memory
291 sodium_memzero($derivedKey);
292
293 // Format: METHOD_PREFIX + base64(NONCE + CIPHERTEXT)
294 $encoded = base64_encode($nonce . $ciphertext);
295 $encoded = str_replace(array('+', '/', '='), array('-', '_', ''), $encoded);
296
297 return self::METHOD_SODIUM . $encoded;
298 }
299
300 /**
301 * Decrypt Sodium encrypted data
302 */
303 private static function decryptSodium(string $data, string $key): string
304 {
305 $c = base64_decode($data);
306 if ($c === false) return '';
307
308 $nonceLen = SODIUM_CRYPTO_SECRETBOX_NONCEBYTES;
309
310 if (strlen($c) < $nonceLen + SODIUM_CRYPTO_SECRETBOX_MACBYTES + 1) {
311 return '';
312 }
313
314 $nonce = substr($c, 0, $nonceLen);
315 $ciphertext = substr($c, $nonceLen);
316
317 // Derive the same key used for encryption
318 $derivedKey = sodium_crypto_generichash($key, '', SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
319
320 try {
321 $plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $derivedKey);
322 } catch (\SodiumException $e) {
323 $plaintext = false;
324 }
325
326 // Clear sensitive data from memory
327 sodium_memzero($derivedKey);
328
329 return $plaintext !== false ? $plaintext : '';
330 }
331
332 // =========================================================================
333 // Pure PHP Fallback (Works Everywhere - Basic Security)
334 // =========================================================================
335
336 /**
337 * Encrypt using pure PHP (XOR cipher with key stretching + HMAC)
338 *
339 * This is NOT as secure as OpenSSL/Sodium but provides:
340 * - Data obfuscation
341 * - Integrity verification (HMAC)
342 * - Works on ANY PHP installation
343 *
344 * @param string $text Plaintext to encrypt
345 * @param string $key Encryption key
346 * @return string Encrypted data
347 */
348 private static function encryptPHP(string $text, string $key): string
349 {
350 // Generate random IV (16 bytes)
351 $iv = self::generateRandomBytes(16);
352
353 // Derive encryption key using PBKDF2-like stretching
354 $derivedKey = self::deriveKey($key, $iv, 32);
355
356 // XOR encrypt
357 $ciphertext = self::xorCrypt($text, $derivedKey);
358
359 // Generate HMAC for integrity (includes IV)
360 $hmac = hash_hmac('sha256', $iv . $ciphertext, $key, true);
361
362 // Format: METHOD_PREFIX + base64(IV + HMAC + CIPHERTEXT)
363 $encoded = base64_encode($iv . $hmac . $ciphertext);
364 $encoded = str_replace(array('+', '/', '='), array('-', '_', ''), $encoded);
365
366 return self::METHOD_PHP . $encoded;
367 }
368
369 /**
370 * Decrypt pure PHP encrypted data
371 */
372 private static function decryptPHP(string $data, string $key): string
373 {
374 $c = base64_decode($data);
375 if ($c === false) return '';
376
377 $ivlen = 16;
378 $hmaclen = 32;
379
380 if (strlen($c) < $ivlen + $hmaclen + 1) return '';
381
382 $iv = substr($c, 0, $ivlen);
383 $hmac = substr($c, $ivlen, $hmaclen);
384 $ciphertext = substr($c, $ivlen + $hmaclen);
385
386 if (empty($ciphertext)) return '';
387
388 // Verify HMAC (includes IV for integrity)
389 $calcmac = hash_hmac('sha256', $iv . $ciphertext, $key, true);
390
391 if (!hash_equals($hmac, $calcmac)) {
392 return '';
393 }
394
395 // Derive the same key used for encryption
396 $derivedKey = self::deriveKey($key, $iv, 32);
397
398 // XOR decrypt (same operation as encrypt)
399 return self::xorCrypt($ciphertext, $derivedKey);
400 }
401
402 /**
403 * XOR cipher - simple but effective when combined with proper key derivation
404 */
405 private static function xorCrypt(string $data, string $key): string
406 {
407 $keyLen = strlen($key);
408 $dataLen = strlen($data);
409 $result = '';
410
411 for ($i = 0; $i < $dataLen; $i++) {
412 $result .= $data[$i] ^ $key[$i % $keyLen];
413 }
414
415 return $result;
416 }
417
418 /**
419 * Derive a key using multiple rounds of hashing (PBKDF2-like)
420 */
421 private static function deriveKey(string $key, string $salt, int $length): string
422 {
423 // Use PBKDF2 if available (PHP 5.5+), otherwise manual derivation
424 if (function_exists('hash_pbkdf2')) {
425 return hash_pbkdf2('sha256', $key, $salt, 10000, $length, true);
426 }
427
428 // Manual PBKDF2-like derivation for older PHP
429 $derivedKey = '';
430 $block = 1;
431
432 while (strlen($derivedKey) < $length) {
433 $h = hash_hmac('sha256', $salt . pack('N', $block), $key, true);
434 $u = $h;
435
436 for ($i = 1; $i < 1000; $i++) {
437 $u = hash_hmac('sha256', $u, $key, true);
438 $h ^= $u;
439 }
440
441 $derivedKey .= $h;
442 $block++;
443 }
444
445 return substr($derivedKey, 0, $length);
446 }
447
448 /**
449 * Generate random bytes using best available method
450 */
451 private static function generateRandomBytes(int $length): string
452 {
453 // Try random_bytes first (PHP 7+)
454 if (function_exists('random_bytes')) {
455 try {
456 return random_bytes($length);
457 } catch (\Exception $e) {
458 // Fall through to alternatives
459 }
460 }
461
462 // Try openssl_random_pseudo_bytes
463 if (function_exists('openssl_random_pseudo_bytes')) {
464 $bytes = openssl_random_pseudo_bytes($length, $strong);
465 if ($strong && $bytes !== false) {
466 return $bytes;
467 }
468 }
469
470 // Last resort: mt_rand based (not ideal but works)
471 $bytes = '';
472 for ($i = 0; $i < $length; $i++) {
473 $bytes .= chr(mt_rand(0, 255));
474 }
475 return $bytes;
476 }
477
478 // =========================================================================
479 // Legacy Compatibility
480 // =========================================================================
481
482 /**
483 * Decrypt very old base64-only format (pre-OpenSSL era)
484 */
485 private static function decryptLegacyBase64(string $ciphertext, string $key): string
486 {
487 // Try to decode the old format: base64(base64(key) + base64(plaintext))
488 $decoded = base64_decode($ciphertext);
489 if ($decoded === false) return '';
490
491 $encodedKey = base64_encode($key);
492
493 // Check if the decoded data starts with the encoded key
494 if (strpos($decoded, $encodedKey) === 0) {
495 $innerCiphertext = substr($decoded, strlen($encodedKey));
496 $plaintext = base64_decode($innerCiphertext);
497 return $plaintext !== false ? $plaintext : '';
498 }
499
500 return '';
501 }
502
503 // =========================================================================
504 // Utility Methods
505 // =========================================================================
506
507 /**
508 * Generate a cryptographically secure encryption key
509 *
510 * @param int $length Key length in characters
511 * @return string Generated key
512 */
513 public static function encKey($length = 256): string
514 {
515 if (defined('WPDM_ENC_KEY')) {
516 return WPDM_ENC_KEY;
517 }
518
519 $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
520 $chars_len = strlen($chars);
521
522 // Use cryptographically secure random bytes if available
523 try {
524 $bytes = self::generateRandomBytes($length);
525 $key = '';
526 for ($i = 0; $i < $length; $i++) {
527 $key .= $chars[ord($bytes[$i]) % $chars_len];
528 }
529 return $key;
530 } catch (\Exception $e) {
531 // Fallback to wp_rand
532 $key = '';
533 for ($i = 0; $i < $length; $i++) {
534 $key .= $chars[wp_rand(0, $chars_len - 1)];
535 }
536 return $key;
537 }
538 }
539
540 /**
541 * Check if secure encryption is available (OpenSSL or Sodium)
542 *
543 * @return bool True if OpenSSL or Sodium is available
544 */
545 public static function isSecureMethodAvailable(): bool
546 {
547 return self::hasOpenSSL() || self::hasSodium();
548 }
549
550 /**
551 * Check if ANY encryption is available (including PHP fallback)
552 *
553 * @return bool Always true (PHP fallback always works)
554 */
555 public static function isAvailable(): bool
556 {
557 return true; // PHP fallback always works
558 }
559
560 /**
561 * Get the current encryption method being used
562 *
563 * @return string 'openssl', 'sodium', or 'php'
564 */
565 public static function getMethod(): string
566 {
567 if (self::hasOpenSSL()) {
568 return 'openssl';
569 }
570 if (self::hasSodium()) {
571 return 'sodium';
572 }
573 return 'php';
574 }
575
576 /**
577 * Get security level of current encryption method
578 *
579 * @return string 'high', 'medium', or 'basic'
580 */
581 public static function getSecurityLevel(): string
582 {
583 if (self::hasOpenSSL() || self::hasSodium()) {
584 return 'high';
585 }
586 return 'basic';
587 }
588 }
589