移动时代的前端加密
标签: 加密 前端 HTML5 移动
相比其他被编译成二进制的应用,前端这种纯文本应用,太容易被解读和窜改。
加密重要的目的是出于对商业利益的保护。
窜改不限于以下:
一些轻度游戏,用户只会玩一两次,生命周期也就两三天。如果你开发的游戏被人山寨且他的渠道比你更广,那么对于流量就是致命打击。
在淘宝上搜索「HTML5 微信小游戏」400套/10元
参考: 锤子手机天猫开卖遇乌龙事件
总之就是减少加密的成本增加破解的成本:如果每次花 1 分钟加密的应用,都需要花 2 小时以上去破解那就算成功了。
100K 文件如果加密后到 1M 无疑增加了用户使用的成本和体验。
即:破解的过程需要人工介入,人工成本无疑是最大的开销。
守护代码和业务放在一起,部署到其他域名则不能正常使用。
对主流的调试工具有防范能力,如:Firebug、Chrome 开发者工具。
压缩的目的通常是减少传输量,但也取到降低可读性的作用。去掉注释、多余的分隔符、空白字符、标识符简写。
这类工具有很多: YUI Compressor 、 UglifyJS 、 Google Closure Compiler
「标识符简写」是一种压缩也是一种混淆。
混淆常见的方法是分离静态资源、打乱控制流、增加无义的代码。
UglifyJS 和 Google 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")
混淆后
$=~[];$={___:++$,$$$$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$],_$_:++$,$_$$:({}+"")[$],$$_$:($[$]+"")[$],_$$:++$,$$$_:(!""+"")[$],$__:++$,$_$:++$,$$__:({}+"")[$],$$_:++$,$$$:++$,$___:++$,$__$:++$};$.$_=($.$_=$+"")[$.$_$]+($._$=$.$_[$.__$])+($.$$=($.$+"")[$.__$])+((!$)+"")[$._$$]+($.__=$.$_[$.$$_])+($.$=(!""+"")[$.__$])+($._=(!""+"")[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$$=$.$+(!""+"")[$._$$]+$.__+$._+$.$+$.$$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$$+"/""+$.$_$_+(![]+"")[$._$_]+$.$$$_+"//"+$.__$+$.$$_+$._$_+$.__+"(///"//"+$.__$+$.__$+$.___+$.$$$_+(![]+"")[$._$_]+(![]+"")[$._$_]+$._$+",//"+$.$__+$.___+"//"+$.__$+$.__$+$._$_+$.$_$_+"//"+$.__$+$.$$_+$.$$_+$.$_$_+"//"+$.__$+$._$_+$._$$+$.$$__+"//"+$.__$+$.$$_+$._$_+"//"+$.__$+$.$_$+$.__$+"//"+$.__$+$.$$_+$.___+$.__+"///"//"+$.$__+$.___+")"+"/"")())();
这里「加密」指代码内容可逆编码。而文中「前端加密」指页面和相关资源文件处理后能正常运行。
加密前
function a(e){/* ... */console.log(e.title)}a({title:'buy'})
加密后
eval(atob("ZnVuY3Rpb24gYShlKXsvKiAuLi4gKi9jb25zb2xlLmxvZyhlLnRpdGxlKX1hKHt0aXRsZTonYnV5J30p"));
加密前
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 文件中,增加代码定位的难度。
利用 HTML Canvas 2D Context 获取二进制数据的特性,可以用图片来存储脚本资源。
利用 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);
var div = document.createElement('div'); div.innerHTML = "<img src=! onerror=/"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!")')();
用字面量就妥妥的,不能被截获。
(function(){}).constructor('console.log("Hello world!")')()
再复杂的前端加密也难对付调试工具的跟踪分析。
那么如何判断浏览器是否开启控制台?
这个问题由@EtherDream 提供了目前最完美的解决方案。
详情参考: Find out whether Chrome console is open?
判断如果控制台开启则堵塞 Javascript 执行
while(1){} // 卡死
单个方法总是容易被破解,但组合起来千变万化就不那么容易了!破解成本显然指数增长。
「C 方法加密 「A 方法加密 ... 」 ... 「B 方法加密 ... 」 」
「随机方法加密 「随机方法加密 ... 」 ... 「随机方法加密 ... 」 」
这里要安利两下:
我和小伙伴们开发的剪纸游戏 天天爱剪纸 ,用到了混合加密,你可以尝试破解挑战一下。
独立开发的代码预处理工具 jdists ,非常适合用来做混合加密。
参考: jdists 混合加密示例
很难做到 100% 防止逆向工程,只是增加一些破解的成本。
修改素材也就是要做同一套素材,这个其实不比修改代码容易到哪去。
前端容易破解,后端却不容易,物理上就隔离了。
再怎么山寨也山寨不了精髓。