'use strict';
|
|
const path = require('path');
|
const fs = require('fs');
|
const fsPro = require('fs-extra');
|
const is = require('is-type-of');
|
const bytenode = require('bytenode');
|
const crypto = require('crypto');
|
const JavaScriptObfuscator = require('javascript-obfuscator');
|
const globby = require('globby');
|
const chalk = require('chalk');
|
const Utils = require('../lib/utils');
|
|
class Encrypt {
|
constructor(options = {}) {
|
// cli args
|
const outputFolder = options.out || './public';
|
const configFile = options.config || './electron/config/bin.js';
|
|
this.basePath = process.cwd();
|
this.encryptCodeDir = path.join(this.basePath, outputFolder);
|
|
// 先从 bin config获取,没有的话从 config/encrypt.js
|
const hasConfig = Utils.checkConfig(configFile);
|
if (hasConfig) {
|
const cfg = Utils.loadConfig(configFile);
|
this.config = cfg.encrypt;
|
}
|
if (!this.config) {
|
this.config = Utils.loadEncryptConfig();
|
}
|
|
this.filesExt = this.config.fileExt || ['.js'];
|
this.type = this.config.type || 'confusion';
|
this.bOpt = this.config.bytecodeOptions || {};
|
this.cOpt = this.config.confusionOptions || {};
|
this.cleanFiles = this.config.cleanFiles || ['./public/electron'];
|
this.patterns = this.config.files || null;
|
this.specificFiles = [ 'electron/preload/bridge.js' ];
|
|
// 旧属性,将废弃
|
this.dirs = [];
|
const directory = this.config.directory || ['electron'];
|
for (let i = 0; i < directory.length; i++) {
|
let codeDirPath = path.join(this.basePath, directory[i]);
|
if (fs.existsSync(codeDirPath)) {
|
this.dirs.push(directory[i]);
|
}
|
}
|
|
this.codefiles = this._initCodeFiles();
|
console.log(chalk.blue('[ee-bin] [encrypt] ') + 'cleanFiles:' + this.cleanFiles);
|
}
|
|
/**
|
* 初始化需要加密的文件列表
|
*/
|
_initCodeFiles() {
|
if (!this.patterns) return;
|
|
const files = globby.sync(this.patterns, { cwd: this.basePath });
|
return files;
|
}
|
|
/**
|
* 备份代码
|
*/
|
backup() {
|
// clean
|
this.cleanCode();
|
|
console.log(chalk.blue('[ee-bin] [encrypt] ') + 'backup start');
|
if (this.patterns) {
|
this.codefiles.forEach((filepath) => {
|
let source = path.join(this.basePath, filepath);
|
if (fs.existsSync(source)) {
|
let target = path.join(this.encryptCodeDir, filepath);
|
fsPro.copySync(source, target);
|
}
|
})
|
} else {
|
// 旧的逻辑,将废弃
|
for (let i = 0; i < this.dirs.length; i++) {
|
// check code dir
|
let codeDirPath = path.join(this.basePath, this.dirs[i]);
|
if (!fs.existsSync(codeDirPath)) {
|
console.log('[ee-bin] [encrypt] ERROR: backup %s is not exist', codeDirPath);
|
return
|
}
|
|
// copy
|
let targetDir = path.join(this.encryptCodeDir, this.dirs[i]);
|
console.log('[ee-bin] [encrypt] backup target Dir:', targetDir);
|
if (!fs.existsSync(targetDir)) {
|
this.mkdir(targetDir);
|
this.chmodPath(targetDir, '777');
|
}
|
|
fsPro.copySync(codeDirPath, targetDir);
|
}
|
}
|
|
console.log(chalk.blue('[ee-bin] [encrypt] ') + 'backup end');
|
return true;
|
}
|
|
/**
|
* 清除加密代码
|
*/
|
cleanCode() {
|
this.cleanFiles.forEach((file) => {
|
let tmpFile = path.join(this.basePath, file);
|
this.rmBackup(tmpFile);
|
console.log(chalk.blue('[ee-bin] [encrypt] ') + 'clean up tmp files:' + chalk.magenta(`${tmpFile}`));
|
})
|
}
|
|
/**
|
* 加密代码
|
*/
|
encrypt() {
|
console.log(chalk.blue('[ee-bin] [encrypt] ') + 'start ciphering');
|
if (this.patterns) {
|
for (const file of this.codefiles) {
|
const fullpath = path.join(this.encryptCodeDir, file);
|
if (!fs.statSync(fullpath).isFile()) continue;
|
|
// 特殊文件处理
|
if (this.specificFiles.includes(file)) {
|
this.generate(fullpath, 'confusion');
|
continue;
|
}
|
|
this.generate(fullpath);
|
}
|
} else {
|
// 旧逻辑,将废弃
|
console.log('[ee-bin] [encrypt] !!!!!! please use the new encryption method !!!!!!');
|
for (let i = 0; i < this.dirs.length; i++) {
|
let codeDirPath = path.join(this.encryptCodeDir, this.dirs[i]);
|
this.loop(codeDirPath);
|
}
|
console.log('[ee-bin] [encrypt] !!!!!! please use the new encryption method !!!!!!');
|
}
|
|
console.log(chalk.blue('[ee-bin] [encrypt] ') + 'end ciphering');
|
};
|
|
/**
|
* 递归
|
*/
|
loop(dirPath) {
|
let files = [];
|
if (fs.existsSync(dirPath)) {
|
files = fs.readdirSync(dirPath);
|
files.forEach((file, index) => {
|
let curPath = dirPath + '/' + file;
|
if (fs.statSync(curPath).isDirectory()) {
|
this.loop(curPath);
|
} else {
|
const extname = path.extname(curPath);
|
if (this.filesExt.indexOf(extname) !== -1) {
|
this.generate(curPath);
|
}
|
}
|
});
|
}
|
}
|
|
/**
|
* 生成文件
|
*/
|
generate(curPath, type) {
|
let encryptType = type ? type : this.type;
|
|
let tips = chalk.blue('[ee-bin] [encrypt] ') + 'file: ' + chalk.green(`${curPath}`) + ' ' + chalk.cyan(`(${encryptType})`);
|
console.log(tips);
|
|
if (encryptType == 'bytecode') {
|
this.generateBytecodeFile(curPath);
|
} else if (encryptType == 'confusion') {
|
this.generateJSConfuseFile(curPath);
|
} else {
|
this.generateJSConfuseFile(curPath);
|
this.generateBytecodeFile(curPath);
|
}
|
}
|
|
/**
|
* 使用 javascript-obfuscator 生成压缩/混淆文件
|
*/
|
generateJSConfuseFile(file) {
|
let opt = Object.assign({
|
compact: true,
|
stringArray: true,
|
stringArrayThreshold: 1,
|
}, this.cOpt);
|
|
let code = fs.readFileSync(file, "utf8");
|
let result = JavaScriptObfuscator.obfuscate(code, opt);
|
fs.writeFileSync(file, result.getObfuscatedCode(), "utf8");
|
}
|
|
/**
|
* 生成字节码文件
|
*/
|
generateBytecodeFile(curPath) {
|
if (path.extname(curPath) !== '.js') {
|
return
|
}
|
//let jscFile = curPath.replace(/.js/g, '.jsc');
|
let jscFile = curPath + 'c';
|
let opt = Object.assign({
|
filename: curPath,
|
output: jscFile,
|
electron: true
|
}, this.bOpt);
|
|
bytenode.compileFile(opt);
|
|
//fs.writeFileSync(curPath, 'require("bytenode");module.exports = require("./'+path.basename(jscFile)+'");', 'utf8');
|
|
fsPro.removeSync(curPath);
|
}
|
|
/**
|
* 移除备份
|
*/
|
rmBackup(file) {
|
if (fs.existsSync(file)) {
|
fsPro.removeSync(file);
|
}
|
return;
|
}
|
|
/**
|
* 检查文件是否存在
|
*/
|
fileExist(filePath) {
|
try {
|
return fs.statSync(filePath).isFile();
|
} catch (err) {
|
return false;
|
}
|
};
|
|
mkdir(dirpath, dirname) {
|
// 判断是否是第一次调用
|
if (typeof dirname === 'undefined') {
|
if (fs.existsSync(dirpath)) {
|
return;
|
}
|
this.mkdir(dirpath, path.dirname(dirpath));
|
} else {
|
// 判断第二个参数是否正常,避免调用时传入错误参数
|
if (dirname !== path.dirname(dirpath)) {
|
this.mkdir(dirpath);
|
return;
|
}
|
if (fs.existsSync(dirname)) {
|
fs.mkdirSync(dirpath);
|
} else {
|
this.mkdir(dirname, path.dirname(dirname));
|
fs.mkdirSync(dirpath);
|
}
|
}
|
};
|
|
chmodPath(path, mode) {
|
let files = [];
|
if (fs.existsSync(path)) {
|
files = fs.readdirSync(path);
|
files.forEach((file, index) => {
|
const curPath = path + '/' + file;
|
if (fs.statSync(curPath).isDirectory()) {
|
this.chmodPath(curPath, mode); // 递归删除文件夹
|
} else {
|
fs.chmodSync(curPath, mode);
|
}
|
});
|
fs.chmodSync(path, mode);
|
}
|
};
|
|
md5(file) {
|
const buffer = fs.readFileSync(file);
|
const hash = crypto.createHash('md5');
|
hash.update(buffer, 'utf8');
|
const str = hash.digest('hex');
|
return str;
|
}
|
}
|
|
const run = (options = {}) => {
|
const e = new Encrypt(options);
|
if (!e.backup()) return;
|
e.encrypt();
|
}
|
|
const clean = (options = {}) => {
|
let files = options.dir !== undefined ? options.dir : ['./public/electron'];
|
files = is.string(files) ? [files] : files;
|
|
files.forEach((file) => {
|
const tmpFile = path.join(process.cwd(), file);
|
if (fs.existsSync(tmpFile)) {
|
fsPro.removeSync(tmpFile);
|
console.log(chalk.blue('[ee-bin] [encrypt] ') + 'clean up tmp files: ' + chalk.magenta(`${tmpFile}`));
|
}
|
})
|
}
|
|
module.exports = {
|
run,
|
clean,
|
};
|