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