JavaScript 代码保护技术 混淆和加密

『前端代码无秘密』这句话好似一个业界共识一般在前端领域传播。但在日常的开发过程中,我们又会涉及以及需要相当强度的前端核心代码的加密,特别是在于与后端的数据通信上面(包括HTTP、HTTPS请求以及WebSocket的数据交换)。

比如一个场景,在视频相关的产品中,我们通常需要增加相关的安全逻辑防止被直接盗流或是盗播。特别是对于直播来说,我们的直播视频流文件通常会被划分为分片然后通过协商的算法生成对应的URL参数并逐次请求。分片通常以5至10秒一个间隔,如果将分片URL的获取作为接口完全放置于后端,那么不仅会给后端带来极大的压力外还会带来直播播放请求的延迟,因此我们通常会将部分实现放置于前端以此来减少后端压力并增强体验。

对于iOS或是Android来说,我们可以将相关的算法通过C/C++进行编写,然后编译为dylib或是so并进行混淆以此来增加破解的复杂度,但是对于前端来说,并没有类似的技术可以使用。当然,自从asm.js及WebAssembly的全面推进后,我们可以使用其进一步增强我们核心代码的安全性。


一, 使用Javascript的混淆器
在我们的日常开发过程中,对于Javascript的混淆器我们是不陌生的,我们常常使用其进行代码的压缩以及混淆以此来减少代码体积并增加人为阅读代码的复杂度。常使用的项目包括: 
    •    UglifyJS 
    •    Google Closure Compiler 
    •    YUI Compressor 
    •    ...
Javascript混淆器的原理并不复杂,其核心是对目标代码进行AST Transformation(抽象语法树改写),我们依靠现有的Javascript的AST Parser库,能比较容易的实现自己的Javascript混淆器。以下我们借助 acorn 来实现一个if语句片段的改写。
假设我们存在这么一个代码片段:
for(var i = 0; i < 100; i++){
    if(i % 2 == 0){
        console.log("foo");
    }else{
        console.log("bar");
    }
}
我们通过使用UglifyJS进行代码的混淆,我们能够得到如下的结果:
for(var i=0;i<100;i++)i%2==0?console.log("foo"):console.log("bar");


从上面的实现我们可以看出,Javascript混淆器只是将Javascript代码变化为另一种更不可读的形式,以此来增加人为分析的难度从而达到增强安全的目的。这种方式在很久以前具有很不错的效果,但是随着开发者工具越来越强大,实际上通过单步调试可以很容易逆向出原始的Javascript的核心算法。当然,后续也有相当多的库做了较多的改进,JavaScript Obfuscator Tool 是其中的代表项目,其增加了诸如反调试、变量前缀、变量混淆等功能增强安全性。但万变不离其宗,由于混淆后的代码仍然是明文的,如果有足够的耐心并借助开发者工具我们仍然可以尝试还原,因此安全性仍然大打折扣。


二、 使用Flash的C/C++扩展方式
在Flash还大行其道的时期,为了更好的方便引擎开发者使用C/C++来提升Flash游戏相关引擎的性能,Adobe开源了 CrossBridge 这个技术。在这种过程中,原有的C/C++代码经过LLVM IR变为Flash运行时所需要的目标代码,不管是从效率提升上还是从安全性上都有了非常大的提升。对于目前的开源的反编译器来说,很难反编译由CorssBridge编译的C/C++代码,并且由于Flash运行时生产环境中禁用调试,因此也很难进行对应的单步调试。
使用Flash的C/C++扩展方式来保护我们的前端核心代码看起来是比较理想的方法,但Flash的移动端上已经没有任何可被使用的空间,同时Adobe已经宣布2020年不再对Flash进行维护,因此我们完全没有理由再使用这种方法来保护我们前端的核心代码。
当然,由于Flash目前在PC上仍然有很大的占有率,并且IE10以下的浏览器仍然有不少份额,我们仍旧可以把此作为一种PC端的兼容方案考虑进来。


三、使用asm.js或WebAssembly
为了解决Javascript的性能问题,Mozilla提出了一套新的面相底层的Javascript语法子集 -- asm.js,其从JIT友好的角度出发,使得Javascript的整体运行性能有了很大的提升。后续Mozilla与其他厂商进行相关的标准化,产出了WebAssembly标准。
不管是asm.js或是WebAssembly,我们都可以将其看作为一个全新的VM,其他语言通过相关的工具链产出此VM可执行的代码。从安全性的角度来说,相比单纯的Javascript混淆器而言,其强度大大的增加了,而相比于Flash的C/C++扩展方式来说,其是未来的发展方向,并现已被主流的浏览器实现。
可以编写生成WebAssembly的语言及工具链非常多,我们使用C/C++及其Emscripten作为示范编写一个简单的签名模块进行体验。
#include <string>
#include <emscripten.h>
#include <emscripten/bind.h>
#include "md5.h"

#define SALTKEY "md5 salt key"

std::string sign(std::string str){
    return md5(str + string(SALTKEY));
}

// 此处导出sign方法供Javascript外部环境使用
EMSCRIPTEN_BIND(my_module){
    emscripten::function("sign", &sign);
}
接着,我们使用emscripten编译我们的C++代码,得到对应的生成文件。
em++ -std=c++11 -Oz --bind \
    -I ./md5 ./md5/md5.cpp ./sign.cpp \
    -o ./sign.js
最后,我们引入生成sign.js文件,然后进行调用。
<body>
    <script src="./sign.js"></script>
    <script>
        // output: 0b57e921e8f28593d1c8290abed09ab2
        Module.sign("This is a test string");
    </script>
</body>
目前看起来WebAssembly是目前最理想的前端核心代码保护的方案了,我们可以使用C/C++编写相关的代码,使用Emscripten相关工具链编译为asm.js和wasm,根据不同的浏览器的支持情况选择使用asm.js还是wasm。并且对于PC端IE10以下的浏览器,我们还可以通过CrossBridge复用其C/C++代码,产出对应的Flash目标代码,从而达到非常好的浏览器兼容性。
然而使用asm.js/wasm后对于前端核心代码的保护就可以高枕无忧了么?由于asm.js以及wasm的标准规范都是完全公开的,因此对于asm.js/wasm标准实现良好反编译器来说,完全可以尽可能的产出阅读性较强的代码从而分析出其中的核心算法代码。但幸运的是,目前作者还暂时没有找到实现良好的asm.js/wasm反编译器,因此我暂时认为使用此种方法在保护前端核心代码的安全性上已经可堪重用了。
 

来源:https://www.v2ex.com/t/552383

修改时间 2019-04-10

真诚赞赏,手留余香
赞赏
随机推荐
ps -ef | grep 输出的具体含义
Sequelize 事务的使用与Transaction类
X-sendfile 大文件下载解决方案
Javascript 获取时间戳
Wordpress 常用标签笔记
Apache自带的ab压力测试工具
创建Sprites雪碧图 精灵图
Photoshop 智能对象 学习笔记
Vue和Ajax的关系,Axios简介
使用mint-ui开发项目的一些所得