原文地址:http://www.moye.me/2015/12/19/protect_jsapp_tsl_by_using_node-forge/

引子

半年前的最后一次更新(惭愧  ),提到了对称与非对称的混合加解密系统,点到为止而未涉及实践,今次将就此展开,再续爱丽丝与鲍伯的前缘。

为什么

Javascript应用的安全传输,这事不是有SSL吗,为什么要多此一举呢? 好问题,自己实现TSL是因为:

  • SSL是基于浏览器的,是浏览器负责的安全性,这意味着,非浏览器应用得不到保护,即使你的服务器上安装了证书
  • SSL需要有明确的CA受信端,自颁发证书的对等端应用,显然是使用不能

那么,这样的场景是需要自实现 TSL 的:一台node服务器 和 一个electron桌面应用 之间的安全通信

怎么做

SSL是怎么做的呢? 它的流程和之前提到的混合密码系统是一样:公私钥和对称算法混合应用,在安全和性能之间取得平衡(更为具体的流程请参见 HTTPS/SSL原理及Ruby实现)。

用啥做

既然要保障的是Javascript应用的传输安全,那自然需要在npm上网罗一下,感谢社区,让我找到了 node-forge 。它是这么介绍自己的:

JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.

功能似乎很全,然后,关于性能,这货在评测中表现颇为优秀:

任何加解密都需要操作 bytes,在node-forge里,也提供了一个byte buffer的实现:ByteStringBuffer,inspect一下,是不是很眼熟:

更多的介绍可参见 官方repo文档 和 Node.js加解密

动手实践

需求

有一个页面,通过ajax的方式与node的后端通信,我希望能把各种请求(GET/POST/PUT/DELETE)里的某个参数加密(假设为id好了)

准备工作

颁发一对公私钥:

openssl genrsa -out prv.key
openssl rsa -in prv.key -pubout > pub.key

公钥给页面用来做加密:

-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKPLxKHePPC3OusMSYqPymDuuUpojM00
OF66DkWizWoein2b7n/lTUqwtiEkwY0ZZkDvktfjijpiDJMp4xscN18CAwEAAQ==
-----END PUBLIC KEY-----

私钥给后端用来解密:

-----BEGIN RSA PRIVATE KEY-----
MIIBPAIBAAJBAKPLxKHePPC3OusMSYqPymDuuUpojM00OF66DkWizWoein2b7n/l
TUqwtiEkwY0ZZkDvktfjijpiDJMp4xscN18CAwEAAQJBAJsMZ3zmV39xoxcekXrV
dDhfogw6fZY96WJZ8uqeGp5o+E8kIiwSvVPJJ/ktntSeGdz82BKip6CB7Pw28iuM
CiECIQDZESt+cflsbSLydOX8Ioo4PGDw7ftJT4YMlUHvIaOZDwIhAMEsm4v0CSm5
4sXODT546WrnrCECk7Yi1pAwqcSmIlyxAiAyvejE7i+4QOricqEwh4J4EuU2bOtI
/+X+GwYGuH5d0QIhAITnDMk4B4nWoweWIRSHGYh8hbdcT4Xy6A3h/RsXdfKxAiEA
luBD4h2dSlbNwjFyb3bRW+1Kc4PbMFOPCX6ip5PGFQ4=
-----END RSA PRIVATE KEY-----

页面端

先clone一份node-forge源码,安装完依赖,再生成bundle:

git clone https://github.com/digitalbazaar/forge && cd forge
npm install
npm run bundle

然后就能得到一个完整的forge包:js/forge.bundle.js,在页面中引用它:

<script src="js/forge.bundle.js"></script>
<script src="js/jquery.min.js"></script> //jQuery 假定你也是用的

一如之前设计的,我们要山寨的TSL是基于RSA+AES的混合方案,纵然有node-forge这么老卵的包,也还是需要自己写点工具方法的:

//aes对称加密,返回: 密文/key/iv
function _encrypt_by_aes(message) {
var key = forge.random.getBytesSync(32);
var iv = forge.random.getBytesSync(32);
var cipher = forge.cipher.createCipher('AES-CBC', key);
cipher.start({iv: iv});
cipher.update(forge.util.createBuffer(message));
cipher.finish();
var encrypted = cipher.output;
return {encrypted: encrypted, key: key, iv: iv};
}
//rsa公钥加密,传入: 公钥PEM形式
function _encrypt_by_rsa(message, pubkey) {
var pki = forge.pki;
var publicKey = pki.publicKeyFromPem(pubkey);
return publicKey.encrypt(message);
}
//序列化:把密文binary形式转成能够传输的hex形式
function _serialize(obj) {
return forge.util.bytesToHex(obj);
}

页面与后端的传输逻辑也就相对容易实施了:

