⚠️ SecurityWorker核心已开源
⚠️ SecurityWorker不再维护,你可以选择更好的代替方案 sablejs。
SecurityWorker提供完全隐匿且兼容ECMAScript 5.1的类WebWorker的安全可信环境,帮助保护你的核心Javascript代码不被破解。 SecurityWorker不同于普通的Javascript代码混淆,我们使用 独立Javascript VM + 二进制混淆opcode核心执行 的方式防止您的代码被开发者工具调试、代码反向。
- 完整的ECMAScript 5.1标准兼容性
- 极小的SecruityWorker VM文件体积(~160kb)
- 保密性极强,执行逻辑及核心算法完全隐匿不可逆
- 可选择支持多种环境,Browser/NodeJS/小程序(默认不允许NodeJS黑盒运行)
- 良好的浏览器兼容性,主流浏览器全覆盖
- 易于使用,API兼容WebWorker(不允许访问DOM/BOM)
- 易于调试,被保护代码不做混淆,报错信息准确
- IE11
- Chrome 24+
- Safari 6.2+
- Firefox 16+
- Edge 15+
- Android 4.4.2+
- iOS Safari 8+
- iOS WebView 9+
我们以一个Ping-Pong的示例程序讲解SecurityWorker的基本使用,首先我们创建sw.js用于实现SecurityWorker VM的内部业务逻辑:
// sw.js onmessage = function(data) { if(data === 'ping'){ console.log('SecurityWorker recv: ' + data); postMessage('pong'); } };
接着我们在使用bin文件对sw.js进行编译得到保护文件loader.js:
> npm run compile ${youfile}
然后我们创建index.html加载loader.js并完成调用逻辑:
<html> <head> <!--编译sw.js得到保护文件loader.js--> <script src="loader.js"></script> </head> <body> <!--.....--> <script> // 等待SecurityWorker初始化完成 SecurityWorker.ready(function() { var sw = new SecurityWorker(); // SecurityWorker正常创建,可被使用 sw.oncreate = function() { console.log('ready'); sw.postMessage('ping'); }; // 获取SecurityWorker传递的数据 sw.onmessage = function(data) { console.log('MainThread recv: ' + data); sw.terminate(); }; // SecurityWorker销毁事件 sw.onterminate = function() { console.log('terminate'); }; }); </script> </body> </html>
最后我们访问index.html即可看到控制台完整打印出如下结果:
> ready > [LOG] SecurityWorker recv: ping > MainThread recv: pong > terminate
恭喜你已经完全掌握SecurityWorker的使用了,进一步查看SecurityWorker和SecurityWorker VM提供的API帮助你更好的运用SecurityWorker。
设定SecurityWorker VM执行的环境,有3个选择:
- SecurityWorker.AUTO_MODE: 自动选择运行于WebWorker还是浏览器主线程中,推荐的默认值
- SecurityWorker.WORKER_THREAD_MODE: 强制运行于WebWorker环境中,由于某些浏览器的WebWorker环境有一些兼容性问题,因此对于需要兼容性的应用不推荐此选项
- SecurityWorker.MAIN_THREAD_MODE: 强制运行于浏览器主线程中,兼容性很好,但是对于较老的设备可能会导致页面较长时间无响应
SecurityWorker构造函数,用于创建一个SecurityWorker实例。但请注意,实例的创建需要在SecurityWorker.ready调用后进行创建,否则将有几率导致SecurityWorker VM内存分配失败。
SecurityWorker.ready(function(){ var sw = new SecurityWorker(); // ...... });
SecurityWorker已经初始化完毕,可以安全的进行SecurityWorker实例的创建。
SecurityWorker.ready(function(){ // ...... });
发送消息给SecurityWorker VM对象。
var sw = new SecurityWorker(); sw.oncreate = function() { sw.postMessage("Hello World!"); sw.postMessage({ now: Date.now() }) }
销毁SecurityWorker实例和SecurityWorker VM对象,此后将无法进行消息发送。
var sw = new SecurityWorker(); sw.oncreate = function() { sw.terminate(); sw.postMessage(); // error, can't postMessage after terminate }
SecurityWorker实例创建成功的回调,此后可以安全的与SecurityWorker VM进行数据通信。
var sw = new SecurityWorker(); sw.oncreate = function() { console.log('ready') }
接收到SecurityWorker VM传递的相关数据的回调。
var sw = new SecurityWorker(); sw.onmessage = function(data) { if(typeof data == 'string') { console.log(data); }else if(typeof data == 'object') { console.log(JSON.stringify(data)); } }
SecurityWorker实例及SecurityWorker VM已经成功销毁的回调,此后将不能再进行postMessage的调用。
var sw = new SecurityWorker(); sw.onterminate = function() { sw.postMessage(); // error, can't postMessage after terminate }
以下所有API只能在SecurityWorker VM中使用,外部环境的SecurityWorker实例及原型并未提供此类API。
获取SecurityWorker实例发送的相关数据
onmessage = function(data) { if(typeof data == 'string') { console.log(data); }else if(typeof data == 'object') { console.log(JSON.stringify(data); } } // or use self self.onmessage = function() { // something to do... }
发送数据给SecurityWorker实例。
postMessage('Hello World!'); postMessage({ now: Date.now() });
对字符串进行Base64编码。
var b64 = btoa('Hello World!'); console.log(b64); // SGVsbG8gV29ybGQh
对用Base64编码过的字符串进行解码。
var str = atob('SGVsbG8gV29ybGQh'); console.log(str); // 'Hello World!'
延迟执行函数
setTimeout(function(){ console.log('Hello World!'); }, 1000);
循环执行函数(注意:setInterval内部实现采用setTimeout)
setInterval(function(){ console.log('Hello World!'); }, 1000);
SecurityWorker VM支持如下的Console函数:
- console.log
- console.info
- console.debug
- console.error
- console.time
- console.timeEnd
发送Ajax请求,其接收一个对象所含参数为:
- String uri: 请求地址
- String method: 请求方法,可使用GET/POST/DELETE/HEAD/PUT,默认GET
- String body: POST/DELETE/PUT的请求参数,可选
- Object headers: 附加的HTTP Header信息, 可选
- Function success: 请求成功的回调,可选
- Function error: 请求失败的回调,可选
// GET请求 request({ uri: 'http://www.baidu.com', method: 'GET', headers: { 'X-AUTH': 'THIS IS YOUR AUTH KEY' }, success: function(data){ console.log("status: " + data.status); console.log("statusText: " + data.statusText); console.log("text: " + data.text); }, error: function(err){ console.log(err); } }); // POST请求 request({ uri: 'http://www.baidu.com', method: 'POST', body: JSON.stringify({id: 1}), success: function(data){ console.log("status: " + data.status); console.log("statusText: " + data.statusText); console.log("text: " + data.text); }, error: function(err){ console.log(err); } });
WebSocket构造函数。
var ws = new WebSocket('wss://www.baidu.com');
向服务器发送字符串或二进制数据。
var ws = new WebSocket('wss://www.baidu.com'); ws.addEventListener('open', function(){ ws.send('Hello World!'); ws.send(new Uint8Array([1,2,3])); });
关闭WebSocket连接。
var ws = new WebSocket('wss://www.baidu.com'); ws.close();
支持4种标准事件:
- open: 连接打开
- message: 获得服务器发送的数据
- error: 发生相关错误
- close: 连接关闭
var ws = new WebSocket('wss://www.baidu.com'); ws.addEventListener('open', function(){ ws.send('ready'); }); ws.addEventListener('message', function(message){ if(message === 'close'){ ws.close(); } }); ws.addEventListener('error', function(error){ console.log(error); }); ws.addEventListener('close', function(){ // 不需要进行removeEventListener操作, // 当调用close事件后自动解绑所有事件回调 console.log('ws client open') });
支持4种标准事件:
- open: 连接打开
- message: 获得服务器发送的数据
- error: 发生相关错误
- close: 连接关闭
var ws = new WebSocket('wss://www.baidu.com'); ws.addEventListener('open', function(){ ws.send('ready'); }); function onmessage(message){ console.log(message); } ws.addEventListener('message', onmessage); setTimeout(function(){ ws.removeEventListener('message', onmessage); }, 1000);
$函数是SecurityWorker VM内部的类预处理函数,其可以方便的在外部环境执行代码。它不同于提供的postMessage和onmessage方法,它是同步的,在编译阶段你的代码会被编译成属性访问,例如:
// sw.js var location = $('window.location.href'); // 编译后实际代码为 var location = $[0];
此预处理函数的出现主要是针对使用postMessage容易暴露行为的场景。假设我们的SecurityWorker VM的代码需要首先判断当前的Domain后再决定是否进行数据请求,当我们不使用$时,我们的代码如下:
// sw.js onmessage = function(data){ if(data.indexOf('your domain') > -1){ request({ uri: 'your url', success: function(data){ postMessage(data.text); }}); } }
// your index.html SecurityWorker.ready(function(){ var sw = new SecurityWorker(); sw.oncreate = function(){ sw.postMessage(location.href); } sw.onmessage = function(data){ console.log(data); } });
这里我们可以看到,攻击者很容易发现我们index.html中有传递location.href值的逻辑。但当我们使用$预处理函数后,我们最终的代码会依靠VM转换为opcode经过LLVM处理并进行高强度混淆后嵌入到编译后的代码之中,增强了隐匿性(但需要注意的是,由于$的整个逻辑涉及到从不隐匿环境(Browser)到隐匿环境(SecurityWorker VM)的数据传递,代码仍然在最终编译后的文件中出现,无法做到完全保密,因此可能带来不安全的风险,请斟酌使用)。
onmessage = function(data){ var location = $(function(){ // 以下代码在宿主环境中运行 // SecurityWorker会在编译期进行混淆并嵌入到生成代码中 // 尽管只是代码的混淆,但是我们再隐藏下真正的数据序列 var l = location.href; return l.split('').map(function(v){ return v.charCodeAt(0) << 4 + 128; }).join(','); }); // 下面的代码已经被编译为SecurityWorker VM的opcode,执行在安全环境 location = location.split(',').map(function(v){ return String.fromCharCode((v - 128) >> 4); }); if(location.indexOf('your domain') > -1){ request({ uri: 'your url', success: function(data){ postMessage(data.text); } }); } }
SecurityWorker.ready(function(){ var sw = new SecurityWorker(); sw.oncreate = function(){ sw.postMessage('What Ever You Want'); } sw.onmessage = function(data){ console.log(data); } });
SecurityWorker VM与V8等强调性能的Javascript引擎不同,SecurityWorker VM主要目标是更小的emscripten生成体积以及更少的内存使用。对于SecurityWorker VM来说,我们并没有集成类似V8一样的JIT机制,而是使用通过离线翻译你的Javascript代码为SecurityWorker VM指令,然后在运行时解释执行的方式,因此在性能上会有一定的损失。
相较于最新版本的V8 JIT优化后的代码,纯CPU计算性能相差7-8倍(执行10000次),I/O任务由于使用了原生环境的功能,性能大体持平。在实际应用中,我们使用SecurityWorker VM的WebSocket每20ms接收10k加密字符串并进行纯Javascript的AES256的解密操作这一任务与原生环境测试结果相比并不大( Mac Pro 2017, Intel Core i5 2.3GHz 平均占用:2.3% vs 1.8% )。
由于SecurityWorker VM并没有JIT,因此你所熟悉的一些优化手段可能会在SecurityWorker VM中失效。不要寄希望于SecurityWorker会优化你的代码,他目前并不智能(逃),任何多余的Javascript代码操作都会增加运行时的开销,例如:
var i = 1000, x = 0; while( i-- ) x++;
相比于
var x = 0; for( var j = 0; j < 1000; j++ ) x++;
在SecurityWorker VM中将会快15%,因为for循环中我们额外的引入了比较操作(j < 1000)。但对于此并不需要感到紧张,我们的建议是仍然按照你的方式编写代码,在需要深度优化的时候再进行考虑,因为在SecurityWorker VM中我们持续运行CPU密集型任务的场景并不多,大部分是等待I/O,这很难成为你代码的性能瓶颈。
Javascript的Number类型包含了Int和Float,同时根据ECMA-262标准的要求,我们需要通过一个浮点数指针来实现64-bit IEEE Math操作。但是对于SecurityWorker VM内部,我们考虑到内存占用的问题对Int和Float实际上进行了更细的区分,因此在大部分测试下Int的相关操作相比于Float会更快,占用内存会更少。
var a = 1; // 4 bytes var a = 0.7; // 12 bytes
当你数组中的类型明确为Number时,我们强烈建议你使用TypedArray来解决你的问题。对于TypedArray来说,我们可以明确类型同时省去类型包装的花销,并且不需要自动的进行数组的resize操作,因此相比与普通数组来说会更快更省内存。
var b = new Array( 2048 ); // 4KB for the array with values for( var i = 0; i++; i < 2048 ) b[ i ] = i + 0.6; // + 8KB with floats // Just 4KB allocated var a = new Float32Array( 2048 );
反复测试并联系我们,帮助我们让SecurityWorker变得更好(笑)。
- 增加SecurityWorker的onerror回调,返回SecurityWorker VM内部的未被捕获的错误
- 提供小程序的环境支持
- 提供小游戏的环境支持
- 提供NodeJS的环境支持
- 进一步优化生成的opcode大小