NodeJs学习日志(9):javascript-obfuscator代码混淆

使用javascript-obfuscator 进行代码混淆

文档

包文档 https://doc.yoouu.cn/front-end/javascript-obfuscator.html
github:https://github.com/javascript-obfuscator/javascript-obfuscator

保护知识产权

JavaScript 作为前端主要开发语言,其源代码在客户端是公开可见的。代码混淆可以有效:

  1. 防止代码被直接复制:混淆后的代码难以理解和复用
  2. 保护核心算法:商业逻辑和专有技术得到一定程度的保护
  3. 增加逆向工程难度:即使有经验的开发者也需要大量时间分析

混淆的局限性

代码混淆:

  1. 不是加密:源代码仍然可以在浏览器中执行
  2. 不能完全防止逆向:只能增加分析难度和成本
  3. 不影响网络请求:API调用仍然可以被抓包分析

依赖包

npm install fs-extra javascript-obfuscator rimraf

  "dependencies": {
    "fs-extra": "^11.3.2",
    "javascript-obfuscator": "^4.1.1",
    "rimraf": "^6.1.2"
  }

用法

可以被混淆的代码

// original-code.js
function calculatePrice(quantity, price) {
    const taxRate = 0.1;
    const discount = quantity > 10 ? 0.15 : 0.05;
    const subtotal = quantity * price;
    const tax = subtotal * taxRate;
    const total = subtotal + tax;
    return total * (1 - discount);
}

console.log("Total price:", calculatePrice(5, 100));
console.log("Total price:", calculatePrice(15, 100));

运行

清理历史文件

 npm run clean

代码混淆

 npm run obfuscate

在这里插入图片描述

忽略文件

在这里补充忽略文件,

// 忽略列表(在列表中的文件/目录直接复制,不在的 JS 文件自动混淆)
// 支持格式:单个文件、文件夹、通配符(和 .gitignore 规则一致)
const ignoreList = [
    // 无需混淆的目录(直接复制)
    'files',
    'oss',
    'uploads',
    'bin',
    '.env',
    'license.dat',
    "node_modules",
    "obfuscated-dist",
];

并且在function processAllFiles(dir) 中强制忽略文件

        if (item === 'node_modules' || item === 'obfuscated-dist') {
            console.log(`🚫 完全跳过目录: ${item}`);
            continue; // 直接跳过,不复制、不处理、不递归
        }

package.json

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "obfuscate": "node obfuscate.js",
    "clean": "rimraf obfuscated-src dist obfuscated-dist"
  },

工具代码

const JavaScriptObfuscator = require('javascript-obfuscator');
const fs = require('fs-extra');
const path = require('path');

//  混淆核心配置
const obfuscationConfig = {
    compact: true, // 压缩代码(去除空格、换行,减小体积)
    controlFlowFlattening: true, // 启用控制流扁平化(打乱代码执行逻辑,降低可读性)
    controlFlowFlatteningThreshold: 0.75, // 75% 的代码块会被扁平化(平衡混淆强度和性能)
    deadCodeInjection: true, // 注入死代码(无效代码片段,干扰逆向分析)
    deadCodeInjectionThreshold: 0.4, // 40% 的概率注入死代码(避免过度增大体积)
    debugProtection: false, // 关闭调试保护(无需阻止开发者工具调试)
    debugProtectionInterval: 0, // 调试保护间隔(关闭时设为 0)
    disableConsoleOutput: false, // 不禁用 console 输出(保留日志打印功能)
    identifierNamesGenerator: 'hexadecimal', // 变量名生成方式:十六进制(如 0xabc123)
    log: false, // 关闭混淆过程日志输出
    numbersToExpressions: true, // 将数字转为表达式(如 100 → 0x64 + 0)
    renameGlobals: false, // 不重命名全局变量(避免影响外部调用)
    renameProperties: false, // 不重命名对象属性(避免破坏代码逻辑,如配置项、API 字段)
    selfDefending: true, // 启用自保护(防止代码被二次格式化、反混淆)
    simplify: true, // 简化混淆后的代码(去除冗余逻辑,平衡可读性和体积)
    splitStrings: true, // 拆分字符串(将长字符串拆分为短片段拼接)
    splitStringsChunkLength: 10, // 字符串拆分长度:每 10 个字符拆分一次
    stringArray: true, // 启用字符串数组(将字符串集中存储,通过索引访问)
    stringArrayEncoding: ['base64', 'rc4'], // 字符串数组编码方式(双重加密,增强安全性)
    stringArrayIndexShift: true, // 字符串数组索引偏移(访问时偏移索引,防止直接读取)
    stringArrayWrappersCount: 3, // 字符串数组包装函数数量(3 层嵌套函数,增加复杂度)
    stringArrayWrappersChainedCalls: true, // 包装函数链式调用(如 fn1(fn2(fn3())) )
    stringArrayWrappersParametersMaxCount: 4, // 包装函数最大参数数量(4 个参数,干扰分析)
    stringArrayWrappersType: 'function', // 包装函数类型:普通函数(而非箭头函数)
    stringArrayThreshold: 0.75, // 75% 的字符串会被存入字符串数组(平衡混淆和性能)
    unicodeEscapeSequence: false, // 不使用 Unicode 转义(避免代码可读性过低,便于少量调试)
    sourceMap: false, // 不生成 sourceMap(防止通过映射还原原代码)
    rotateStringArray: true, // 运行时旋转字符串数组(每次执行索引动态变化,静态分析失效)
    shuffleStringArray: true, // 混淆前打乱字符串数组顺序(干扰索引查找,增强安全性)
};

