UNPKG

js-code-encryptor

Version:

A powerful JavaScript code encryptor with ID-based encryption/decryption

502 lines (421 loc) 14.4 kB
const lzString = require('lz-string'); const jsConfuser = require('js-confuser'); const CryptoJS = require('crypto-js'); class JSCodeEncryptor { constructor(options = {}) { this.options = { compression: true, obfuscation: true, unicodeEscape: true, base64Encoding: true, aesEncryption: true, stringSplitting: true, obfuscationOptions: { target: 'browser', preset: 'medium', compact: true, controlFlowFlattening: true, deadCode: true, stringConcealing: true, stringSplitting: true }, ...options }; } generateEncryptionId() { return 'enc_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); } createAESKey(encryptionId) { return CryptoJS.SHA256(encryptionId).toString(); } aesEncrypt(data, encryptionId) { if (!this.options.aesEncryption) return data; const key = this.createAESKey(encryptionId); const encrypted = CryptoJS.AES.encrypt(data, key); return encrypted.toString(); } aesDecrypt(encryptedData, encryptionId) { if (!this.options.aesEncryption) return encryptedData; try { const key = this.createAESKey(encryptionId); const decrypted = CryptoJS.AES.decrypt(encryptedData, key); return decrypted.toString(CryptoJS.enc.Utf8); } catch (error) { throw new Error('AES decryption failed - invalid encryption ID or corrupted data'); } } splitString(str, chunkSize = 10000) { if (!this.options.stringSplitting || str.length <= chunkSize) return [str]; const chunks = []; for (let i = 0; i < str.length; i += chunkSize) { chunks.push(str.substring(i, i + chunkSize)); } return chunks; } joinString(chunks) { return chunks.join(''); } compressWithChunking(code) { if (!this.options.compression) return code; try { const compressed = lzString.compressToBase64(code); if (compressed.length > 50000 && this.options.stringSplitting) { const chunks = this.splitString(compressed, 30000); return JSON.stringify({ chunked: true, chunks }); } return JSON.stringify({ chunked: false, data: compressed }); } catch (error) { console.warn('Compression failed, using original code:', error.message); return JSON.stringify({ chunked: false, data: code }); } } decompressWithChunking(compressedData) { if (!this.options.compression) return compressedData; try { const dataObj = JSON.parse(compressedData); if (dataObj.chunked && dataObj.chunks) { const joinedData = this.joinString(dataObj.chunks); return lzString.decompressFromBase64(joinedData); } else { return lzString.decompressFromBase64(dataObj.data); } } catch (error) { try { return lzString.decompressFromBase64(compressedData); } catch (e) { return compressedData; } } } async obfuscate(code) { if (!this.options.obfuscation) return code; try { if (code.length > 100000) { console.warn('Large code detected, using simplified obfuscation'); this.options.obfuscationOptions.preset = 'low'; } return await jsConfuser.obfuscate(code, { ...this.options.obfuscationOptions, seed: Math.floor(Math.random() * 1000000) }); } catch (error) { console.warn('Obfuscation failed, returning original code:', error.message); return code; } } toBase64(str) { if (!this.options.base64Encoding) return str; return Buffer.from(str).toString('base64'); } fromBase64(base64Str) { if (!this.options.base64Encoding) return base64Str; return Buffer.from(base64Str, 'base64').toString('utf8'); } toUnicodeEscape(str) { if (!this.options.unicodeEscape) return str; return str.split('').map(char => { const code = char.charCodeAt(0); if (code > 127) { return '\\u' + code.toString(16).padStart(4, '0'); } return char; }).join(''); } fromUnicodeEscape(escapedStr) { if (!this.options.unicodeEscape) return escapedStr; return escapedStr.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => { return String.fromCharCode(parseInt(hex, 16)); }); } async encrypt(code, customId = null) { const encryptionId = customId || this.generateEncryptionId(); let processedCode = code; console.log(`Encrypting with ID: ${encryptionId}`); try { if (this.options.obfuscation) { console.log('Applying obfuscation...'); processedCode = await this.obfuscate(processedCode); } if (this.options.compression) { console.log('Applying compression...'); processedCode = this.compressWithChunking(processedCode); } if (this.options.aesEncryption) { console.log('Applying AES encryption...'); processedCode = this.aesEncrypt(processedCode, encryptionId); } if (this.options.base64Encoding) { console.log('Applying Base64 encoding...'); processedCode = this.toBase64(processedCode); } if (this.options.unicodeEscape) { console.log('Applying Unicode escaping...'); processedCode = this.toUnicodeEscape(processedCode); } const finalOutput = { encryptedData: processedCode, encryptionId: encryptionId, timestamp: Date.now(), version: '2.0' }; return { encryptedCode: JSON.stringify(finalOutput, null, 2), encryptionId: encryptionId }; } catch (error) { throw new Error(`Encryption failed: ${error.message}`); } } async decrypt(encryptedCode, encryptionId = null) { try { let dataObj; try { dataObj = JSON.parse(encryptedCode); if (dataObj.encryptedData && dataObj.encryptionId) { encryptedCode = dataObj.encryptedData; encryptionId = encryptionId || dataObj.encryptionId; } } catch (e) { if (!encryptionId) { throw new Error('Encryption ID required for decryption'); } } if (!encryptionId) { throw new Error('No encryption ID found or provided'); } console.log(`Decrypting with ID: ${encryptionId}`); let processedCode = encryptedCode; if (this.options.unicodeEscape) { console.log('Reversing Unicode escaping...'); processedCode = this.fromUnicodeEscape(processedCode); } if (this.options.base64Encoding) { console.log('Reversing Base64 encoding...'); processedCode = this.fromBase64(processedCode); } if (this.options.aesEncryption) { console.log('Reversing AES encryption...'); processedCode = this.aesDecrypt(processedCode, encryptionId); } if (this.options.compression) { console.log('Reversing compression...'); processedCode = this.decompressWithChunking(processedCode); } return processedCode; } catch (error) { throw new Error(`Decryption failed: ${error.message}`); } } extractEncryptionId(encryptedCode) { try { const dataObj = JSON.parse(encryptedCode); if (dataObj.encryptionId) { return { id: dataObj.encryptionId, timestamp: dataObj.timestamp, version: dataObj.version }; } } catch (error) { const idPattern = /enc_\d+_[a-z0-9]+/g; const matches = encryptedCode.match(idPattern); if (matches && matches.length > 0) { return { id: matches[0], foundIn: 'raw code pattern' }; } } throw new Error('No encryption ID found in the file'); } async createSelfExtractingPayload(code, variableName = 'encryptedPayload') { const { encryptedCode, encryptionId } = await this.encrypt(code); const decoderScript = ` // Self-Extracting Encrypted // Encryption ID: ${encryptionId} (function(){ ${this.getDecoderFunctions()} var encryptedData = ${JSON.stringify(JSON.parse(encryptedCode))}; var decodedCode = ${variableName}_decode(encryptedData.encryptedData, "${encryptionId}"); try { eval(decodedCode); } catch(e) { console.error('Execution failed:', e); } })(); `; return { payload: decoderScript, encryptionId: encryptionId }; } getDecoderFunctions() { return ` function ${this.options.unicodeEscape ? ` function unescapeUnicode(str) { return str.replace(/\\\\u([0-9a-fA-F]{4})/g, function(_, hex) { return String.fromCharCode(parseInt(hex, 16)); }); } ` : ''} ${this.options.base64Encoding ? ` function decodeBase64(base64) { if (typeof atob === 'function') { return atob(base64); } else if (typeof Buffer === 'function') { return Buffer.from(base64, 'base64').toString('binary'); } else { var base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; var output = ''; var i = 0; base64 = base64.replace(/[^A-Za-z0-9+/=]/g, ''); while (i < base64.length) { var enc1 = base64Chars.indexOf(base64.charAt(i++)); var enc2 = base64Chars.indexOf(base64.charAt(i++)); var enc3 = base64Chars.indexOf(base64.charAt(i++)); var enc4 = base64Chars.indexOf(base64.charAt(i++)); var chr1 = (enc1 << 2) | (enc2 >> 4); var chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); var chr3 = ((enc3 & 3) << 6) | enc4; output += String.fromCharCode(chr1); if (enc3 !== 64) output += String.fromCharCode(chr2); if (enc4 !== 64) output += String.fromCharCode(chr3); } return output; } } ` : ''} ${this.options.aesEncryption ? ` function decryptAES(encryptedData, encryptionId) { function createKey(id) { function sha256(ascii) { function rightRotate(value, amount) { return (value>>>amount) | (value<<(32 - amount)); } var mathPow = Math.pow; var maxWord = mathPow(2, 32); var lengthProperty = 'length'; var i, j; var result = ''; var words = []; var asciiBitLength = ascii[lengthProperty]*8; var hash = [], k = []; var primeCounter = k[lengthProperty]; var isComposite = {}; for (var candidate = 2; primeCounter < 64; candidate++) { if (!isComposite[candidate]) { for (i = 0; i < 313; i += candidate) { isComposite[i] = candidate; } hash[primeCounter] = (mathPow(candidate, .5)*maxWord)|0; k[primeCounter++] = (mathPow(candidate, 1/3)*maxWord)|0; } } ascii += '\\x80'; while (ascii[lengthProperty]%64 - 56) ascii += '\\x00'; for (i = 0; i < ascii[lengthProperty]; i++) { j = ascii.charCodeAt(i); if (j>>8) return; words[i>>2] |= j << ((3 - i)%4)*8; } words[words[lengthProperty]] = ((asciiBitLength/maxWord)|0); words[words[lengthProperty]] = (asciiBitLength) for (j = 0; j < words[lengthProperty];) { var w = words.slice(j, j += 16); var oldHash = hash; hash = hash.slice(0, 8); for (i = 0; i < 64; i++) { var w15 = w[i-15], w2 = w[i-2]; var a = hash[0], e = hash[4]; var temp1 = hash[7] + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) + ((e&hash[5])^((~e)&hash[6])) + k[i] + (w[i] = (i < 16) ? w[i] : ( w[i-16] + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15>>>3)) + w[i-7] + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2>>>10)) )|0 ); var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) + ((a&hash[1])^(a&hash[2])^(hash[1]&hash[2])); hash = [(temp1 + temp2)|0].concat(hash); hash[4] = (hash[4] + temp1)|0; } for (i = 0; i < 8; i++) { hash[i] = (hash[i] + oldHash[i])|0; } } for (i = 0; i < 8; i++) { for (j = 3; j + 1; j--) { var b = (hash[i]>>(j*8))&255; result += ((b < 16) ? 0 : '') + b.toString(16); } } return result; } return sha256(id); } try { var key = createKey(encryptionId); var encryptedBytes = atob(encryptedData); var decrypted = ''; for (var i = 0; i < encryptedBytes.length; i++) { var keyIndex = i % key.length; var keyChar = key.charCodeAt(keyIndex); var encryptedChar = encryptedBytes.charCodeAt(i); decrypted += String.fromCharCode(encryptedChar ^ keyChar); } return decrypted; } catch(e) { throw new Error('AES decryption failed: ' + e.message); } } ` : ''} ${this.options.compression ? ` function decompressLZ(base64) { var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; var output = ""; var chr1, chr2, chr3; var enc1, enc2, enc3, enc4; var i = 0; base64 = base64.replace(/[^A-Za-z0-9+/=]/g, ""); while (i < base64.length) { enc1 = keyStr.indexOf(base64.charAt(i++)); enc2 = keyStr.indexOf(base64.charAt(i++)); enc3 = keyStr.indexOf(base64.charAt(i++)); enc4 = keyStr.indexOf(base64.charAt(i++)); chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; output += String.fromCharCode(chr1); if (enc3 != 64) output += String.fromCharCode(chr2); if (enc4 != 64) output += String.fromCharCode(chr3); } return output; } ` : ''} function decodePayload(encryptedData, encryptionId) { var data = encryptedData; ${this.options.unicodeEscape ? 'data = unescapeUnicode(data);' : ''} ${this.options.base64Encoding ? 'data = decodeBase64(data);' : ''} ${this.options.aesEncryption ? 'data = decryptAES(data, encryptionId);' : ''} ${this.options.compression ? ` try { var dataObj = JSON.parse(data); if (dataObj.chunked && dataObj.chunks) { data = decompressLZ(dataObj.chunks.join('')); } else { data = decompressLZ(dataObj.data); } } catch(e) { data = decompressLZ(data); } ` : ''} return data; } `; } } module.exports = JSCodeEncryptor;