# cryptx/4.2.1/js/cryptx.js

CryptX, version 4.2.1. 757 lines.

- Page: https://pluginprobe.com/plugins/cryptx/4.2.1/code/js/cryptx.js
- Raw: https://pluginprobe.com/plugins/cryptx/4.2.1/raw/js/cryptx.js
- Modified: 2026-09-11T17:18:18+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/cryptx/4.2.1/code/js/cryptx.js#L10-L20`.

```javascript
/**
 * Secure CryptX Library - Fixed for backward compatibility
 *
 * Everything below lives inside an IIFE. Without it, the top-level `const` and
 * `class` declarations -- CONFIG, ITERATIONS, KEY_LENGTH, SecureUtils,
 * SecureEncryption -- sit in the global lexical environment of the page, and a
 * second script declaring any of those names does not merely overwrite them:
 * it throws "Identifier has already been declared" and one of the two scripts
 * stops dead. With names this general that is a matter of time, and the failure
 * would look like CryptX being broken for no reason.
 *
 * What the outside is meant to reach is assigned to `window` at the bottom,
 * deliberately and by name.
 */
(function () {

// Configuration constants
// The ceiling PHP already enforces (SecureEncryption::MAX_ITERATIONS), repeated
// here so that no path can hand an absurd number to PBKDF2 and leave a
// visitor's browser tab grinding.
const MAX_ROUNDS = 1000000;

// parseInt, because wp_localize_script turns every value into a string on the
// way into the page -- so cryptxConfig.iterations arrives as "10000", and a
// string fails Number.isInteger. Without this the clamp below applied only to
// the value read from a link and never to this one, which is the opposite of
// what one would assume from reading it.
const ITERATIONS = Math.min(
	parseInt(window.cryptxConfig?.iterations, 10) || 100000, // fallback to old value
	MAX_ROUNDS
);
const KEY_LENGTH = window.cryptxConfig?.keyLength || 32;
const IV_LENGTH = window.cryptxConfig?.ivLength || 16;
const SALT_LENGTH = window.cryptxConfig?.saltLength || 16;
const CONFIG = {
	ALLOWED_PROTOCOLS: ['http:', 'https:', 'mailto:'],
	MAX_URL_LENGTH: 2048,
	ENCRYPTION_KEY_SIZE: 32,
	IV_SIZE: 16
};

/**
 * Utility functions for secure operations
 */
class SecureUtils {
	static getSecureRandomBytes(length) {
		if (typeof crypto === 'undefined' || !crypto.getRandomValues) {
			throw new Error('Secure random number generation not available');
		}
		return crypto.getRandomValues(new Uint8Array(length));
	}

	static arrayBufferToBase64(buffer) {
		const bytes = new Uint8Array(buffer);
		let binary = '';
		for (let i = 0; i < bytes.byteLength; i++) {
			binary += String.fromCharCode(bytes[i]);
		}
		return btoa(binary);
	}

	static base64ToArrayBuffer(base64) {
		const binary = atob(base64);
		const buffer = new ArrayBuffer(binary.length);
		const bytes = new Uint8Array(buffer);
		for (let i = 0; i < binary.length; i++) {
			bytes[i] = binary.charCodeAt(i);
		}
		return buffer;
	}

	static validateUrl(url) {
		if (typeof url !== 'string' || url.length === 0) {
			return null;
		}

		if (url.length > CONFIG.MAX_URL_LENGTH) {
			console.error('URL exceeds maximum length');
			return null;
		}

		try {
			const urlObj = new URL(url);

			if (!CONFIG.ALLOWED_PROTOCOLS.includes(urlObj.protocol)) {
				console.error('Protocol not allowed:', urlObj.protocol);
				return null;
			}

			if (urlObj.protocol === 'mailto:') {
				const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
				if (!emailRegex.test(urlObj.pathname)) {
					console.error('Invalid email format in mailto URL');
					return null;
				}
			}

			return url;
		} catch (error) {
			console.error('Invalid URL format:', error.message);
			return null;
		}
	}

	static escapeJavaScript(str) {
		if (typeof str !== 'string') {
			return '';
		}
		return str.replace(/\\/g, '\\\\')
			.replace(/'/g, "\\'")
			.replace(/"/g, '\\"')
			.replace(/\n/g, '\\n')
			.replace(/\r/g, '\\r')
			.replace(/\t/g, '\\t');
	}
}

/**
 * Legacy encryption class - Fixed to match original PHP algorithm
 */
class LegacyEncryption {
	/**
	 * Decrypts using the original CryptX algorithm (matches PHP version)
	 * @param {string} encryptedString
	 * @returns {string}
	 */
	static originalDecrypt(encryptedString) {
		if (typeof encryptedString !== 'string' || encryptedString.length === 0) {
			throw new Error('Invalid encrypted string');
		}

		// Constants from original algorithm
		const UPPER_LIMIT = 8364;
		const DEFAULT_VALUE = 128;

		let charCode = 0;
		let decryptedString = "mailto:";
		let encryptionKey = 0;

		try {
			for (let i = 0; i < encryptedString.length; i += 2) {
				if (i + 1 >= encryptedString.length) {
					break;
				}

				// Get the salt (encryption key) from current position
				encryptionKey = parseInt(encryptedString.charAt(i), 10);

				// Handle invalid salt values
				if (isNaN(encryptionKey)) {
					encryptionKey = 0;
				}

				// Get the character code from next position
				charCode = encryptedString.charCodeAt(i + 1);

				// Apply the same logic as original
				if (charCode >= UPPER_LIMIT) {
					charCode = DEFAULT_VALUE;
				}

				// Decrypt by subtracting the salt
				const decryptedCharCode = charCode - encryptionKey;

				// Validate the result
				if (decryptedCharCode < 0 || decryptedCharCode > 1114111) {
					throw new Error('Invalid character code during decryption');
				}

				decryptedString += String.fromCharCode(decryptedCharCode);
			}

			return decryptedString;
		} catch (error) {
			throw new Error('Original decryption failed: ' + error.message);
		}
	}

	/**
	 * Encrypts using the original CryptX algorithm (matches PHP version)
	 * @param {string} inputString
	 * @returns {string}
	 */
	static originalEncrypt(inputString) {
		if (typeof inputString !== 'string' || inputString.length === 0) {
			throw new Error('Invalid input string');
		}

		// Remove "mailto:" prefix if present for encryption
		const cleanInput = inputString.replace(/^mailto:/, '');
		let crypt = '';

		// ASCII values blacklist (from PHP constant)
		const ASCII_VALUES_BLACKLIST = ['32', '34', '39', '60', '62', '63', '92', '94', '96', '127'];

		try {
			for (let i = 0; i < cleanInput.length; i++) {
				let salt, asciiValue;
				let attempts = 0;
				const maxAttempts = 20; // Prevent infinite loops

				do {
					if (attempts >= maxAttempts) {
						// Fallback to a safe salt if we can't find a valid one
						salt = 1;
						asciiValue = cleanInput.charCodeAt(i) + salt;
						break;
					}

					// Generate random number between 0 and 3 (matching PHP rand(0,3))
					const randomValues = SecureUtils.getSecureRandomBytes(1);
					salt = randomValues[0] % 4;

					// Get ASCII value and add salt
					asciiValue = cleanInput.charCodeAt(i) + salt;

					// Check if value exceeds limit (matching PHP logic)
					if (asciiValue >= 8364) {
						asciiValue = 128;
					}

					attempts++;
				} while (ASCII_VALUES_BLACKLIST.includes(asciiValue.toString()) && attempts < maxAttempts);

				// Append salt and character to result
				crypt += salt.toString() + String.fromCharCode(asciiValue);
			}

			return crypt;
		} catch (error) {
			throw new Error('Original encryption failed: ' + error.message);
		}
	}
}

/**
 * Modern encryption class using Web Crypto API - PHP Compatible
 */
class SecureEncryption {
	/**
	 * @param {string} password
	 * @param {Uint8Array} salt
	 * @param {number} [iterations] What the link itself says it was made with.
	 *   Left out only by links written before 4.2.0, which then fall back to
	 *   the configured value -- the behaviour that made changing the setting
	 *   kill every link already delivered.
	 */
	static async deriveKey(password, salt, iterations) {
		// Clamped to the same ceiling PHP enforces. The value can only come
		// from the server today -- KSES lets neither class nor data-* through
		// for anyone without unfiltered_html, and anyone with it does not need
		// this route -- but a number that reaches PBKDF2 unchecked is worth one
		// line of arithmetic.
		const rounds = Number.isInteger(iterations) && iterations > 0
			? Math.min(iterations, MAX_ROUNDS)
			: ITERATIONS;
		const encoder = new TextEncoder();
		const keyMaterial = await crypto.subtle.importKey(
			'raw',
			encoder.encode(password),
			{ name: 'PBKDF2' },
			false,
			['deriveKey']
		);

		return crypto.subtle.deriveKey(
			{
				name: 'PBKDF2',
				salt: salt,
				iterations: rounds,
				hash: 'SHA-256'
			},
			keyMaterial,
			{ name: 'AES-GCM', length: KEY_LENGTH * 8},
			false,
			['encrypt', 'decrypt']
		);
	}

	static async encrypt(plaintext, password) {
		if (typeof plaintext !== 'string' || typeof password !== 'string') {
			throw new Error('Both plaintext and password must be strings');
		}

		const encoder = new TextEncoder();
		const salt = SecureUtils.getSecureRandomBytes(16);
		const iv = SecureUtils.getSecureRandomBytes(CONFIG.IV_SIZE);

		const key = await this.deriveKey(password, salt);

		const encrypted = await crypto.subtle.encrypt(
			{ name: 'AES-GCM', iv: iv },
			key,
			encoder.encode(plaintext)
		);

		// Match PHP format: salt(16) + iv(16) + encrypted_data + tag(16)
		const encryptedArray = new Uint8Array(encrypted);
		const encryptedData = encryptedArray.slice(0, -16); // Remove tag from encrypted data
		const tag = encryptedArray.slice(-16); // Get the tag

		const combined = new Uint8Array(salt.length + iv.length + encryptedData.length + tag.length);
		combined.set(salt, 0);
		combined.set(iv, salt.length);
		combined.set(encryptedData, salt.length + iv.length);
		combined.set(tag, salt.length + iv.length + encryptedData.length);

		return SecureUtils.arrayBufferToBase64(combined.buffer);
	}

	/**
	 * @param {string} encryptedData
	 * @param {string} password
	 * @param {number} [iterations] See deriveKey().
	 */
	static async decrypt(encryptedData, password, iterations) {
		if (typeof encryptedData !== 'string' || typeof password !== 'string') {
			throw new Error('Both encryptedData and password must be strings');
		}

		try {
			const combined = SecureUtils.base64ToArrayBuffer(encryptedData);
			const totalLength = combined.byteLength;

			// PHP format: salt(16) + iv(16) + encrypted_data + tag(16)
			const saltLength = 16;
			const ivLength = 16;
			const tagLength = 16;
			const encryptedDataLength = totalLength - saltLength - ivLength - tagLength;

			if (totalLength < saltLength + ivLength + tagLength) {
				throw new Error('Encrypted data too short');
			}

			const salt = combined.slice(0, saltLength);
			const iv = combined.slice(saltLength, saltLength + ivLength);
			const encryptedDataOnly = combined.slice(saltLength + ivLength, saltLength + ivLength + encryptedDataLength);
			const tag = combined.slice(-tagLength); // Last 16 bytes

			const key = await this.deriveKey(password, new Uint8Array(salt), iterations);

			// Reconstruct the encrypted data with tag for Web Crypto API
			const encryptedWithTag = new Uint8Array(encryptedDataOnly.byteLength + tag.byteLength);
			encryptedWithTag.set(new Uint8Array(encryptedDataOnly), 0);
			encryptedWithTag.set(new Uint8Array(tag), encryptedDataOnly.byteLength);

			const decrypted = await crypto.subtle.decrypt(
				{ name: 'AES-GCM', iv: new Uint8Array(iv) },
				key,
				encryptedWithTag
			);

			const decoder = new TextDecoder();
			return decoder.decode(decrypted);
		} catch (error) {
			throw new Error('Decryption failed: ' + error.message);
		}
	}
}

/**
 * Main CryptX functions with backward compatibility
 */

/**
 * Securely decrypts and validates a URL before navigation
 * @param {string} encryptedUrl
 * @param {string} password
 */
async function secureDecryptAndNavigate(encryptedUrl, password = 'default_key', iterations) {
	if (typeof encryptedUrl !== 'string' || encryptedUrl.length === 0) {
		console.error('Invalid encrypted URL provided');
		return;
	}

	try {
		let decryptedUrl;

		// Try modern decryption first, then fall back to original algorithm
		try {
			decryptedUrl = await SecureEncryption.decrypt(encryptedUrl, password, iterations);
		} catch (modernError) {
			console.warn('Modern decryption failed, trying original algorithm');
			decryptedUrl = LegacyEncryption.originalDecrypt(encryptedUrl);
		}

		const validatedUrl = SecureUtils.validateUrl(decryptedUrl);
		if (!validatedUrl) {
			console.error('Invalid or unsafe URL detected');
			return;
		}

		window.location.href = validatedUrl;

	} catch (error) {
		console.error('Error during URL decryption and navigation:', error.message);
	}
}

/**
 * Legacy function for backward compatibility - using original algorithm
 * @param {string} encryptedString
 * @returns {string|null}
 */
function DeCryptString(encryptedString) {
	try {
		return LegacyEncryption.originalDecrypt(encryptedString);
	} catch (error) {
		console.error('Legacy decryption failed:', error.message);
		return null;
	}
}

/**
 * Legacy function for backward compatibility - secured
 * @param {string} encryptedUrl
 */
function DeCryptX(encryptedUrl) {
	const decryptedUrl = DeCryptString(encryptedUrl);
	if (!decryptedUrl) {
		console.error('Failed to decrypt URL');
		return;
	}

	const validatedUrl = SecureUtils.validateUrl(decryptedUrl);
	if (!validatedUrl) {
		console.error('Invalid or unsafe URL detected');
		return;
	}

	window.location.href = validatedUrl;
}

/**
 * Generates encrypted email link with proper security
 * @param {string} emailAddress
 * @param {string} password
 * @returns {Promise<string>}
 */
async function generateSecureEmailLink(emailAddress, password = 'default_key') {
	if (typeof emailAddress !== 'string' || emailAddress.length === 0) {
		throw new Error('Valid email address required');
	}

	const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
	if (!emailRegex.test(emailAddress)) {
		throw new Error('Invalid email format');
	}

	const mailtoUrl = `mailto:${emailAddress}`;
	const encryptedData = await SecureEncryption.encrypt(mailtoUrl, password);
	const escapedData = SecureUtils.escapeJavaScript(encryptedData);

	// The iteration count goes in as well, for the same reason the PHP side
	// puts it in data-cxi: encrypt() used whatever ITERATIONS says right now,
	// and without recording that, a later change to the setting would leave
	// this link unopenable. Nothing in the plugin calls this function -- it is
	// here for anyone building links themselves -- which is exactly why it
	// should not be the one place that still breaks.
	return `javascript:secureDecryptAndNavigate('${escapedData}', '${SecureUtils.escapeJavaScript(password)}', ${ITERATIONS})`;
}

/**
 * Legacy function for backward compatibility - using original algorithm
 * @param {string} emailAddress
 * @returns {string}
 */
function generateDeCryptXHandler(emailAddress) {
	if (typeof emailAddress !== 'string' || emailAddress.length === 0) {
		console.error('Valid email address required');
		return 'javascript:void(0)';
	}

	try {
		const encrypted = LegacyEncryption.originalEncrypt(emailAddress);
		const escaped = SecureUtils.escapeJavaScript(encrypted);
		return `javascript:DeCryptX('${escaped}')`;
	} catch (error) {
		console.error('Error generating handler:', error.message);
		return 'javascript:void(0)';
	}
}

/**
 * Legacy function - matches original PHP generateHashFromString
 * @param {string} inputString
 * @returns {string}
 */
function generateHashFromString(inputString) {
	try {
		return LegacyEncryption.originalEncrypt(inputString);
	} catch (error) {
		console.error('Error generating hash:', error.message);
		return '';
	}
}

/**
 * CSP-safe link handling
 *
 * Markup produced by the PHP side (no javascript: URI, therefore no
 * 'unsafe-inline' needed in the Content-Security-Policy):
 *
 *   <a href="#" class="cryptx-link" data-cx="BASE64" data-cxk="PASSWORD" data-cxm="secure" data-cxi="10000">…</a>
 *   <a href="#" class="cryptx-link" data-cx="0i2p2h…" data-cxm="legacy">…</a>
 *
 * A single delegated listener on `document` covers links that are added later
 * (widgets, AJAX, block editor preview). The legacy javascript: entry points
 * above stay untouched for pages that were cached before this version.
 */

const CRYPTX_LINK_CLASS = 'cryptx-link';
const CRYPTX_ATTR_PAYLOAD = 'data-cx';
const CRYPTX_ATTR_KEY = 'data-cxk';
const CRYPTX_ATTR_MODE = 'data-cxm';
const CRYPTX_ATTR_ITERATIONS = 'data-cxi';
const CRYPTX_MAX_DELEGATION_DEPTH = 50;

/**
 * True only when the Web Crypto API is usable (secure context, modern browser)
 * @returns {boolean}
 */
function cryptxHasSubtleCrypto() {
	return typeof crypto !== 'undefined' &&
		!!crypto &&
		!!crypto.subtle &&
		typeof crypto.subtle.importKey === 'function';
}

/**
 * @param {*} element
 * @returns {boolean}
 */
function isCryptxLink(element) {
	if (!element || typeof element.getAttribute !== 'function') {
		return false;
	}

	if (element.classList && typeof element.classList.contains === 'function') {
		return element.classList.contains(CRYPTX_LINK_CLASS);
	}

	if (typeof element.className === 'string') {
		return (' ' + element.className + ' ').indexOf(' ' + CRYPTX_LINK_CLASS + ' ') !== -1;
	}

	return false;
}

/**
 * Walks up from the event target to the CryptX link (clicks may land on a
 * child element, e.g. an <img> or <span> inside the anchor).
 * @param {*} startNode
 * @returns {*|null}
 */
function findCryptxLink(startNode) {
	let node = startNode;
	let depth = 0;

	while (node && depth < CRYPTX_MAX_DELEGATION_DEPTH) {
		if (isCryptxLink(node)) {
			return node;
		}
		node = node.parentElement || node.parentNode || null;
		depth++;
	}

	return null;
}

/**
 * Decrypts a data-cx payload according to data-cxm.
 * Missing mode behaves like "secure" with a fallback to "legacy",
 * exactly like secureDecryptAndNavigate() does.
 * @param {string} payload
 * @param {string|null} password
 * @param {string|null} mode
 * @param {string|number|null} [iterations] What data-cxi says. Links written
 *   before 4.2.0 do not carry it and fall back to the configured value -- which
 *   is why changing that value used to break every link already delivered.
 * @returns {Promise<string>}
 */
async function cryptxDecryptPayload(payload, password, mode, iterations) {
	if (typeof payload !== 'string' || payload.length === 0) {
		throw new Error('Missing or invalid data-cx payload');
	}

	const normalizedMode = typeof mode === 'string' ? mode.trim().toLowerCase() : '';

	if (normalizedMode === 'legacy') {
		return LegacyEncryption.originalDecrypt(payload);
	}

	if (!cryptxHasSubtleCrypto()) {
		if (normalizedMode === 'secure') {
			throw new Error('Web Crypto API (crypto.subtle) is not available in this context');
		}
		return LegacyEncryption.originalDecrypt(payload);
	}

	const key = typeof password === 'string' && password.length > 0 ? password : 'default_key';
	const rounds = parseInt(iterations, 10);

	try {
		return await SecureEncryption.decrypt(payload, key, rounds);
	} catch (secureError) {
		if (normalizedMode === 'secure') {
			throw secureError;
		}
		console.warn('CryptX: modern decryption failed, trying original algorithm');
		return LegacyEncryption.originalDecrypt(payload);
	}
}

/**
 * Delegated click handler. Never navigates without SecureUtils.validateUrl().
 * @param {Object} event
 * @returns {Promise<void>}
 */
async function handleCryptxLinkClick(event) {
	if (!event) {
		return;
	}

	const link = findCryptxLink(event.target);
	if (!link) {
		return;
	}

	// Suppress the "#" jump before anything asynchronous happens.
	if (typeof event.preventDefault === 'function') {
		event.preventDefault();
	}

	const payload = link.getAttribute(CRYPTX_ATTR_PAYLOAD);
	const password = link.getAttribute(CRYPTX_ATTR_KEY);
	const mode = link.getAttribute(CRYPTX_ATTR_MODE);
	const iterations = link.getAttribute(CRYPTX_ATTR_ITERATIONS);

	try {
		const decryptedUrl = await cryptxDecryptPayload(payload, password, mode, iterations);

		const validatedUrl = SecureUtils.validateUrl(decryptedUrl);
		if (!validatedUrl) {
			console.error('CryptX: invalid or unsafe URL detected, navigation aborted');
			return;
		}

		window.location.href = validatedUrl;
	} catch (error) {
		console.error('CryptX: could not resolve link target:', error && error.message ? error.message : error);
	}
}

/**
 * Attaches the single delegated listener. Idempotent.
 *
 * The "attached" flag lives on the document itself (an expando property),
 * not in a closure variable. Two things went wrong with a closure variable:
 * first, any plugin or loader that runs cryptx.js a second time on the same
 * document -- @swup/scripts-plugin does, Turbo/Hotwire does, any AJAX loader
 * that brings footer markup along does -- gets a fresh closure and therefore
 * a second `click` listener on `document`; measured: one click fired
 * `mailto:` twice. Second, initCryptxLinkHandler(otherDocument) is exported
 * for exactly this use but never worked, because the closure flag was
 * already true from the main document and nothing was attached to the one
 * passed in. Marking the document, not the module, fixes both with the same
 * few lines.
 * @param {Object} [targetDocument]
 * @returns {boolean} true when the listener was attached by this call
 */
function initCryptxLinkHandler(targetDocument) {
	const doc = targetDocument || (typeof document !== 'undefined' ? document : null);

	if (!doc || typeof doc.addEventListener !== 'function') {
		return false;
	}

	if (doc.__cryptxLinkHandlerAttached) {
		return false;
	}

	doc.addEventListener('click', handleCryptxLinkClick, false);
	doc.__cryptxLinkHandlerAttached = true;

	return true;
}

// Attach immediately - delegation on `document` needs no finished DOM, so this
// works for a <head> include as well as for a footer include, where
// DOMContentLoaded may already have fired and would never come again.
if (typeof document !== 'undefined' && typeof document.addEventListener === 'function') {
	initCryptxLinkHandler(document);

	if (document.readyState === 'loading') {
		// Safety net for exotic environments that replace `document` while parsing.
		document.addEventListener('DOMContentLoaded', function () {
			initCryptxLinkHandler(document);
		});
	}
}

// Keep everything reachable by name, also after minification.
//
// The three the plugin itself depends on are secureDecryptAndNavigate and
// DeCryptX, which appear in the generated "javascript:" links, and
// generateDeCryptXHandler, which the help tab documents for use in a theme.
// The rest is kept because it has been exported for years; new code should use
// the window.CryptX namespace, and the bare names will go with the next major
// release, together with the deprecated encryptx().
if (typeof window !== 'undefined') {
	window.CryptX = {
		secureDecryptAndNavigate,
		DeCryptX,
		DeCryptString,
		generateSecureEmailLink,
		generateDeCryptXHandler,
		generateHashFromString,
		handleCryptxLinkClick,
		initCryptxLinkHandler,
		SecureUtils,
		LegacyEncryption,
		SecureEncryption
	};

	window.secureDecryptAndNavigate = secureDecryptAndNavigate;
	window.DeCryptX = DeCryptX;
	window.DeCryptString = DeCryptString;
	window.generateSecureEmailLink = generateSecureEmailLink;
	window.generateDeCryptXHandler = generateDeCryptXHandler;
	window.generateHashFromString = generateHashFromString;
	window.handleCryptxLinkClick = handleCryptxLinkClick;
	window.initCryptxLinkHandler = initCryptxLinkHandler;
	window.SecureUtils = SecureUtils;
	window.LegacyEncryption = LegacyEncryption;
	window.SecureEncryption = SecureEncryption;
}

// Export functions for module usage
if (typeof module !== 'undefined' && module.exports) {
	module.exports = {
		secureDecryptAndNavigate,
		generateSecureEmailLink,
		DeCryptX,
		DeCryptString,
		generateDeCryptXHandler,
		generateHashFromString,
		handleCryptxLinkClick,
		initCryptxLinkHandler,
		cryptxDecryptPayload,
		SecureEncryption,
		LegacyEncryption,
		SecureUtils
	};
}
})();

```
