你尚未登录,仅允许查看本站部分内容。请登录使用邀请码注册
zswang

移动时代的前端加密 18个回复 专栏 @ 业务

zswang 发布于 2 年前

移动时代的前端加密

标签: 加密 前端 HTML5 移动


背景

相比其他被编译成二进制的应用,前端这种纯文本应用,太容易被解读和窜改。

前端为什么要加密?

加密重要的目的是出于对商业利益的保护。

  • 由于作品太容易被复制窜改,容易会失去渠道先机

窜改不限于以下:

  1. 署名被移除或替换;
  2. 链接地址被替换;
  3. 文案被修改;
  4. 广告被移除、替换或植入;
    ...

一些轻度游戏,用户只会玩一两次,生命周期也就两三天。如果你开发的游戏被人山寨且他的渠道比你更广,那么对于流量就是致命打击。

  • HTML5 被山寨后太廉价

在淘宝上搜索「HTML5 微信小游戏」400套/10元

  • 避免泄露一些用于运营的脚本

参考:锤子手机天猫开卖遇乌龙事件

前端加密的目标

总之就是减少加密的成本增加破解的成本:如果每次花 1 分钟加密的应用,都需要花 2 小时以上去破解那就算成功了。

  • 加密后的文件不易过大;

100K 文件如果加密后到 1M 无疑增加了用户使用的成本和体验。

  • 没有人工介入不能破解;

即:破解的过程需要人工介入,人工成本无疑是最大的开销。

  • 限制在其他域名部署;

守护代码和业务放在一起,部署到其他域名则不能正常使用。

  • 不容易被调试跟踪;

对主流的调试工具有防范能力,如:Firebug、Chrome 开发者工具。

哪些代码不需要加密?

  • 开源项目
  • 用于学习的项目

降低可读性的方法

压缩(compression)

压缩的目的通常是减少传输量,但也取到降低可读性的作用。
去掉注释、多余的分隔符、空白字符、标识符简写。

这类工具有很多:YUI CompressorUglifyJSGoogle Closure Compiler

「标识符简写」是一种压缩也是一种混淆。

混淆(obfuscation)

混淆常见的方法是分离静态资源、打乱控制流、增加无义的代码。

UglifyJSGoogle Closure Compiler 这类工具实际上也会做简单改变语句。

混淆是降低可读性的利器,有一款商业产品 jscrambler,最高配每个月 95 美刀。

  • 标识符混淆

混淆前

function render(obj) {
  /* ... */
  console.log(obj.title);
}
render({title: 'buy'});

混淆后

function a(e){/* ... */console.log(e.title)}a({title:'buy'})
  • 逻辑混淆

混淆前

function render(obj) {
  /* ... */
  console.log(obj.title);
}
render({title: 'buy'});

混淆后

var self=this,o={};o.__defineSetter__('t',function(e){self[t('elosnoc')][t('gol')](e[t('eltit')])});function t(e){return e.split('').reverse().join('')};o[t('eltit')]=t('yub');o.t=o

混淆前

alert("Hello, JavaScript")

混淆后

゚ω゚ノ= /`m´)ノ ~┻━┻   //*´∇`*/ ['_']; o=(゚ー゚)  =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); (゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = ((゚Д゚)+'_') [ (゚ー゚)+(゚ー゚)-(゚Θ゚) ];(゚Д゚) ['o'] = ((゚Д゚)+'_') [゚Θ゚];(゚o゚)=(゚Д゚) ['c']+(゚Д゚) ['o']+(゚ω゚ノ +'_')[゚Θ゚]+ ((゚ω゚ノ==3) +'_') [゚ー゚] + ((゚Д゚) +'_') [(゚ー゚)+(゚ー゚)]+ ((゚ー゚==3) +'_') [゚Θ゚]+((゚ー゚==3) +'_') [(゚ー゚) - (゚Θ゚)]+(゚Д゚) ['c']+((゚Д゚)+'_') [(゚ー゚)+(゚ー゚)]+ (゚Д゚) ['o']+((゚ー゚==3) +'_') [゚Θ゚];(゚Д゚) ['_'] =(o^_^o) [゚o゚] [゚o゚];(゚ε゚)=((゚ー゚==3) +'_') [゚Θ゚]+ (゚Д゚) .゚Д゚ノ+((゚Д゚)+'_') [(゚ー゚) + (゚ー゚)]+((゚ー゚==3) +'_') [o^_^o -゚Θ゚]+((゚ー゚==3) +'_') [゚Θ゚]+ (゚ω゚ノ +'_') [゚Θ゚]; (゚ー゚)+=(゚Θ゚); (゚Д゚)[゚ε゚]='\\'; (゚Д゚).゚Θ゚ノ=(゚Д゚+ ゚ー゚)[o^_^o -(゚Θ゚)];(o゚ー゚o)=(゚ω゚ノ +'_')[c^_^o];(゚Д゚) [゚o゚]='\"';(゚Д゚) ['_'] ( (゚Д゚) ['_'] (゚ε゚+(゚Д゚)[゚o゚]+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚Θ゚)+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚Θ゚)+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) +(o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) - (゚Θ゚))+ (o^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (o^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (゚Θ゚)+ (゚Д゚)[゚o゚]) (゚Θ゚)) ('_');

