把 JavaScript 代码混淆到难以还原(javascript-obfuscator)

javascript-obfuscator 是一个用 TypeScript 写的 JavaScript 与 Node.js 代码混淆器,能重命名标识符、抽取并加密字符串、打平控制流。这篇文章给出它的安装方式、最短可用示例、主要参数和已知限制,并和另外两个同类混淆工具做横向对照,帮你判断它该不该进你的构建链。

交付到浏览器或者客户服务器上的 JavaScript 是明文。打开 DevTools,函数名、字符串字面量、接口地址和业务分支都摆在那里。javascript-obfuscator 把这些明文变换成语义等价、但阅读成本高得多的代码,开发者可以在打包流程的最后一步接上它。

javascript-obfuscator 用 TypeScript 写成,输入 JavaScript 或 Node.js 源码,输出语义等价但难以还原的代码,给需要保护自有代码的开发者使用。

它最常出现在两类场景,前端 bundle 发布前做最后一道处理,或者把 Node 脚本交付给外部部署。README 里有一条使用前就该看到的提醒。混淆后的代码比原始代码慢 15–80%,体积也明显增大,所以它不能当压缩工具使。

javascript-obfuscator 混淆前后的代码对照示意

基础用法

安装与依赖

包管理器二选一,官方 README 给的是装到 devDependencies:

yarn add --dev javascript-obfuscator
npm install --save-dev javascript-obfuscator

不想走构建流程,只想在页面里直接调用,可以从 CDN 引入浏览器版:

<script src="https://cdn.jsdelivr.net/npm/javascript-obfuscator/dist/index.browser.js"></script>

也可以从 node_modules 里按相对路径引 dist/index.browser.js。CLI 入口的可执行文件名就叫 javascript-obfuscator。

最短能跑通的示例

Node API 的调用形式,节选自 README:

var JavaScriptObfuscator = require('javascript-obfuscator');

var obfuscationResult = JavaScriptObfuscator.obfuscate(
    `
        (function(){
            var variable1 = '5' - 3;
            var variable2 = '5' + 3;
            console.log(variable1);
            console.log(variable2);
        })();
    `,
    {
        compact: false,
        controlFlowFlattening: true
    }
);

第二个参数是选项对象,上面这例只开了 compact 和 controlFlowFlattening。CLI 那条路径上,README 节选没有展开完整参数列表,只在一处提到了 --pro-api-token 与 --options-preset 两个跟 Pro API 有关的开关。

确认它跑起来了

仓库里没有说明 Node API 返回对象上取混淆代码的方法,也没有给出 CLI 的完整参数与输出位置。可用的判断办法是把输出代码实际跑一遍,对照混淆前的行为和控制台输出,再看耗时的变化。README 在开头就提醒混淆会带来明显性能损耗,这一步不该省。

主要功能

代码变换

  • 标识符重命名:identifierNamesGenerator 决定变量和函数名重写成什么样,renameGlobals 控制要不要连全局标识符一起改。打开 renameGlobals 之后,全局变量和函数名都会变,外部调用方需要跟着调整。
  • 字符串抽取与编码:stringArray 把散落在源码里的字符串字面量集中到一个数组,stringArrayEncoding 支持 base64 与 rc4 两种编码方式,rotateStringArray 与 shuffleStringArray 控制数组的轮换和打乱。
  • 控制流平坦化:controlFlowFlattening 把顺序分支改写成状态机结构。它会改变代码结构,5.8.1 修掉了它破坏 &&、||、?? 短路求值的问题。
  • 死代码注入:deadCodeInjection 往输出里插入不会被执行的代码分支,进一步增加阅读和自动化分析的难度。
  • 对象键变换:transformObjectKeys 处理对象字面量的键名。5.8.1 修复了它在赋值超过 50 次时产生重复变量声明的问题。

运行时防护

  • 自我保护:selfDefending 让混淆后的代码在被人为格式化之后无法正常运行,挡住最省事的一种还原手段。
  • 调试保护:debugProtection 在浏览器 DevTools 打开时冻结调试流程,用调试器单步跟踪会变得困难。
  • VM 字节码混淆:这是 Obfuscator.io 提供的付费能力,把 JavaScript 函数编译成跑在内嵌虚拟机上的自定义字节码,每次构建产出不同的操作码和虚拟机结构。Node API 入口是 obfuscatePro(),CLI 通过 --pro-api-token 传 token,optionsPreset 可以引用在 obfuscator.io 后台保存的自定义预设。它需要联网并持有 token,免费版本地运行、不联网。

参数与配置

常用参数

把选项表列全没有意义,README 自己的选项表就有几十项。下面这些在 issue 和更新日志里被反复提到。

  • compact:压缩输出格式。
  • controlFlowFlattening:控制流平坦化开关。
  • deadCodeInjection:死代码注入开关。
  • debugProtection:调试保护开关。
  • identifierNamesGenerator:标识符命名策略,issue 里出现过的取值是 mangled。
  • renameGlobals:是否重命名全局标识符。
  • stringArray 与 stringArrayEncoding:字符串数组与编码方式。
  • rotateUnicodeArray:Unicode 数组轮换,issue 里有人把它和杀毒误报放在一起讨论。
  • unicodeEscapeSequence:用 Unicode 转义写字符串。关掉它时某些转义字符会在混淆后变样,打开会让输出明显变大。
  • transformObjectKeys:对象键变换。
  • advertisement:5.7.0 新增,控制控制台里 Obfuscator Pro 推广信息的显示。

配置文件

