js-code-encryptor
Version:
A powerful JavaScript code encryptor with ID-based encryption/decryption
502 lines (421 loc) • 14.4 kB
JavaScript
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;