function _normalize(url, kvset) {
var api_url = url + '?';
for (var k in kvset) {
api_url += k + '=' + kvset[k] + '&';
}
return api_url;
}
function _transfer_wrapper(type) {
return function(id, pubkey, url, cb_success, cb_err) {
var _cipher = _encrypt_by_aes(id);
var _cipher_id = _serialize(_cipher.encrypted);
//后端对称解密id原文需要用到的的key和iv,被rsa加密后序列化传输:
var _cipher_key = _serialize(_encrypt_by_rsa(_serialize(_cipher.key), pubkey));
var _cipher_iv = _serialize(_encrypt_by_rsa(_serialize(_cipher.iv), pubkey)); var api_url = _normalize(access_url, {
key: _cipher_key,
iv: _cipher_iv,
id: _cipher_id
}); $.ajax({type: type, url: api_url, data: {}})
.done(cb_success)
.fail(cb_err);
};
} $.fn.API_GET = _transfer_wrapper('GET');
$.fn.API_POST = _transfer_wrapper('POST');
$.fn.API_PUT = _transfer_wrapper('PUT');
$.fn.API_DELETE = _transfer_wrapper('DELETE');

页面上如此调用:

var pubkey = `-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKPLxKHePPC3OusMSYqPymDuuUpojM00
OF66DkWizWoein2b7n/lTUqwtiEkwY0ZZkDvktfjijpiDJMp4xscN18CAwEAAQ==
-----END PUBLIC KEY-----`;
var id = 'b5a98f0a-73a2-403a-b6fe-a7cc712169a8'; //被加密的id
var url = 'http://example.org/api'; function success(data){
console.log('SUCCESS', data);
}
function error(err){
console.log('ERR', err);
} $.fn.API_POST(id, pubkey, url, success, error);

node.js 后端

后端一切从简,假定web框架用的express 4.x,依然需要先安装node-forge包:

npm install node-forge --save

纵然老卵,工具方法还是要自己写的:

var forge = require('node-forge');

function _decrypt_by_aes(encrypted, key, iv) {
var decipher = forge.cipher.createDecipher('AES-CBC', key);
decipher.start({iv: iv});
decipher.update(encrypted);
decipher.finish();
return decipher.output;
}
function _decrypt_by_rsa(encrypted, prvkey) {
var pki = forge.pki;
var privateKey = pki.privateKeyFromPem(prvkey);
return privateKey.decrypt(encrypted);
}
function _deserialize(hex) {
var buffer = forge.util.hexToBytes(hex);
return forge.util.createBuffer(buffer, 'raw');
} var private_key = `-----BEGIN RSA PRIVATE KEY-----
MIIBPAIBAAJBAKPLxKHePPC3OusMSYqPymDuuUpojM00OF66DkWizWoein2b7n/l
TUqwtiEkwY0ZZkDvktfjijpiDJMp4xscN18CAwEAAQJBAJsMZ3zmV39xoxcekXrV
dDhfogw6fZY96WJZ8uqeGp5o+E8kIiwSvVPJJ/ktntSeGdz82BKip6CB7Pw28iuM
CiECIQDZESt+cflsbSLydOX8Ioo4PGDw7ftJT4YMlUHvIaOZDwIhAMEsm4v0CSm5
4sXODT546WrnrCECk7Yi1pAwqcSmIlyxAiAyvejE7i+4QOricqEwh4J4EuU2bOtI
/+X+GwYGuH5d0QIhAITnDMk4B4nWoweWIRSHGYh8hbdcT4Xy6A3h/RsXdfKxAiEA
luBD4h2dSlbNwjFyb3bRW+1Kc4PbMFOPCX6ip5PGFQ4=
-----END RSA PRIVATE KEY-----`;

写个中间件,尝试解密请求并判定是否合法:

function _check_api_request(req, res, next) {
//...blabla
try { //出错必定为非法请求
var key = _deserialize(req.query.key);
var iv = _deserialize(req.query.iv);
var cipher_id = _deserialize(req.query.id);
var cipher_k = _deserialize(_decrypt_by_rsa(key.data, private_key));
var cipher_v = _deserialize(_decrypt_by_rsa(iv.data, private_key));
//注入id
req.id = _decrypt_by_aes(cipher_id, cipher_k, cipher_v);
} catch(err) { return res.sendStatus(401); } return next();
}

小结

至此,关于Javascript的TSL实践告一段落,其实上面的例子可以做得更鲁棒一些,加上CheckSum也是好的选择,有时间的话,择日将再整理一版跨语言的加解密方案。

参考

  1. Node.js加解密 http://www.jianshu.com/p/85f152944527
  2. HTTPS/SSL原理及Ruby实现 http://foobar.me/2011/05/19/https-ssl-yuan-li-ji-ruby-shi-xian/