仓库里没有说明配置文件的格式和查找路径。README 节选演示的是把选项对象直接传给 obfuscate()。Pro API 那条路径上有一个 optionsPreset,它接受在 obfuscator.io 后台保存的自定义预设别名,obfuscatePro() 和 CLI 的 --options-preset 会去取这份预设并合并进来。5.8.0 之后,--options-preset 传 VM 预设名(例如 vm-default)不再报错。

实际使用中的坑

下面几条来自仓库的 Issue 列表,都能查到具体讨论,写作时它们都已经是已解决状态。

  • 杀毒软件误报:有人用 rotateUnicodeArray: true、compact: true 这组配置混淆之后,Avast 把脚本识别为病毒威胁。已解决。
  • 分割字符串打坏构建产物:在 webpack 加 webpack 插件的流程里,字符串分割相关的处理会让最终代码跑不起来。已解决。
  • transformObjectKeys 产生重复变量声明:只有在赋值超过 50 次时才能复现,讨论里用最小例子定位到了触发条件。5.8.1 修复。
  • 短路求值被控制流平坦化破坏:&&、||、?? 在开启 controlFlowFlattening 后语义出错。5.8.1 修复。

几个同类怎么选

同样在做 JavaScript 混淆的项目,放在一起对照会更清楚。

项目适合谁部署方式主要限制什么情况下选它更合适项目地址
javascript-obfuscator要把前端 bundle 或 Node 脚本交付出去、又不想对方直接读到逻辑的开发者用 npm install --save-dev javascript-obfuscator 装包,或引入 dist/index.browser.js,CLI 与 Node API 两种调用方式混淆后代码比原始代码慢 15–80%、体积显著变大;VM 字节码混淆要走 obfuscator.io API,需要 token 且不能离线想要一套免费、选项颗粒度细、能接进 webpack / Rollup / Vite / Gulp / Grunt 等构建链的混淆方案javascript-obfuscator
js-confuser手上已有一套混淆流程、想再找一套做输出对照的开发者仓库没有说明未逐一核实当你要比较两套混淆器在同一份源码上的产物差别时,可以把它作为对照项js-confuser
jscvm想要 VM 字节码级别保护、又希望整套流程跑在本地的项目仓库没有说明未逐一核实你的目标是把 JavaScript 编译成运行在自带虚拟机上的加密字节码,这条路线和本项目免费版的纯 JavaScript 输出在形态上不同jscvm

只打算在发布前压一压体积的项目不该用它,混淆会让文件更大、执行更慢,压缩交给专门的 minifier 更划算。需要连字节码一起保护、而且必须全程离线完成的话,jscvm 这类把 JavaScript 编译成自跑虚拟机字节码的方案更贴近目标。javascript-obfuscator 免费版的输出仍然是可以读的 JavaScript,走到虚拟机那一层要叠加 obfuscator.io 的 Pro API 和 token。

适合谁

适合要把前端 bundle 或 Node 脚本交出去、又不想让对方直接读到逻辑的开发者。适合已经把 webpack、Rollup、Vite、Gulp、Grunt 接进构建链、想在打包之后补一步混淆的团队,README 为这些构建工具列出了对应插件和 loader,社区版本在 Netlify、Snowpack、Esbuild 上也有实现。对选项控制有要求、不接受黑盒 SaaS 的团队同样合适。

README 明确不建议混淆 vendor 脚本和 polyfill。这部分代码本来就是公开分发的,混淆它们没有保护价值,性能损失却会集中体现出来。需要离线完成 VM 字节码级别保护的场景,免费版做不到。

还有一条边界写在 README 的显著位置,只混淆属于你自己的代码。混淆能力本身不区分用途,把不属于自己的代码混淆后分发,或者拿它掩盖恶意逻辑,都会给自己带来实际风险。

项目地址在 github.com/javascript-obfuscator/javascript-obfuscator,采用 BSD-2-Clause 许可证,主要语言是 TypeScript,当前 16279 个 Star。

焚评:这个项目的量化评分

本项目的选题来自 焚.com(一个按公开公式给 GitHub 项目打分的站)。焚评当前总分 10.0 分(满分 10)。下表是各维度的得分:

评分维度得分
热度动量(权重 25%)10.0 / 10
开发活跃(权重 25%)9.8 / 10
社区响应(权重 15%)10.0 / 10
文档质量(权重 15%)10.0 / 10
发布节奏(权重 10%)10.0 / 10
风险控制(权重 10%)10.0 / 10

评分口径、权重与计算方式见焚.com 的评分方法页;数据随 GitHub 指标刷新,具体数值以焚.com 当前页面为准。本文正文为诀.com 独立撰写,评分数据由焚.com 授权引用。

内容核验说明

要把打包后的 JavaScript 交给外部部署、又想留一道阅读门槛的人,可以存这一份:安装方式、最短可用示例、常调参数和 README 自己标出的性能代价(比原码慢 15–80%、体积变大)都集中在一处,还列出几条已修复 Issue 的触发条件,比如字符串分割配合 webpack 插件会打坏产物。

文中 15–80% 性能损耗、5.8.1 修复项、Star 数及焚评分等数据来自原作者与公开仓库披露,诀.com 未独立验证;正文未提供混淆前后对照的运行测试证据。

项目来源与说明

开源项目:javascript-obfuscator(javascript-obfuscator)

本文由诀.com 编辑基于该项目的公开信息独立撰写,属原创解读,不是对项目文档的翻译或转载;文中提到的功能与参数以官方仓库为准,代码与文档版权归原作者所有。

查看项目仓库