JavaScript 代码混淆与防护

JavaScript 运行在客户端浏览器,源码对访问者完全可见。任何人都可以查看、复制、篡改、逆向分析业务代码。因此需要对 JS 源码做保护,提升逆向、山寨、盗用的成本。

JS 无法做到绝对不可破解。所有保护手段的目标是提高逆向成本,让破解代价大于破解带来的收益,而不是实现完全的“读不到代码”。浏览器最终仍然需要解析执行 JS,调试器依然可以介入拦截执行流程。

密码学有一句话: 当你采用的加密模式,使得攻击者为了破解所付出的代价 远远超过其所获得的利益之时,你的加密方案就是安全的。

为什么要做代码保护?

源码公开风险:JS 代码下发到浏览器客户端,网络面板、开发者工具可以直接拿到源码。业务逻辑、算法、校验规则容易被直接复制盗用。

业务安全威胁

  • 原创业务逻辑被抄袭、二次山寨;

  • 前端校验逻辑被绕过,接口被恶意调用;

  • 核心算法、风控逻辑被逆向分析。

代码保护的目标:

  • 提高读代码难度:混淆后的代码语义晦涩,人眼难以阅读理解业务逻辑;
  • 对抗动态调试:增加逆向人员调试跟踪、断点分析的难度。

加密和混淆:

  • 加密:原始代码经过加密运算,运行时需要解密得到原始 JS 再执行;一旦解密逻辑被找到,原始代码可以完整还原。单纯加密保护能力弱。
  • 混淆:基于 AST 语法树改造代码本身,不额外解密步骤,直接执行混淆后代码,改变代码结构但保持执行逻辑不变。

业界实践:加密必须配合混淆才有实际防护能力,单纯加密属于伪保护。

主流保护方案

方案 原理 防护能力 缺点
单纯代码加密 代码整体加密,运行时解密再执行 只要提取解密结果,即可拿到完整源码,容易被破解
虚拟机保护 把 JS 翻译为自定义虚拟机字节码,自定义虚拟机解释执行 体积膨胀明显,兼容性风险大,调试排错困难
AST 代码混淆 修改抽象语法树:变量重命名、字符串数组化、控制流平展、插入僵尸死代码等 中-高 体积小幅上涨;部分混淆规则会带来性能损耗;高强度混淆增加排错难度
简单正则替换混淆 正则做简单变量、字符串替换 容易被美化还原,仅适合做基础防复制

生产环境最实用方案:AST 语法树级别的代码混淆,在不改变业务执行逻辑前提下,提升逆向成本。

混淆的技术分类

eval 混淆(初代混淆,防护弱)

早期混淆手段,把代码转为字符串,通过 eval() 执行字符串代码。

弱点:只需要把 eval 的参数打印输出,就能拿到原始代码,防护能力极弱,现在基本淘汰。

Hash、字典混淆

典型代表:javascript-obfuscator

核心思路:

  • 将全部字符串、标识符提取到全局字典数组;
  • 业务代码不再直接写字面量,运行时通过数组下标读取真实字符串。 美化格式化代码后,业务标识符全部变成数组索引,增加阅读难度。

同类早期JSA加密,仅仅做简单变量名替换,经过代码美化工具 beautifier.io 即可快速还原,几乎没有防护价值。

压缩混淆(uglify-js)

代表库:uglify-jsterser

普通代码压缩 ≠ 安全混淆。压缩主要目标是减小包体积:删除空格、注释、缩短变量名,仅仅只能防简单复制,不能抵御逆向分析。

普通代码压缩:

  • 变量、局部函数名压缩重命名;
  • 不会混淆字符串字面量,不会做控制流平展,没有对抗调试能力

uglify-js mangle 混淆相关配置:

{
  mangle: {
    toplevel: true,     // 混淆顶层作用域变量
    eval: true,         // 混淆eval/with内变量
    // properties 谨慎开启,容易破坏对象属性调用逻辑
    // properties: {}
  }
}

混淆器底层原理:AST抽象语法树

编译器基础流程:源代码文本 → 词法分析拆分为 Token(数字、字符串、关键字) → 语法分析生成 AST抽象语法树 → 转换中间代码 → 机器码。

