PluginProbe
CryptX / 4.0.1
CryptX v4.0.1
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
← All changes | js/cryptx.js +451 -16 2.24.0.1 View file →
@@ -1,20 +1,455 @@
1 -function DeCryptString( s )
2 -{
3 - var n = 0;
4 - var r = "mailto:";
5 - for( var i = 0; i < s.length; i++)
6 - {
7 - n = s.charCodeAt( i );
8 - if( n >= 8364 )
9 - {
10 - n = 128;
11 - }
12 - r += String.fromCharCode( n - 1 );
1 +/**
2 + * Secure CryptX Library - Fixed for backward compatibility
3 + */
4 +
5 +// Configuration constants
6 +const CONFIG = {
7 + ALLOWED_PROTOCOLS: ['http:', 'https:', 'mailto:'],
8 + MAX_URL_LENGTH: 2048,
9 + ENCRYPTION_KEY_SIZE: 32,
10 + IV_SIZE: 16
11 +};
12 +
13 +/**
14 + * Utility functions for secure operations
15 + */
16 +class SecureUtils {
17 + static getSecureRandomBytes(length) {
18 + if (typeof crypto === 'undefined' || !crypto.getRandomValues) {
19 + throw new Error('Secure random number generation not available');
20 + }
21 + return crypto.getRandomValues(new Uint8Array(length));
13 22 }
14 - return r;
23 +
24 + static arrayBufferToBase64(buffer) {
25 + const bytes = new Uint8Array(buffer);
26 + let binary = '';
27 + for (let i = 0; i < bytes.byteLength; i++) {
28 + binary += String.fromCharCode(bytes[i]);
29 + }
30 + return btoa(binary);
31 + }
32 +
33 + static base64ToArrayBuffer(base64) {
34 + const binary = atob(base64);
35 + const buffer = new ArrayBuffer(binary.length);
36 + const bytes = new Uint8Array(buffer);
37 + for (let i = 0; i < binary.length; i++) {
38 + bytes[i] = binary.charCodeAt(i);
39 + }
40 + return buffer;
41 + }
42 +
43 + static validateUrl(url) {
44 + if (typeof url !== 'string' || url.length === 0) {
45 + return null;
46 + }
47 +
48 + if (url.length > CONFIG.MAX_URL_LENGTH) {
49 + console.error('URL exceeds maximum length');
50 + return null;
51 + }
52 +
53 + try {
54 + const urlObj = new URL(url);
55 +
56 + if (!CONFIG.ALLOWED_PROTOCOLS.includes(urlObj.protocol)) {
57 + console.error('Protocol not allowed:', urlObj.protocol);
58 + return null;
59 + }
60 +
61 + if (urlObj.protocol === 'mailto:') {
62 + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
63 + if (!emailRegex.test(urlObj.pathname)) {
64 + console.error('Invalid email format in mailto URL');
65 + return null;
66 + }
67 + }
68 +
69 + return url;
70 + } catch (error) {
71 + console.error('Invalid URL format:', error.message);
72 + return null;
73 + }
74 + }
75 +
76 + static escapeJavaScript(str) {
77 + if (typeof str !== 'string') {
78 + return '';
79 + }
80 + return str.replace(/\\/g, '\\\\')
81 + .replace(/'/g, "\\'")
82 + .replace(/"/g, '\\"')
83 + .replace(/\n/g, '\\n')
84 + .replace(/\r/g, '\\r')
85 + .replace(/\t/g, '\\t');
86 + }
15 87 }
16 88
17 -function DeCryptX( s )
18 -{
19 - location.href=DeCryptString( s );
89 +/**
90 + * Legacy encryption class - Fixed to match original PHP algorithm
91 + */
92 +class LegacyEncryption {
93 + /**
94 + * Decrypts using the original CryptX algorithm (matches PHP version)
95 + * @param {string} encryptedString
96 + * @returns {string}
97 + */
98 + static originalDecrypt(encryptedString) {
99 + if (typeof encryptedString !== 'string' || encryptedString.length === 0) {
100 + throw new Error('Invalid encrypted string');
101 + }
102 +
103 + // Constants from original algorithm
104 + const UPPER_LIMIT = 8364;
105 + const DEFAULT_VALUE = 128;
106 +
107 + let charCode = 0;
108 + let decryptedString = "mailto:";
109 + let encryptionKey = 0;
110 +
111 + try {
112 + for (let i = 0; i < encryptedString.length; i += 2) {
113 + if (i + 1 >= encryptedString.length) {
114 + break;
115 + }
116 +
117 + // Get the salt (encryption key) from current position
118 + encryptionKey = parseInt(encryptedString.charAt(i), 10);
119 +
120 + // Handle invalid salt values
121 + if (isNaN(encryptionKey)) {
122 + encryptionKey = 0;
123 + }
124 +
125 + // Get the character code from next position
126 + charCode = encryptedString.charCodeAt(i + 1);
127 +
128 + // Apply the same logic as original
129 + if (charCode >= UPPER_LIMIT) {
130 + charCode = DEFAULT_VALUE;
131 + }
132 +
133 + // Decrypt by subtracting the salt
134 + const decryptedCharCode = charCode - encryptionKey;
135 +
136 + // Validate the result
137 + if (decryptedCharCode < 0 || decryptedCharCode > 1114111) {
138 + throw new Error('Invalid character code during decryption');
139 + }
140 +
141 + decryptedString += String.fromCharCode(decryptedCharCode);
142 + }
143 +
144 + return decryptedString;
145 + } catch (error) {
146 + throw new Error('Original decryption failed: ' + error.message);
147 + }
148 + }
149 +
150 + /**
151 + * Encrypts using the original CryptX algorithm (matches PHP version)
152 + * @param {string} inputString
153 + * @returns {string}
154 + */
155 + static originalEncrypt(inputString) {
156 + if (typeof inputString !== 'string' || inputString.length === 0) {
157 + throw new Error('Invalid input string');
158 + }
159 +
160 + // Remove "mailto:" prefix if present for encryption
161 + const cleanInput = inputString.replace(/^mailto:/, '');
162 + let crypt = '';
163 +
164 + // ASCII values blacklist (from PHP constant)
165 + const ASCII_VALUES_BLACKLIST = ['32', '34', '39', '60', '62', '63', '92', '94', '96', '127'];
166 +
167 + try {
168 + for (let i = 0; i < cleanInput.length; i++) {
169 + let salt, asciiValue;
170 + let attempts = 0;
171 + const maxAttempts = 20; // Prevent infinite loops
172 +
173 + do {
174 + if (attempts >= maxAttempts) {
175 + // Fallback to a safe salt if we can't find a valid one
176 + salt = 1;
177 + asciiValue = cleanInput.charCodeAt(i) + salt;
178 + break;
179 + }
180 +
181 + // Generate random number between 0 and 3 (matching PHP rand(0,3))
182 + const randomValues = SecureUtils.getSecureRandomBytes(1);
183 + salt = randomValues[0] % 4;
184 +
185 + // Get ASCII value and add salt
186 + asciiValue = cleanInput.charCodeAt(i) + salt;
187 +
188 + // Check if value exceeds limit (matching PHP logic)
189 + if (asciiValue >= 8364) {
190 + asciiValue = 128;
191 + }
192 +
193 + attempts++;
194 + } while (ASCII_VALUES_BLACKLIST.includes(asciiValue.toString()) && attempts < maxAttempts);
195 +
196 + // Append salt and character to result
197 + crypt += salt.toString() + String.fromCharCode(asciiValue);
198 + }
199 +
200 + return crypt;
201 + } catch (error) {
202 + throw new Error('Original encryption failed: ' + error.message);
203 + }
204 + }
205 +}
206 +
207 +/**
208 + * Modern encryption class using Web Crypto API - PHP Compatible
209 + */
210 +class SecureEncryption {
211 + static async deriveKey(password, salt) {
212 + const encoder = new TextEncoder();
213 + const keyMaterial = await crypto.subtle.importKey(
214 + 'raw',
215 + encoder.encode(password),
216 + { name: 'PBKDF2' },
217 + false,
218 + ['deriveKey']
219 + );
220 +
221 + return crypto.subtle.deriveKey(
222 + {
223 + name: 'PBKDF2',
224 + salt: salt,
225 + iterations: 100000,
226 + hash: 'SHA-256'
227 + },
228 + keyMaterial,
229 + { name: 'AES-GCM', length: 256 },
230 + false,
231 + ['encrypt', 'decrypt']
232 + );
233 + }
234 +
235 + static async encrypt(plaintext, password) {
236 + if (typeof plaintext !== 'string' || typeof password !== 'string') {
237 + throw new Error('Both plaintext and password must be strings');
238 + }
239 +
240 + const encoder = new TextEncoder();
241 + const salt = SecureUtils.getSecureRandomBytes(16);
242 + const iv = SecureUtils.getSecureRandomBytes(CONFIG.IV_SIZE);
243 +
244 + const key = await this.deriveKey(password, salt);
245 +
246 + const encrypted = await crypto.subtle.encrypt(
247 + { name: 'AES-GCM', iv: iv },
248 + key,
249 + encoder.encode(plaintext)
250 + );
251 +
252 + // Match PHP format: salt(16) + iv(16) + encrypted_data + tag(16)
253 + const encryptedArray = new Uint8Array(encrypted);
254 + const encryptedData = encryptedArray.slice(0, -16); // Remove tag from encrypted data
255 + const tag = encryptedArray.slice(-16); // Get the tag
256 +
257 + const combined = new Uint8Array(salt.length + iv.length + encryptedData.length + tag.length);
258 + combined.set(salt, 0);
259 + combined.set(iv, salt.length);
260 + combined.set(encryptedData, salt.length + iv.length);
261 + combined.set(tag, salt.length + iv.length + encryptedData.length);
262 +
263 + return SecureUtils.arrayBufferToBase64(combined.buffer);
264 + }
265 +
266 + static async decrypt(encryptedData, password) {
267 + if (typeof encryptedData !== 'string' || typeof password !== 'string') {
268 + throw new Error('Both encryptedData and password must be strings');
269 + }
270 +
271 + try {
272 + const combined = SecureUtils.base64ToArrayBuffer(encryptedData);
273 + const totalLength = combined.byteLength;
274 +
275 + // PHP format: salt(16) + iv(16) + encrypted_data + tag(16)
276 + const saltLength = 16;
277 + const ivLength = 16;
278 + const tagLength = 16;
279 + const encryptedDataLength = totalLength - saltLength - ivLength - tagLength;
280 +
281 + if (totalLength < saltLength + ivLength + tagLength) {
282 + throw new Error('Encrypted data too short');
283 + }
284 +
285 + const salt = combined.slice(0, saltLength);
286 + const iv = combined.slice(saltLength, saltLength + ivLength);
287 + const encryptedDataOnly = combined.slice(saltLength + ivLength, saltLength + ivLength + encryptedDataLength);
288 + const tag = combined.slice(-tagLength); // Last 16 bytes
289 +
290 + const key = await this.deriveKey(password, new Uint8Array(salt));
291 +
292 + // Reconstruct the encrypted data with tag for Web Crypto API
293 + const encryptedWithTag = new Uint8Array(encryptedDataOnly.byteLength + tag.byteLength);
294 + encryptedWithTag.set(new Uint8Array(encryptedDataOnly), 0);
295 + encryptedWithTag.set(new Uint8Array(tag), encryptedDataOnly.byteLength);
296 +
297 + const decrypted = await crypto.subtle.decrypt(
298 + { name: 'AES-GCM', iv: new Uint8Array(iv) },
299 + key,
300 + encryptedWithTag
301 + );
302 +
303 + const decoder = new TextDecoder();
304 + return decoder.decode(decrypted);
305 + } catch (error) {
306 + throw new Error('Decryption failed: ' + error.message);
307 + }
308 + }
309 +}
310 +
311 +/**
312 + * Main CryptX functions with backward compatibility
313 + */
314 +
315 +/**
316 + * Securely decrypts and validates a URL before navigation
317 + * @param {string} encryptedUrl
318 + * @param {string} password
319 + */
320 +async function secureDecryptAndNavigate(encryptedUrl, password = 'default_key') {
321 + if (typeof encryptedUrl !== 'string' || encryptedUrl.length === 0) {
322 + console.error('Invalid encrypted URL provided');
323 + return;
324 + }
325 +
326 + try {
327 + let decryptedUrl;
328 +
329 + // Try modern decryption first, then fall back to original algorithm
330 + try {
331 + decryptedUrl = await SecureEncryption.decrypt(encryptedUrl, password);
332 + } catch (modernError) {
333 + console.warn('Modern decryption failed, trying original algorithm');
334 + decryptedUrl = LegacyEncryption.originalDecrypt(encryptedUrl);
335 + }
336 +
337 + const validatedUrl = SecureUtils.validateUrl(decryptedUrl);
338 + if (!validatedUrl) {
339 + console.error('Invalid or unsafe URL detected');
340 + return;
341 + }
342 +
343 + window.location.href = validatedUrl;
344 +
345 + } catch (error) {
346 + console.error('Error during URL decryption and navigation:', error.message);
347 + }
348 +}
349 +
350 +/**
351 + * Legacy function for backward compatibility - using original algorithm
352 + * @param {string} encryptedString
353 + * @returns {string|null}
354 + */
355 +function DeCryptString(encryptedString) {
356 + try {
357 + return LegacyEncryption.originalDecrypt(encryptedString);
358 + } catch (error) {
359 + console.error('Legacy decryption failed:', error.message);
360 + return null;
361 + }
362 +}
363 +
364 +/**
365 + * Legacy function for backward compatibility - secured
366 + * @param {string} encryptedUrl
367 + */
368 +function DeCryptX(encryptedUrl) {
369 + const decryptedUrl = DeCryptString(encryptedUrl);
370 + if (!decryptedUrl) {
371 + console.error('Failed to decrypt URL');
372 + return;
373 + }
374 +
375 + const validatedUrl = SecureUtils.validateUrl(decryptedUrl);
376 + if (!validatedUrl) {
377 + console.error('Invalid or unsafe URL detected');
378 + return;
379 + }
380 +
381 + window.location.href = validatedUrl;
382 +}
383 +
384 +/**
385 + * Generates encrypted email link with proper security
386 + * @param {string} emailAddress
387 + * @param {string} password
388 + * @returns {Promise<string>}
389 + */
390 +async function generateSecureEmailLink(emailAddress, password = 'default_key') {
391 + if (typeof emailAddress !== 'string' || emailAddress.length === 0) {
392 + throw new Error('Valid email address required');
393 + }
394 +
395 + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
396 + if (!emailRegex.test(emailAddress)) {
397 + throw new Error('Invalid email format');
398 + }
399 +
400 + const mailtoUrl = `mailto:${emailAddress}`;
401 + const encryptedData = await SecureEncryption.encrypt(mailtoUrl, password);
402 + const escapedData = SecureUtils.escapeJavaScript(encryptedData);
403 +
404 + return `javascript:secureDecryptAndNavigate('${escapedData}', '${SecureUtils.escapeJavaScript(password)}')`;
405 +}
406 +
407 +/**
408 + * Legacy function for backward compatibility - using original algorithm
409 + * @param {string} emailAddress
410 + * @returns {string}
411 + */
412 +function generateDeCryptXHandler(emailAddress) {
413 + if (typeof emailAddress !== 'string' || emailAddress.length === 0) {
414 + console.error('Valid email address required');
415 + return 'javascript:void(0)';
416 + }
417 +
418 + try {
419 + const encrypted = LegacyEncryption.originalEncrypt(emailAddress);
420 + const escaped = SecureUtils.escapeJavaScript(encrypted);
421 + return `javascript:DeCryptX('${escaped}')`;
422 + } catch (error) {
423 + console.error('Error generating handler:', error.message);
424 + return 'javascript:void(0)';
425 + }
426 +}
427 +
428 +/**
429 + * Legacy function - matches original PHP generateHashFromString
430 + * @param {string} inputString
431 + * @returns {string}
432 + */
433 +function generateHashFromString(inputString) {
434 + try {
435 + return LegacyEncryption.originalEncrypt(inputString);
436 + } catch (error) {
437 + console.error('Error generating hash:', error.message);
438 + return '';
439 + }
440 +}
441 +
442 +// Export functions for module usage
443 +if (typeof module !== 'undefined' && module.exports) {
444 + module.exports = {
445 + secureDecryptAndNavigate,
446 + generateSecureEmailLink,
447 + DeCryptX,
448 + DeCryptString,
449 + generateDeCryptXHandler,
450 + generateHashFromString,
451 + SecureEncryption,
452 + LegacyEncryption,
453 + SecureUtils
454 + };
20 455 }