更多文章请移步我的blog新地址: http://www.moye.me/

[Node.js] 使用node-forge保障Javascript应用的传输安全的更多相关文章

  1. node.js 使用 UglifyJS2 高效率压缩 javascript 文件

    UglifyJS2 这个工具使用很长时间了,但之前都是在 gulp 自动构建 时用到了 UglifyJS 算法进行压缩. 最近玩了一下 UglifyJS2 ,做了一个 在线压缩javascript工具 ...

  2. 一起来学node.js吧 node school简介

    node.js这几年火爆的简直丧心病狂,去lagou.com查查node.js的职位,那叫一个多. 要说火爆到什么程度,竟然有一个网站专门去教大家学习node.js, Node School. 进去逛 ...

  3. .NET程序员也学Node.js——初识Node.js

    清明在石门休了八天假,一眨眼,4月又到中旬了...看到.NET在天朝彻底沦陷而又无能为力,我开始尝试去学习一些新的东西来充实自己,我自然是打死不会去学java的,没有为什么,于是乎,最近开始学习一些前 ...

  4. 【Node.js】Node.js的安装

    Node.js的简介 简单的说,Node.js 是运行在服务端的 JavaScript. Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台. Node.js是一个事件 ...

  5. Node.js实战(五)之必备JavaScript基础

    阅读本章的话,个人觉得之前使用过JavaScript,完全轻松. Node.js的核心类型有:number.boolean.string以及object.另外两种类型分别是函数合数组,其实它们你可以理 ...

  6. Node.js入门-Node.js 介绍

    Node.js 是什么 Node.js 不是一种独立的语言,与 PHP,Python 等"既是语言优势平台"不同,它也不是一个 JavaScrip 框架,不同于 CakePHP,D ...

  7. 初识Node.js之Node与java作为后台服务器的对比

    > 文章原创于公众号:程序猿周先森.本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号. ![file](https://img2018.cnblogs.com/blog/830272/20 ...

  8. 【Node.js】Node.js的调试

    目录结构: contents structure [-] 使用console.log() 使用Chrome DevTools 使用Visual Studio Code 与JavaScript运行在浏览 ...

  9. 极简 Node.js 入门 - Node.js 是什么、性能有优势?

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

随机推荐

  1. switch 的一些事

    switch后面的括号的表达式,其值得 “类型" 应为整数类型(包括字符类型). case后面跟一个常量或者常量表达式,

  2. Android--Intent传递对象

    Intent 传递对象通常有两种实现方式,Serializable 和 Parcelable: 一.Serializable:序列化,表示将一个对象转换成可存储或可传输的状态,序列化后的对象可以在网络 ...

  3. Main()

    P25 “每一个c#可执行文件都必须有一个入口——Main()方法” 我一直对这个Main()方法有一些疑问. 那就是这里头的参数. 在JAVA里,main(String args[])中的参数是绝对 ...

  4. libmegjb.so加载问题调试和分析

    欢迎转载opendevkit文章, 文章原始地址: http://www.opendevkit.com/?e=70 01-01 00:09:21.084: D/dalvikvm(10394): Try ...

  5. ie8下使用knockoutjs遇到的一个模板异常

    ViewModel中有一个数组,代码大概如下: function ReportViewModel(){ var self = this; self.extendedProperties = ko.ob ...

  6. mvvm的优势

    之前在项目中有个功能,是根据数据模型生成页面,然后页面变动之后,再同步到数据模型之中. 当时用的jquery写的,一点一点的控制,整个功能写下来.测试,花了很长时间,而且还担心出bug. 现在用mvv ...

  7. 人人都是 DBA(VI)SQL Server 事务日志

    SQL Server 的数据库引擎通过事务服务(Transaction Services)提供事务的 ACID 属性支持.ACID 属性包括: 原子性(Atomicity) 一致性(Consisten ...

  8. 用手机访问本地环境的利器ngrok

    我们在调试移动端页面,尤其是调试微信页面的时候,会遇到这样的情况,手机连着公司的wifi,开发环境(台式机)插着公司的网线,我们要用手机来访问开发环境下的页面.这个时候,如果网线和wifi是在同一个网 ...

  9. ubuntu git 使用

    apt-get install git//ubuntu安装git mkdir -p /var/www/gitProj //创建文件夹 cd /var/www/gitProj //进入文件夹 git i ...

  10. 来看看Windows9到底是什么

    今天有新闻一直在说windows 8.2 windows9,还给出了一张很有趣的图 我们就假设这张图是真的. 这张图透漏出两个信息 其一:开始菜单真的回来了. 不过还是不死心,绝不放弃开始屏,确实,开 ...