JS混淆器复用编译器思想:

  • 将 JS 源码解析成 AST抽象语法树,不再操作原始字符串;
  • 在AST树上做转换、改写:变量改名、字符串提取、拆分表达式、插入无效僵尸代码、控制流平展;
  • 将修改后的 AST 重新生成 JS 源码。

AST 优势:理解代码语法逻辑,可以做深度语义层面的混淆;

正则混淆:正则只是对文本替换,无法理解语法结构,混淆效果有限。

常用混淆规则(不改变业务执行结果)

  • 标识符重命名:变量、函数名替换为无意义短名称;
  • 字符串提取阵列化:把字符串字面量统一放到数组,运行时下标读取;
  • 控制流平展:把顺序逻辑拆分成大的 switch-case、条件判断,打乱阅读顺序;
  • 插入僵尸无效代码:不会执行的假分支,干扰阅读;
  • 数字字面量编码、表达式拆分。

代码混淆需要注意:

  • 混淆会带来副作用:高强度混淆会增大JS文件体积;增加JS执行开销,造成页面性能下降;混淆后的代码线上报错堆栈难以定位,建议保留混淆前原始sourcemap用于排查问题。
  • 谨慎开启属性名混淆:混淆对象属性名(如uglify mangle-properties)风险很高,JSON接口字段、外部对象、DOM API对象属性被混淆后,业务直接报错。
  • 测试回归:混淆完成后务必完整回归测试,部分混淆规则会对 evalwith、复杂正则、特殊ES语法产生兼容性问题。

增强防护手段

反调试对抗 debugger

利用 debugger; 语句,当开发者打开浏览器开发者工具调试时,持续触发debugger断点,干扰动态调试。

局限性:用户可以在浏览器禁用debugger断点,只能提升调试门槛,无法彻底杜绝。

代码载体变形

图片隐写:Canvas读取PNG图片二进制,把JS脚本存放在图片二进制数据内,运行时提取执行;

CSS载体:利用css content 属性存储字符串,JS读取css内容拿到脚本片段;

以上只是载体变换,源码内容本身没有加密,只是增加获取源码步骤。

常用工具

开源工具

javascript-obfuscator

Github:https://github.com/javascript-obfuscator/javascript-obfuscator

能力:字符串阵列、控制流平展、死代码注入、反调试,支持ES6+;提供CLI、API、在线Demo;

注意:作者维护精力有限,大型项目要充分测试兼容性;高强度混淆会带来性能损耗、包体积增大。

//源码
function aa () {
    console.log('aaa')
}
aa()

//混淆后
var _cs=['\x61\x61\x61']; function _f0() { console.log(_cs[0]) } _f0()

terser、uglify-js

定位:压缩工具,侧重减小代码体积,混淆能力有限

terser 是现行打包工具的默认压缩器,uglify-js 处于维护状态。二者都不能作为高强度安全混淆使用

jsfuck

Github:https://github.com/aemkei/jsfuck

将JS全部使用[]!+等基础符号组合生成代码。

优点:看起来完全天书;

缺点:代码体积爆炸增长,性能差,仅适合玩demo,不适合业务生产。

//源码
alert('a')

//转换后
[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+........

商业付费工具

Jscrambler:行业成熟商业JS保护方案,支持虚拟机、高强度混淆、反调试,兼容性好,适合高安全要求业务。

JShaman:国内JS混淆加密商业平台。

总结

混淆不等同于传输加密。网络传输层面依然建议配合 HTTPS;如果有接口敏感参数,需要结合 Web Crypto API 或 crypto-js 等密码学库做传输加密。混淆是源码保护,HTTPS 是传输层保护,二者解决不同维度问题。

前端混淆的目的:提高逆向成本,不是绝对安全,前端校验永远可以被绕过。真正敏感核心逻辑,优先放到后端服务执行。

参考资料

javascript-obfuscator

uglify-js

jsfuck

© lizhao all right reserved,powered by Gitbook文件修订时间: 2026-08-31 20:32:21

results matching ""

    No results matching ""