// 源目录和目标目录
const srcDir = '.';
const distDir = './obfuscated-dist';

// 忽略列表(在列表中的文件/目录直接复制,不在的 JS 文件自动混淆)
// 支持格式:单个文件、文件夹、通配符(和 .gitignore 规则一致)
const ignoreList = [
    // 无需混淆的目录(直接复制)
    'files',
    'oss',
    'uploads',
    'bin',
    '.env',
    'license.dat',
    "node_modules",
    "obfuscated-dist"
];

// 要排除的全局文件(优先级最高,直接跳过,不参与任何处理)
const excludeFiles = [
    'obfuscate.js',           // 混淆脚本本身(避免自混淆)
    'package.json',           // 依赖配置文件(混淆后会影响依赖安装)
    'package-lock.json',      // 依赖锁文件(无需混淆)   
];

// 确保目标目录存在(先删除旧目录,避免残留文件干扰)
if (fs.existsSync(distDir)) {
    fs.removeSync(distDir);
}
fs.mkdirSync(distDir, { recursive: true });

// ------------------------------
// 核心工具函数:判断文件是否在忽略列表中
// 支持规则:
// 1. 精确匹配(如 .env、node_modules)
// 2. 通配符匹配(如 *.log、node_modules/*、src/*/test.js)
// 3. 隐藏文件匹配(如 .git、.env)
// ------------------------------
function isIgnored(fileRelativePath) {
    // fileRelativePath:文件相对于 srcDir 的路径(如 config/app.js、public/index.html、.env)
    return ignoreList.some(pattern => {
        // 处理 pattern:去除首尾空格,统一路径分隔符为 /(兼容 Windows \)
        const normalizedPattern = pattern.trim().replace(/\\/g, '/');
        const normalizedFile = fileRelativePath.replace(/\\/g, '/');

        // 情况 1:精确匹配(完全相等)
        if (normalizedPattern === normalizedFile) {
            return true;
        }

        // 情况 2:pattern 以 /* 结尾(匹配目录下所有文件,如 node_modules/*)
        if (normalizedPattern.endsWith('/*')) {
            const dir = normalizedPattern.slice(0, -2); // 去掉末尾的 /*
            // 匹配:目录本身 或 目录下的所有文件(如 node_modules 或 node_modules/xxx)
            return normalizedFile === dir || normalizedFile.startsWith(`${dir}/`);
        }

        // 情况 3:pattern 以 * 开头(匹配后缀,如 *.log、*.json)
        if (normalizedPattern.startsWith('*.')) {
            const ext = normalizedPattern.slice(1); // 去掉开头的 *
            return normalizedFile.endsWith(ext);
        }

        // 情况 4:pattern 包含 *(中间通配符,如 src/*/test.js)
        if (normalizedPattern.includes('*')) {
            // 把 * 替换成正则通配符(匹配任意字符,不包括 /),其他字符转义
            const regexStr = normalizedPattern
                .replace(/\*/g, '[^/]*') // * 匹配任意非 / 字符
                .replace(/\./g, '\\.') // 转义 .
                .replace(/\//g, '\\/'); // 转义 /
            const regex = new RegExp(`^${regexStr}$`); // 全匹配
            return regex.test(normalizedFile);
        }

        // 其他情况:不匹配
        return false;
    });
}



// 复制文件/目录(通用函数,处理“忽略混淆”的文件/目录)
function copyFileOrDir(sourcePath, targetPath) {
    try {
        if (fs.statSync(sourcePath).isDirectory()) {
            fs.copySync(sourcePath, targetPath);
            console.log(`📁 复制目录(忽略混淆): ${path.relative(srcDir, sourcePath)}`);
        } else {
            fs.copySync(sourcePath, targetPath);
            console.log(`📁 复制文件(忽略混淆): ${path.relative(srcDir, sourcePath)}`);
        }
    } catch (error) {
        console.error(`❌ 复制失败(忽略混淆): ${path.relative(srcDir, sourcePath)}`, error.message);
    }
}

// 混淆 JavaScript 文件(仅对“不在忽略列表”的 JS 文件执行)
function obfuscateFile(filePath, relativePath) {
    try {
        const code = fs.readFileSync(filePath, 'utf8');
        // 执行混淆
        const obfuscatedResult = JavaScriptObfuscator.obfuscate(code, obfuscationConfig);

        const outputPath = path.join(distDir, relativePath);
        const outputDir = path.dirname(outputPath);
        // 确保输出目录存在
        if (!fs.existsSync(outputDir)) {
            fs.mkdirSync(outputDir, { recursive: true });
        }

        // 写入混淆后的代码
        fs.writeFileSync(outputPath, obfuscatedResult.getObfuscatedCode());
        console.log(`✅ 混淆完成: ${relativePath}`);
    } catch (error) {
        // 混淆失败时, fallback 复制原文件(避免影响整体执行)
        console.error(`❌ 混淆失败( fallback 复制原文件): ${relativePath}`, error.message);
        const targetPath = path.join(distDir, relativePath);
        copyFileOrDir(filePath, targetPath);
    }
}

// 递归处理所有文件/目录(核心逻辑)
function processAllFiles(dir) {
    const items = fs.readdirSync(dir);

    for (const item of items) {
       // node_modules 和 obfuscated-dist 目录(不复制、不处理)
        if (item === 'node_modules' || item === 'obfuscated-dist') {
            console.log(`🚫 完全跳过目录: ${item}`);
            continue; // 直接跳过,不复制、不处理、不递归
        }
         
        // 1. 跳过全局排除文件(优先级最高,不参与任何处理)
        if (excludeFiles.includes(item)) continue;

        const fullPath = path.join(dir, item);
        const relativePath = path.relative(srcDir, fullPath); // 相对于根目录的路径(用于忽略判断)

        // 2. 若在忽略列表 → 直接复制,不递归(避免重复处理子文件)
        if (isIgnored(relativePath)) {
            const targetPath = path.join(distDir, relativePath);
            copyFileOrDir(fullPath, targetPath);
            continue;
        }

        const stat = fs.statSync(fullPath);
        if (stat.isDirectory()) {
            // 3. 目录不在忽略列表 → 递归处理其子文件
            processAllFiles(fullPath);
        } else if (path.extname(item) === '.js') {
            // 4. JS 文件不在忽略列表 → 执行混淆
            obfuscateFile(fullPath, relativePath);
        } else {
            // 5. 非 JS 文件(如 .json、.png 等)不在忽略列表 → 直接复制(混淆无意义)
            const targetPath = path.join(distDir, relativePath);
            const targetDir = path.dirname(targetPath);
            if (!fs.existsSync(targetDir)) {
                fs.mkdirSync(targetDir, { recursive: true });
            }
            copyFileOrDir(fullPath, targetPath);
        }
    }
}

// 主函数
async function main() {
    console.log('🚀 开始混淆源代码...');
    console.log(`📋 忽略列表共 ${ignoreList.length} 项,不在列表的 JS 文件将自动混淆\n`);

    // 递归处理根目录下所有文件/目录(自动遍历,无需手动指定混淆目录)
    processAllFiles(srcDir);

    console.log('\n🎉 所有文件处理完成!');
    console.log(`📁 输出目录: ${distDir}`);
    console.log('\n📋 使用说明:');
    console.log('1. 进入 obfuscated-dist 目录');
    console.log('2. 运行: npm install');
    console.log('3. 运行: npm start');
}

// 执行主函数(捕获异常并提示依赖安装)
main().catch(error => {
    console.error('\n❌ 执行失败:', error.message);
    console.log('💡 提示:请先安装依赖:npm i javascript-obfuscator  fs-extra');
});

参数

  stringArrayEncoding: ['base64', 'rc4'], // 字符串数组编码方式    
    debugProtection: false, // 关闭调试保护(无需阻止开发者工具调试)
    debugProtectionInterval: 0, // 调试保护间隔(关闭时设为 0)(双重加密,增强安全性)
    stringArrayWrappersCount: 3, // 字符串数组包装函数数量(3 层嵌套函数,增加复杂度)
    compact: true, // 压缩代码(去除空格、换行,减小体积)
    controlFlowFlattening: true, // 启用控制流扁平化(打乱代码执行逻辑,降低可读性)
    controlFlowFlatteningThreshold: 0.75, // 75% 的代码块会被扁平化(平衡混淆强度和性能)
    deadCodeInjection: true, // 注入死代码(无效代码片段,干扰逆向分析)
    deadCodeInjectionThreshold: 0.4, // 40% 的概率注入死代码(避免过度增大体积)

stringArrayEncoding: [‘base64’, ‘rc4’]

字符串数组双重编码机制

  • Base64编码:将字符串转换为Base64格式,防止直接阅读原始文本内容
  • RC4加密:应用流加密算法,在运行时动态解码,增加静态分析难度
  • 双重保护:两种编码方式叠加使用,破解者需要同时逆向两种编码机制才能获取原始字符串
  • 安全增强:即使攻击者识别出Base64编码,仍需破解RC4加密才能获得有用信息

debugProtection 与 debugProtectionInterval

调试保护机制

  • debugProtection: false:保持开发者工具可用,便于问题排查和日志输出
  • debugProtectionInterval: 0:当debugProtection关闭时,间隔设置无效
  • 启用时的作用:如果设置为true,会阻止浏览器开发者工具的调试功能,包括断点设置和代码单步执行
  • 使用建议:在需要极高级别保护的场景下可开启,但会牺牲调试便利性

stringArrayWrappersCount: 3

多层函数包装设计

  • 包装层级:创建3层嵌套的包装函数来访问字符串数组
  • 调用复杂度:简单的数组访问变为多层函数调用链,如 getString(index) 转换为 wrapper1(wrapper2(wrapper3(index)))
  • 反分析效果:增加代码执行路径的复杂性,干扰自动化分析工具
  • 性能考量:适中的层数在安全性和运行效率间取得平衡

compact: true

代码压缩优化

  • 空间优化:移除所有不必要的空格、换行符和注释
  • 体积减少:显著减小代码文件大小,提升加载性能
  • 可读性降低:压缩后的代码失去格式化,人工阅读困难
  • 生产就绪:符合生产环境代码部署的最佳实践

controlFlowFlattening 与 controlFlowFlatteningThreshold

控制流混淆技术

  • controlFlowFlattening: true:激活控制流扁平化,将顺序执行的代码转换为复杂的控制结构
  • controlFlowFlatteningThreshold: 0.75:75%的代码块会接受扁平化处理
  • 技术原理:将直线型代码逻辑重构为基于switch-case的状态机模式
  • 平衡策略:阈值0.75确保大部分关键逻辑被保护,同时避免过度影响运行时性能

deadCodeInjection 与 deadCodeInjectionThreshold

死代码注入策略

  • deadCodeInjection: true:在原始代码中插入不会执行的冗余代码片段
  • deadCodeInjectionThreshold: 0.4:40%的代码位置有机会被注入死代码
  • 干扰作用:增加代码静态分析的噪音,误导逆向工程人员
  • 体积控制:适中的阈值避免代码体积过度膨胀,维持合理的文件大小
在解决 `webpack-obfuscator@2.6.0` 安装过程中出现的 `peerDependencies` 版本不匹配问题时,需要理解其依赖关系以及如何处理 npm 的依赖解析机制。`webpack-obfuscator` 依赖于 `javascript-obfuscator` 库来实现代码混淆功能,并且对 `webpack` 的版本也有特定要求。 ### 依赖冲突的原因 `webpack-obfuscator` 依赖的 `webpack` 版本可能与项目中当前安装的版本不兼容。例如,`webpack-obfuscator@2.6.0` 可能要求 `webpack@^4.0.0`,而其他插件(如 `compression-webpack-plugin@9.2.0`)则需要 `webpack@^5.1.0`,这会导致依赖冲突 [^2]。 ### 解决方案 #### 使用 `--legacy-peer-deps` 参数 在安装过程中,可以通过使用 `--legacy-peer-deps` 参数来忽略 `peerDependencies` 的版本冲突问题。该参数会按照 npm v4 到 v6 的方式处理依赖关系,忽略 `peerDependencies` 的版本检查。 ```bash npm install webpack-obfuscator@2.6.0 --save-dev --legacy-peer-deps ``` #### 使用 `--force` 参数 另一种方法是使用 `--force` 参数强制安装,这会忽略所有冲突并安装指定版本的依赖,但可能导致潜在的不稳定性。 ```bash npm install webpack-obfuscator@2.6.0 --save-dev --force ``` #### 检查并更新依赖版本 确保所有依赖项都兼容当前的 `webpack` 版本。如果可能,更新 `webpack-obfuscator` 或其他插件到最新版本,以获得更好的兼容性。 #### 使用本地安装而非全局安装 避免全局安装可能会导致的版本冲突问题,而是将 `webpack-obfuscator` 作为开发依赖安装在项目本地。 ```bash npm install webpack-obfuscator@2.6.0 --save-dev ``` ### 兼容性注意事项 在处理依赖冲突时,还需要考虑 Node.js 的版本是否满足项目需求。例如,某些插件可能需要 Node.js 版本 `^12.0.0` 或更高版本 [^4]。确保使用的 Node.js 版本符合所有依赖项的要求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值