混淆前

alert("Hello, JavaScript")

混淆后

$=~[];$={___:++$,$$$$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$],_$_:++$,$_$$:({}+"")[$],$$_$:($[$]+"")[$],_$$:++$,$$$_:(!""+"")[$],$__:++$,$_$:++$,$$__:({}+"")[$],$$_:++$,$$$:++$,$___:++$,$__$:++$};$.$_=($.$_=$+"")[$.$_$]+($._$=$.$_[$.__$])+($.$$=($.$+"")[$.__$])+((!$)+"")[$._$$]+($.__=$.$_[$.$$_])+($.$=(!""+"")[$.__$])+($._=(!""+"")[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$$=$.$+(!""+"")[$._$$]+$.__+$._+$.$+$.$$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$$+"\""+$.$_$_+(![]+"")[$._$_]+$.$$$_+"\\"+$.__$+$.$$_+$._$_+$.__+"(\\\"\\"+$.__$+$.__$+$.___+$.$$$_+(![]+"")[$._$_]+(![]+"")[$._$_]+$._$+",\\"+$.$__+$.___+"\\"+$.__$+$.__$+$._$_+$.$_$_+"\\"+$.__$+$.$$_+$.$$_+$.$_$_+"\\"+$.__$+$._$_+$._$$+$.$$__+"\\"+$.__$+$.$$_+$._$_+"\\"+$.__$+$.$_$+$.__$+"\\"+$.__$+$.$$_+$.___+$.__+"\\\"\\"+$.$__+$.___+")"+"\"")())();

加密(encryption)

这里「加密」指代码内容可逆编码。而文中「前端加密」指页面和相关资源文件处理后能正常运行。

  • 简单 base64

加密前

function a(e){/* ... */console.log(e.title)}a({title:'buy'})

加密后

eval(atob("ZnVuY3Rpb24gYShlKXsvKiAuLi4gKi9jb25zb2xlLmxvZyhlLnRpdGxlKX1hKHt0aXRsZTonYnV5J30p"));
  • Packer

加密前

function a(e){/* ... */console.log(e.title)}a({title:'buy'})

加密后

