zoukankan      html  css  js  c++  java
  • 关于代码压缩混淆加密整理;

    我这里说的前端加密,是对浏览器端的js文件加密,不是传输过程中的加密,不涉及hash摘要等

    1. 降低可读性

    1.1 压缩(compression)

    很好理解,就是去掉注释、多于的空格、简化标识符等等。工具很多,YUI Compressor、UglifyJS、Google Closure Compiler等等。

    1.2 混淆(obfuscation)

    保证不破坏代码执行结果的情况下,让代码变得难以阅读。常用混淆规则:拆分字符串、拆分数组、增加废代码、,压缩其实也有一定混淆功能。本质就是改变输入代码字符串的抽象语法树(AST)的结构。其他工具:v8就是一个,还有mozilla的SpiderMonkey, 知名的esprima,还有uglify;商业混淆服务有:jscramble。

    1.3 加密(encryption)

    这里的加密指文本可逆编码,是狭义的加密,也就是我们常说的加密啦。这个部分依然是借助一些工具,如: Packer 、bcrypt等等。

    2. 代码不放置在JS文件中

    将代码放在非js文件中,增加定位难度。这里常用的方式有两种:放置到png中,通过HTML Canvas 2D Context获取二进制数据的特性,可以用图片来存储脚本资源;放置到css文件中,利用content样式可以存放字符串的特性,同样可以。

    2.1 png

    用png保存js代码,首先需要对png进行编码,然后使用的时候进行解码。借助canvas及base64和二进制编码。

    编码

    1、字符串转换成ascii码; 
    2、创建足够存储空间的canvas; 
    3、将字符填入到像素中(忽略alpha值); 
    4、获取data url; 
    canvas.toDataURL(“image/png”); 
    5、存为png图片。

    function encodeUTF8(str) {  
        return String(str).replace(  
            /[u0080-u07ff]/g,  
            function(c) {  
                let cc = c.charCodeAt(0);
                return String.fromCharCode(0xc0 | cc >> 6, 0x80 | cc & 0x3f);
            }
        ).replace(  
            /[u0800-uffff]/g,  
            function(c) {  
                let cc = c.charCodeAt(0);
                return String.fromCharCode(0xe0 | cc >> 12, 0x80 | cc >> 6 & 0x3f, 0x80 | cc & 0x3f);
            }
        );
    }
    
    function request(url, loaded) {  
        let xmlhttp = new XMLHttpRequest();
        xmlhttp.onreadystatechange = function() {  
            if (xmlhttp.readyState == 4)  
                if (xmlhttp.status == 200)  
                    loaded(xmlhttp);
        }
        xmlhttp.open("GET", url, true);
        xmlhttp.send();
    }
    
    void function(){  
        let source = '../image/test.js';
        request(source, function(xmlhttp){
            let text = encodeUTF8(xmlhttp.responseText);
            let pixel = Math.ceil((text.length + 2) / 3); // 1一个像素存3个字节,  
            let size = Math.ceil(Math.sqrt(pixel));
            //console.log([text.length, pixel, size, size * size * 3]);
            let canvas = document.createElement('canvas');
            canvas.width = canvas.height = size;
            let context = canvas.getContext("2d"),  
                imageData = context.getImageData(0, 0, canvas.width, canvas.height),  
                pixels = imageData.data;
            for(let i = 0, j = 0, l = pixels.length; i < l; i++){  
                if (i % 4 == 3) { // alpha会影响png还原
                    pixels[i] = 255;
                    continue;
                }
                let code = text.charCodeAt(j++);
                if (isNaN(code)) break;
                pixels[i] = code;
            }
            context.putImageData(imageData, 0, 0);
            document.getElementById('base64').src = canvas.toDataURL("image/png");
        });
    }();

    编码后的图片: 
    这里写图片描述

    解码

    1、加载png; 
    2、将png原尺寸绘制到canvas中; 
    3、读取像素中的字符串; 
    4、生成相应协议的data url使用。

    void function(){  
        let source = '../image/test.png';
        let img = document.createElement('img');
        img.onload = function(){  
            let canvas = document.createElement('canvas');
            canvas.width = img.width;
            canvas.height = img.height;
    
            let context = canvas.getContext("2d");
            context.drawImage(img, 0, 0);
            let imageData = context.getImageData(0, 0, canvas.width, canvas.height),  
                pixels = imageData.data;
    
            let script = document.createElement('script');
            let buffer = [];
            for (let i = 0, l = pixels.length; i < l; i++) {  
                if (i % 4 == 3) continue; // alpha会影响png还原  
                if (!pixels[i]) break;
                buffer.push(String.fromCharCode(pixels[i]));
            }
            script.src = 'data:text/javascript;charset=utf-8,' + encodeURIComponent(buffer.join(''));
            document.body.appendChild(script);
            script.onload = function(){  
                console.log('script is loaded!');
            }
            img = null;
        }
        img.src = source;
    }();

    这里需要手动下载编码后的图片,我没有写自动下载的函数,这又是另一个可以深入探讨的问题了,所以不过多扩展。

    2.2 css

    使用content就简单多啦。

    let div = document.getElementById('content');
    let content = window.getComputedStyle(div, ':before').content;

    只需要和上面代码一样,新建一个srcript标签,利用data协议,就可以执行content内保存的js代码啦。

    3. 防止代码执行被截获

    • 截获 eval() / new Function() 的示例代码
    eval = function() {
      console.log('eval', JSON.stringify(arguments));
    };
    
    eval('console.log("Hello world!")');
    
    Function = function() {
      console.log('Function', JSON.stringify(arguments));
      return function() {};
    };
    
    new Function('console.log("Hello world!")')();

    但是可能不是全局使用:

    (function(){}).constructor('console.log("Hello world!")')()
    • 截获 constructor 的示例代码
    Function.prototype.__defineGetter__('constructor', function () {
        return function () {
            console.log('constructor', JSON.stringify(arguments));
        };
    });
    (function() {}).constructor('console.log("Hello world!")');

    目前能想到的是判断 eval 是否被重定向

    示例,如果 eval 被重定向 z 变量不会被泄露

    (function(x){
        var z = 'console.log("Hello world!")';
        eval('function x(){eval(z)}');
        x();
    })(function() { /* ... */ });

    uglify介绍

    概述:

    
    
    • 案例:Cesium打包流程,相关技术点和大概流程

    • 原理:代码优化的意义:压缩 优化 混淆

    • 优化:如何完善Cesium打包流程

    
    

    关键字:Cesium gulp uglifyjs

    
    

    字数:2330 | 阅读时间:7min+

    
    

     

    
    

    1 Cesium打包流程

    
    

           如果没有记错,Cesium从2016年初对代码构建工具做了一次调整,从grunt改为gulp。作为一名业余选手,就不揣测两者的差别了。个人而言,gulp和Ant的思路很相似,通过管道连接,都是基于流的构建风格,而且gulp更像是JS的编码风格,自带一种亲切感。

    
    

    gulp.task('task1',['task0'], function() {

        return fun_task1();

    });

    
    

           Task语句是gulp中最常见的,懂了这句话,就等于你看懂脚本了。这句话的意思是,要执行task1,需要先执行task0,而task1的具体工作都在fun_task1方法中。这就是之前说的基于流的构建风格。有了这句话,在命令行中键入:gulp task1,回车执行该指令即可。

    
    

           先安装Node,环境变量等,并安装npm包后,即可使用gulp打包工具,这里推荐cnpm。环境搭建好后,命令行中键入gulp minify开始打包。完整的过程是build->generateStubs->minify。

    
    

    1gulp

    
    

    Cesium打包流程

    
    

           build:准备工作,创建Build文件夹;将glsl文件转为js形式;最主要的是createCesiumJs方法,遍历Source中所有js脚本,将所有Object记录到Source/Cesium.js;其他的是范例,单元测试相关模块。

    
    

           generateStubs:用于单元测试,略。

    
    

           minify; 首先combineJavaScript主要做了两件事情,打包Cesium和Workers脚本,这是打包的最终结果。Gulp根据指令的不同,比如minify下采用uglify2优化,而combine对应的参数为none,生成路径为CesiumUnminified。

    
    

           另外,细心的人会发现,combineCesium的实现中有这样一句话path.relative('Source',require.resolve('almond')),这是一个小优化,almond是requirejs的精简包,因此,最终的Cesium.js中包含'almond脚本,内置了requirejs的主要方法。

    
    

           如上是Cesium打包的主要流程,简单说主要有3+1类个指令:

    
    
    • Clean

      • 清空文件

    • minify

      • 打包&压缩

    • combine

      • 只打包,不压缩

    • JScoverage

      • 单元测试覆盖率,不了解

    
    

    2 代码优化

    
    

           对流程有了一个大概了解,下面,我们详细了解一下uglify2过程都做了哪些代码优化,一言以蔽之,压缩,优化,混淆。

    
    

           uglify2主要有三个参数:-o,-c,-m,-o参数必选,指定输出文件,-c压缩,-m混淆变量名。如下分别为combine、(uglifyjs -o)、(uglifyjs –c -m -o)的文件对比,单位是k:

    
    

    2file

    
    

    uglify2的压缩对比

    
    

           都在一个屋檐下,差距怎么就这么大呢?我们简单说一下从1~2,2~3之间青取之于蓝而胜于蓝的过程。

    
    

           1~2的过程其实很简单,就是干了三件事,去掉注释, 去掉多余的空格(换行符),去掉不必要的分号(;)。就这三件事情,文件一下子小了一半多,换句话就是平时你写的代码有一大半都是废话,此时你旁边的AI程序员可能会喃喃道来“你们人类好愚蠢~”。

    
    

           2~3则是很多小细节的综合应用:

    
    
    • 去掉一些实际没有调用的函数(Dead code);

    • 将零散的变量声明合并,比如 var a; var b;变为var a,b;

    • 逻辑函数的精简,比如if(a) b(); else c()变为a ? b() : c();

    • 变量名的简化,比如var strObject;变为var s;

    • ……

    
    

           这些小技巧有很多,具体要看不同的压缩工具的考虑优劣,但有些压缩高效的工具并不稳定,可能会破坏语法规范或语意,所以没必要为了几个kb承担过多的风险,目前比较成熟的工具主要有三个uglify2,google closure以及yuicompressor,具体优劣得自己来体会了,我是按照自己的理解给出的先后顺序。最终的效果如下:

    
    

    3uglifyresult

    
    

    Cesium脚本效果

    
    

           这样的代码只能用单位“坨”来形容了,人类是无法直接读懂的,那浏览器能读懂吗?这是一个好问题!如下是V8引擎对JS语法解析的大概流程:

    
    

    4parser

    
    

    V8引擎解析JS脚本

    
    

           下面是在我本机Chrome解析Cesium.js脚本花费时间(脚本从下载完到浏览器解析完的时间差),单位毫秒,因为只测试了一次,可能会有误差,但基本吻合期望值:

    
    

     5parser

    
    

    JS脚本解析时间对比

    
    

           首先因为是本机测试,脚本无论是最大的8M还是最小的2.4M,下载速度都很快,因此我们不讨论(但实际应用中要考虑)脚本下载所需时间。

    
    

    其次,如上图,多了一个source,这是源码情况下,这个时间水分比较大,因为是零散的文件,可以做到按需下载,但因为文件比较琐碎,性能也不高。

    
    

           结论是,这种JS脚本优化策略对浏览器的影响不大,浏览器看到优化后的代码,可能会愣一会神,但很快就克服了。

    
    

    3实战

    
    

           知道了代码优化的大概原理,回顾一下代码优化的目的(压缩,优化,混淆),匹配一下结果是否符合期望值。嗯,其一,脚本的大小小了,其二,代码效率也优化了,其三,别人也看不懂了。似乎该做的都已经做了,这个脚本已经很完美了。

    
    

     6format

    
    

    Format后的效果

    
    

           毛爷爷说,与人斗其乐无穷。确实,前两点的目的达到了,但第三点,还差很多。如上,和刚才的脚本是同一个文件,我只是用Chrome的调试工具format而已。这就是理想和现实之间的差距。

    
    

           可见,Cesium默认打包工具在压缩和优化上都没有问题,但在混淆上并不充分,当然Cesium本身是开源的,也没必要搞这些。客观说,JS脚本是明码的,所以反编译只是时间和能力的问题,所以不妨换个态度来看待这个问题,增加反编译的成本,当该成本大于购买成本即可

     
  • 相关阅读:
    loadrunner—事务、TPS
    loadrunner—集合点rendezvous
    loadrunner—web_submit_data
    阿里云的docker仓库登陆打标签
    linux 下安装git服务器
    解决mvn clean后打包错:Cannot create resource output directory
    eclipse中创建的spring-boot项目在启动时指定加载那一个配置文件的设置
    docker命令
    springboot所有pom依赖
    pthon小游戏
  • 原文地址:https://www.cnblogs.com/horsemoon/p/7111416.html
Copyright © 2011-2022 走看看