PluginProbe
CryptX / 4.0.2
CryptX v4.0.2
4.2.0 4.1.1 trunk 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.9 2.0 2.1 2.2 2.3 2.3.1 2.3.2 2.3.3 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 All 92 releases
cryptx / classes / SecureEncryption.php

SecureEncryption.php in CryptX 4.0.2, at classes/SecureEncryption.php

209 lines 6.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace CryptX;
4
5 /**
6 * Secure encryption class using modern cryptographic standards
7 * Compatible with JavaScript Web Crypto API
8 */
9 class SecureEncryption
10 {
11 private const CIPHER = 'aes-256-gcm';
12 private const KEY_LENGTH = 32; // 256 bits
13 private const IV_LENGTH = 16; // 128 bits
14 private const SALT_LENGTH = 16; // 128 bits
15 private const ITERATIONS = 100000; // PBKDF2 iterations
16
17 /**
18 * Derives a key from password using PBKDF2 - compatible with JavaScript
19 *
20 * @param string $password
21 * @param string $salt
22 * @return string
23 * @throws \Exception
24 */
25 private static function deriveKey(string $password, string $salt): string
26 {
27 if (!function_exists('hash_pbkdf2')) {
28 throw new \Exception('PBKDF2 not available');
29 }
30
31 return hash_pbkdf2('sha256', $password, $salt, self::ITERATIONS, self::KEY_LENGTH, true);
32 }
33
34 /**
35 * Encrypts plaintext using AES-256-GCM - JavaScript compatible format
36 *
37 * @param string $plaintext
38 * @param string $password
39 * @return string Base64 encoded encrypted data
40 * @throws \Exception
41 */
42 public static function encrypt(string $plaintext, string $password): string
43 {
44 if (!function_exists('openssl_encrypt')) {
45 throw new \Exception('OpenSSL extension not available');
46 }
47
48 if (!in_array(self::CIPHER, openssl_get_cipher_methods())) {
49 throw new \Exception('AES-256-GCM cipher not available');
50 }
51
52 // Generate random salt and IV
53 $salt = random_bytes(self::SALT_LENGTH);
54 $iv = random_bytes(self::IV_LENGTH);
55
56 // Derive key from password
57 $key = self::deriveKey($password, $salt);
58
59 // Encrypt data
60 $tag = '';
61 $encrypted = openssl_encrypt(
62 $plaintext,
63 self::CIPHER,
64 $key,
65 OPENSSL_RAW_DATA,
66 $iv,
67 $tag
68 );
69
70 if ($encrypted === false) {
71 throw new \Exception('Encryption failed');
72 }
73
74 // Format: salt(16) + iv(16) + encrypted_data + tag(16)
75 // This matches the JavaScript format expectation
76 $combined = $salt . $iv . $encrypted . $tag;
77
78 return base64_encode($combined);
79 }
80
81 /**
82 * Decrypts encrypted data using AES-256-GCM - JavaScript compatible
83 *
84 * @param string $encryptedData Base64 encoded encrypted data
85 * @param string $password
86 * @return string Decrypted plaintext
87 * @throws \Exception
88 */
89 public static function decrypt(string $encryptedData, string $password): string
90 {
91 if (!function_exists('openssl_decrypt')) {
92 throw new \Exception('OpenSSL extension not available');
93 }
94
95 try {
96 $combined = base64_decode($encryptedData, true);
97 if ($combined === false) {
98 throw new \Exception('Invalid base64 encoding');
99 }
100
101 $totalLength = strlen($combined);
102 $expectedMinLength = self::SALT_LENGTH + self::IV_LENGTH + 16; // +16 for tag
103
104 if ($totalLength < $expectedMinLength) {
105 throw new \Exception('Encrypted data too short');
106 }
107
108 // Extract components: salt(16) + iv(16) + encrypted_data + tag(16)
109 $salt = substr($combined, 0, self::SALT_LENGTH);
110 $iv = substr($combined, self::SALT_LENGTH, self::IV_LENGTH);
111 $encryptedDataLength = $totalLength - self::SALT_LENGTH - self::IV_LENGTH - 16;
112 $encrypted = substr($combined, self::SALT_LENGTH + self::IV_LENGTH, $encryptedDataLength);
113 $tag = substr($combined, -16); // Last 16 bytes
114
115 if (strlen($salt) !== self::SALT_LENGTH ||
116 strlen($iv) !== self::IV_LENGTH ||
117 strlen($tag) !== 16) {
118 throw new \Exception('Invalid encrypted data format');
119 }
120
121 // Derive key from password
122 $key = self::deriveKey($password, $salt);
123
124 // Decrypt data
125 $decrypted = openssl_decrypt(
126 $encrypted,
127 self::CIPHER,
128 $key,
129 OPENSSL_RAW_DATA,
130 $iv,
131 $tag
132 );
133
134 if ($decrypted === false) {
135 throw new \Exception('Decryption failed or data corrupted');
136 }
137
138 return $decrypted;
139 } catch (\Throwable $e) {
140 throw new \Exception('Decryption failed: ' . $e->getMessage());
141 }
142 }
143
144 /**
145 * Test encryption/decryption with debug output
146 *
147 * @param string $plaintext
148 * @param string $password
149 * @return array Debug information
150 */
151 public static function debugEncryption(string $plaintext, string $password): array
152 {
153 try {
154 $encrypted = self::encrypt($plaintext, $password);
155 $decrypted = self::decrypt($encrypted, $password);
156
157 return [
158 'success' => true,
159 'plaintext' => $plaintext,
160 'encrypted' => $encrypted,
161 'decrypted' => $decrypted,
162 'match' => ($plaintext === $decrypted),
163 'encrypted_length' => strlen($encrypted),
164 'binary_length' => strlen(base64_decode($encrypted))
165 ];
166 } catch (\Exception $e) {
167 return [
168 'success' => false,
169 'error' => $e->getMessage(),
170 'plaintext' => $plaintext
171 ];
172 }
173 }
174
175 /**
176 * Validates URL for security
177 *
178 * @param string $url
179 * @return bool
180 */
181 public static function validateUrl(string $url): bool
182 {
183 $allowedProtocols = ['http', 'https', 'mailto'];
184 $maxLength = 2048;
185
186 if (strlen($url) > $maxLength) {
187 return false;
188 }
189
190 $parsedUrl = parse_url($url);
191 if (!$parsedUrl || !isset($parsedUrl['scheme'])) {
192 return false;
193 }
194
195 if (!in_array($parsedUrl['scheme'], $allowedProtocols)) {
196 return false;
197 }
198
199 // Additional validation for mailto URLs
200 if ($parsedUrl['scheme'] === 'mailto') {
201 $email = $parsedUrl['path'] ?? '';
202 if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
203 return false;
204 }
205 }
206
207 return true;
208 }
209 }