eval(function(p,a,c,k,e,r){e=String;if(!''.replace(/^/,String)){while(c--)r[c]=k[c]||c;k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('3 0(1){4.5(1.2)}0({2:\'6\'})',7,7,'a|e|title|function|console|log|buy'.split('|'),0,{}))

新技术带来的新思路

到移动时代我们可以放心的用上 HTML5、CSS3,使用一些新特性结合已有的方案,可以更大程度增加破解的成本。

代码可以放置其他位置

将代码放到非 JS 文件中,增加代码定位的难度。

  • 放到 png 中

利用 HTML Canvas 2D Context 获取二进制数据的特性,可以用图片来存储脚本资源。

  • 放到 css 文件中

利用 content 样式能存放字符串的特性,同样可以用来存储脚本资源。

执行代码字符串

无论代码放到哪,都需要执行。执行代码字符串的方式有如下几种:

  • 创建 <script> 执行
var script = document.createElement('script');
script.src = 'data:application/javascript,console.log("Hello%20world!"))';
document.querySelector('script').parentNode.appendChild(script);
  • 调用 setTimeout() / setInterval() 执行
setTimeout('console.log("Hello world!")', 0);
  • 创建 new Function() 执行
new Function('console.log("Hello world!")')();
  • 使用 Worker 执行
var URL = window.URL || window.webkitURL;
var Blob = window.Blob || window.webkitBlob;
var blobURL = URL.createObjectURL(
    new Blob(['console.log("Hello World!")'], {type: 'application/javascript'})
);
new Worker(blobURL);
URL.revokeObjectURL(blobURL);
  • 使用 DOM 事件执行
var div = document.createElement('div');
div.innerHTML = "<img src=! onerror=\"console.log('Hello world!')\">";
  • location 赋值 javascript 协议的链接
location = 'javascript:console.log("Hello world!");';

如何防止代码执行被截获

比想象的难太多

  • 截获 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!")')();
  • 还以为用字面量就妥妥了。o(╯□╰)o 后来发现是杯具的
(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() { /* ... */ });

防止开发者工具

再复杂的前端加密也难对付调试工具的跟踪分析。

while(1){} // 卡死

混合加密

单个方法总是容易被破解,但组合起来千变万化就不那么容易了!破解成本显然指数增长。

  • 嵌套加密
「C 方法加密
  「A 方法加密
   ...
   」
   ...
  「B 方法加密
   ...
   」
 」
  • 随机加密
「随机方法加密
  「随机方法加密
   ...
   」
   ...
  「随机方法加密
   ...
   」
 」

实际案例

这里要安利两下:

  • 我和小伙伴们开发的剪纸游戏 天天爱剪纸,用到了混合加密,你可以尝试破解挑战一下。(已被 @前端农民工 扒了)

  • 独立开发的代码预处理工具 jdists,非常适合用来做混合加密。

参考:jdists 混合加密示例

更有力的防范

很难做到 100% 防止逆向工程,只是增加一些破解的成本。

使用专属素材

修改素材也就是要做同一套素材,这个其实不比修改代码容易到哪去。

不是单一的前端应用,依赖服务端的存储

前端容易破解,后端却不容易,物理上就隔离了。

更好的产品和服务,更快的迭代

再怎么山寨也山寨不了精髓。

参考资料

  • jsw0528

    提示说不登录只能看部分内容,然后我登录了,可是内容居然没变化。。。

    #1
  • freedom_melody

    3.2防止开发者工具
    这个很赞
    我记得QQ某个新闻阅读模块就做了这个限制。

    #2
  • 前端农民工

    我想说:

    前端根本就没有秘密,这些都是然并卵。。。

    以天天爱剪纸为例,第一层混淆是aaencode,这类混淆都是利用了构造了基本ascii字符实现的函数拼装,最后都要通过eval/Function来执行函数,所以,只要拦截就行了,aaencode混淆之后,前面几行都在构造这些东西,简单看过之后发现最终执行代码利用的是 Number.prototype.constructor.constructor 来获取Function函数最终执行,所以,只要拦截这个就行了:

    Number.prototype.constructor.constructor = function(a){
        console.log(a);
        return Function.apply(null, arguments);
    }
    

    第一重解密得到函数:

    window._host = localStorage._host || document.location.host;
    剪=~[];剪={___:++剪,$$$$:(![]+"")[剪],__$:++剪,$_$_:(![]+"")[剪],_$_:++剪,$_$$:({}+"")[剪],$$_$:(剪[剪]+"")[剪],_$$:++剪,$$$_:(!""+"")[剪],$__:++剪,$_$:++剪,$$__:({}+"")[剪],$$_:++剪,$$$:++剪,$___:++剪,$__$:++剪};剪.$_=(剪.$_=剪+"")[剪.$_$]+(剪._$=剪.$_[剪.__$])+(剪.$$=(剪.$+"")[剪.__$])+((!剪)+"")[剪._$$]+(剪.__=剪.$_[剪.$$_])+(剪.$=(!""+"")[剪.__$])+(剪._=(!""+"")[剪._$_])+剪.$_[剪.$_$]+剪.__+剪._$+剪.$;剪.$$=剪.$+(!""+"")[剪._$$]+剪.__+剪._+剪.$+剪.$$;剪.$=(剪.___)[剪.$_][剪.$_];剪.$(剪.$(剪.$$+"\""+"\\"+剪.__$+剪._$_+"\\"+剪.__$+剪.$$_+剪.$$$+"\\"+剪.__$+剪.$_$+剪.__$+"\\"+剪.__$+剪.$_$+剪.$$_+剪.$$_$+剪._$+"\\"+剪.__$+剪.$$_+剪.$$$+"._"+剪.$$_$+"\\"+剪.__$+剪.$_$+剪.__$+剪.$$__+剪.__+"\\"+剪.$__+剪.___+"=\\"+剪.$__+剪.___+"\\"+剪.__$+剪.$_$+剪._$_+"\\"+剪.__$+剪.$_$+剪.$_$+剪.$$_$+剪.$_$+"\\"+剪.__$+剪.$$_+剪._$$+"."+剪.$$$_+"\\"+剪.__$+剪.$_$+剪.$$_+剪.$$__+剪._$+剪.$$_$+剪.$$$_+"(\\"+剪.__$+剪.$$_+剪.$$$+"\\"+剪.__$+剪.$_$+剪.__$+"\\"+剪.__$+剪.$_$+剪.$$_+剪.$$_$+剪._$+"\\"+剪.__$+剪.$$_+剪.$$$+"._\\"+剪.__$+剪.$_$+剪.___+剪._$+"\\"+剪.__$+剪.$$_+剪._$$+剪.__+");\\"+剪.__$+剪._$_+"\"")())();
    if(!/MicroMessenger/.test(navigator.userAgent)&&String(location+location.flag).indexOf("zswang")<0){var el=document.createElement("div");el.__defineGetter__("id",function(){clearInterval(timer),setTimeout(function(){for(;;);},10)});var timer=setInterval(function(){console.log(el),console.clear()},5e3)}$(".title").css("content").replace(/[\w+=\/]+/,function(e){var t=window._dict;(function(){}).constructor(atob(e).split("").map(function(e,n){return String.fromCharCode(e.charCodeAt()^t[n%t.length])}).join(""))()}),delete window._dict,delete window._host;
    

    这里显然又用了jjencode混淆,有简单看了一下,发现jjencode跟aaencode完全一个套路,同样的代码又能破解这个混淆,最终得到:

    window._host = localStorage._host || document.location.host;
    window._dict = jmd5s.encode(window._host);
    if (!/MicroMessenger/.test(navigator.userAgent) && String(location + location.flag).indexOf("zswang") < 0) {
        var el = document.createElement("div");
        el.__defineGetter__("id", function() {
            clearInterval(timer), setTimeout(function() {
                for (;;);
            }, 10)
        });
        var timer = setInterval(function() {
            console.log(el), console.clear()
        }, 5e3)
    }
    $(".title").css("content").replace(/[\w+=\/]+/, function(e) {
        var t = window._dict;
        (function() {}).constructor(atob(e).split("").map(function(e, n) {
            return String.fromCharCode(e.charCodeAt() ^ t[n % t.length])
        }).join(""))()
    }), delete window._dict, delete window._host;
    

    混入了防止打开控制台的代码,开启fiddler,拦截页面请求,替换已破解的部分的js内容,然后删除防止打开控制台的代码,拦截解码后的代码内容,就非常容易破解了:

    window._host = localStorage._host || document.location.host;
    window._dict = jmd5s.encode(window._host);
    // if (!/MicroMessenger/.test(navigator.userAgent) && String(location + location.flag).indexOf("zswang") < 0) {
    //     var el = document.createElement("div");
    //     el.__defineGetter__("id", function() {
    //         clearInterval(timer), setTimeout(function() {
    //             for (;;);
    //         }, 10)
    //     });
    //     var timer = setInterval(function() {
    //         console.log(el), console.clear()
    //     }, 5e3)
    // }
    $(".title").css("content").replace(/[\w+=\/]+/, function(e) {
        var t = window._dict;
        var code = atob(e).split("").map(function(e, n) {
            return String.fromCharCode(e.charCodeAt() ^ t[n % t.length])
        }).join("");
    
        console.log(code); //→ 这里捉到了代码
    
        (function() {}).constructor(code)()
    }), delete window._dict, delete window._host;
    
    #3
  • 前端农民工

    破解结果:

    (function() {
        function format(template, json) {
            return template.replace(/#\{(.*?)\}/g, function(all, key) {
                return json && (key in json) ? json[key] : "";
            });
        }
        var colors = [
            '#E82C4B', '#2C97E8', '#20AF74', '#F39D1C', '#F95E1A', 'black'
        ];
        var currentColor = colors[parseInt(Math.random() * colors.length)];
        var edgesList = [
            5, 6, 7, 8
        ];
        var currentEdges = edgesList[parseInt(Math.random() * edgesList.length)];
        var currentColor = colors[parseInt(Math.random() * colors.length)];
        var history = [];
        var updating;
        var game = jcuts.createGame({
            center: [200, 550],
            radius: 500,
            edges: currentEdges,
            fill: currentColor,
            stroke: 'none',
            container: '.game',
            onchange: function() {
                if (!updating) {
                    history.unshift(game.getData());
                }
                previewChange();
            }
        });
        var originShape;
        function rebuild() {
            originShape = game.getShape();
            originShape = JSON.stringify({
                base: originShape.base,
                edges: originShape.edges,
                polygon: originShape.polygon
            });
        }
        var preview = jcuts.createRender({
            container: '.preview',
            stroke: 'none'
        });
        var storage = localStorage['cutpaper-data'];
        if (storage) {
            try {
                var data = JSON.parse(storage);
                if (data.fill !== 'none') {
                    currentColor = data.fill || currentColor;
                }
                currentEdges = data.edges || currentEdges;
                game.setAttributes({
                    edges: currentEdges
                });
                rebuild();
                game.setData(data);
            }
            catch (ex) {
                alert(ex.message);
            }
        } else {
            rebuild();
        }
        $('.colors').html(colors.map(function(color) {
            return format('<li data-color="#{color}" #{className} style="background: #{color}"></li>', {
                color: color,
                className: color === currentColor ? 'class = "active"' : ''
            });
        }).join('\n'));
        $('.edges').html(edgesList.map(function(edges) {
            return format('<li data-edges="#{edges}" #{className}">#{edges}</li>', {
                edges: edges,
                className: edges === currentEdges ? 'class = "active"' : ''
            });
        }).join('\n'));
        var shape;
        var touchTimer;
        function previewChange() {
            var data = game.getData();
            shape = game.getShape();
            preview.setAttributes({
                fill: data.fill
            });
            preview.render(shape);
            localStorage['cutpaper-data'] = JSON.stringify(data);
            if (history.length > 1) {
                $('.undo, .undo-hint').show();
            }
            else {
                $('.undo, .undo-hint').hide();
            }
            if (originShape === JSON.stringify({
                base: shape.base,
                edges: shape.edges,
                polygon: shape.polygon
            })) {
                if (touchTimer) {
                    clearTimeout(touchTimer);
                }
                touchTimer = setTimeout(function() {
                    touchTimer = null;
                    $('.touch').show()
                }, 3000);
                $('.save, .save-hint').hide();
                $('.delete, .delete-hint').hide();
            }
            else {
                if (touchTimer) {
                    clearTimeout(touchTimer);
                    touchTimer = null;
                }
                $('.touch').hide();
                $('.save, .save-hint').show();
                $('.delete, .delete-hint').show();
            }
        }
        previewChange();
        function deleteHandler() {
            history = [];
            game.replay();
        }
        new Hammer(document.querySelector('.delete')).on('tap', deleteHandler);
        new Hammer(document.querySelector('.delete-hint')).on('tap', deleteHandler);
        function kata() {
            var canvas = document.createElement('canvas');
            var context = canvas.getContext('2d');
            canvas.height = 300;
            canvas.width = 300;
            var shareIcon = jcuts.createRender({
                container: canvas,
                fill: currentColor,
                backgrund: 'white',
                stroke: 'none'
            });
            shape.fill = currentColor;
            shape.polygon = shape.polygon.map(function(item) {
                return [+item[0].toFixed(2), +item[1].toFixed(2)];
            });
            shareIcon.render(shape);
            return canvas.toDataURL();
        }
        new Hammer(document.querySelector('.colors')).on('tap', function(e) {
            if (!e.srcEvent) {
                return;
            }
            if (!e.srcEvent.target) {
                return;
            }
            var color = e.srcEvent.target.getAttribute('data-color');
            if (!color) {
                return;
            }
            currentColor = color;
            $('.colors li').removeClass('active');
            $(e.srcEvent.target).addClass('active');
            game.setAttributes({
                fill: currentColor
            });
            preview.setAttributes({
                fill: currentColor
            });
            previewChange();
        });
        new Hammer(document.querySelector('.edges')).on('tap', function(e) {
            if (!e.srcEvent) {
                return;
            }
            if (!e.srcEvent.target) {
                return;
            }
            var edges = e.srcEvent.target.getAttribute('data-edges');
            if (!edges) {
                return;
            }
            currentEdges = parseInt(edges);
            $('.edges li').removeClass('active');
            $(e.srcEvent.target).addClass('active');
            history = [];
            game.setAttributes({
                edges: currentEdges
            });
            rebuild();
            previewChange();
        });
        new Hammer(document.querySelector('.ranking')).on('tap', function(e) {
            location = 'ranking.html';
        });
        new Hammer(document.querySelector('.ranking-hint')).on('tap', function(e) {
            location = 'ranking.html';
        });
        function undoHandler(e) {
            if (history.length > 1) {
                history.shift();
                updating = true;
                game.setData(history[0]);
                game.setAttributes({
                    fill: currentColor
                });
                updating = false;
            }
        }
        new Hammer(document.querySelector('.undo')).on('tap', undoHandler);
        new Hammer(document.querySelector('.undo-hint')).on('tap', undoHandler);
        function saveHandler(e) {
            $('.mask').show();
            var dataURL = kata();
            var md5 = jmd5s.encode(dataURL);
    
            $.ajax({
                type: 'POST',
                url: '/kata/',
                data: {
                    dataURL: dataURL,
                    shape: JSON.stringify(shape),
                    md5: md5
                },
                dataType: 'json',
                success: function(data) {
                    if (data === null) {
                        alert('post error.');
                        $('.mask').hide();
                        return;
                    }
                    if (data.error) {
                        alert(data.error);
                        $('.mask').hide();
                        return;
                    }
                    setTimeout(function() {
                        $('.mask').hide();
                        location = 'preview.html?md5=' + md5;
                    }, 1500);
                },
                error: function() {
                    alert('network error.');
                    $('.mask').hide();
                }
            });
        }
        new Hammer(document.querySelector('.save')).on('tap', saveHandler);
        new Hammer(document.querySelector('.save-hint')).on('tap', saveHandler);
    })();
    
    #4
  • 小小小无路

    ....直接二进制吧。。。

    #5
  • 前端农民工

    二进制也没用,任何地方都能fiddler替换拦截,二进制总有转成js源码的那一刻,找到之后console.log一下就好了

    #6
  • 剧中人

    前端坑前端,相煎何太急!

    #7
  • zswang

    赞,瓶神。

    #8
  • 前端农民工

    还有一种混淆方案,就是在同构语法的基础上提出所有key值到闭包的参数中,破坏代码的可读性,这种方案相对来说逆向的成本高一些:

    比如代码:

    var a = document.getElementById('a');
    a.innerHTML = 'adsf';
    

    混淆之后是:

    (function(a,b,c,d,e,f){
        var g=a[b][c](d);
        g[e]=f
    })(window,'document', 'getElementById', 'a', 'innerHTML', 'adsf');
    

    在线展示:http://www.ijinshan.com/game/flappybird/js/main.js

    #9
  • 前端-小强

    那以后看人家写的代码!都欣赏不到了啊!

    #10
  • 幾米

    @前端农民工 nice fuck,顺便马赛个克

    #11
  • JS-SHENG

    如果都要加密的话,那github可以下线了

    #12
  • rambo

    FE坑FE,相煎何太急!

    牺牲可维护性,牺牲性能,换来这价值并不是太大的保密。有价值吗?

    你线上出现问题,恰好你又不在公司,这时候,是死呢?还是哭呢?

    真正的防护,是尽可能的从根本上防,这种只是多走几步路的防护,根本没什么意义吧

    我现在的做法,UglifyJS 压缩后,保留一份.map文件。 就足矣了。

    前端保密,至少对大多数项目来说,真的还不到那个地步!

    by the way: 想起google大会上一哥们说的一句话, 前端有秘密吗? 如果需要前端加密,是不是证明你们走错路了??

    #13
  • thonatos

    哈哈哈哈哈,想起那天研究某公司的一个插件,
    混淆啊,转换啊,base64啊...

    然并卵,console大法好...

    #14
  • clq_web

    源码始终是下载到客户端才能执行,这点逃不过

    #15
  • zswang

    是的没有任何一种方案是能绝对防止破解的。

    这些加密手段的作用也只是增加「逆向的成本」。

    前端加密并非没有实际意义,就像家里装个防盗门碰到专业开锁的人,五分钟就开了。但只要防住 99% 的人,安全系数就提高很多。

    #16
  • ro87630872

    @前端农民工 nice fuck, 这枪打得够黑!!

    #17
  • tabooc

    看来只能防住我

    #18
登录后回复,如无账号,请使用邀请码注册