[Node.js] 使用node-forge保障Javascript应用的传输安全
原文地址: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.
功能似乎很全,然后,关于性能,这货在评测中表现颇为优秀:
- http://dominictarr.github.io/crypto-bench/
- http://cryptojs.altervista.org/test/simulate-threading-speed_test.html
任何加解密都需要操作 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也是好的选择,有时间的话,择日将再整理一版跨语言的加解密方案。
参考
- Node.js加解密 http://www.jianshu.com/p/85f152944527
- 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应用的传输安全的更多相关文章
- node.js 使用 UglifyJS2 高效率压缩 javascript 文件
UglifyJS2 这个工具使用很长时间了,但之前都是在 gulp 自动构建 时用到了 UglifyJS 算法进行压缩. 最近玩了一下 UglifyJS2 ,做了一个 在线压缩javascript工具 ...
- 一起来学node.js吧 node school简介
node.js这几年火爆的简直丧心病狂,去lagou.com查查node.js的职位,那叫一个多. 要说火爆到什么程度,竟然有一个网站专门去教大家学习node.js, Node School. 进去逛 ...
- .NET程序员也学Node.js——初识Node.js
清明在石门休了八天假,一眨眼,4月又到中旬了...看到.NET在天朝彻底沦陷而又无能为力,我开始尝试去学习一些新的东西来充实自己,我自然是打死不会去学java的,没有为什么,于是乎,最近开始学习一些前 ...
- 【Node.js】Node.js的安装
Node.js的简介 简单的说,Node.js 是运行在服务端的 JavaScript. Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台. Node.js是一个事件 ...
- Node.js实战(五)之必备JavaScript基础
阅读本章的话,个人觉得之前使用过JavaScript,完全轻松. Node.js的核心类型有:number.boolean.string以及object.另外两种类型分别是函数合数组,其实它们你可以理 ...
- Node.js入门-Node.js 介绍
Node.js 是什么 Node.js 不是一种独立的语言,与 PHP,Python 等"既是语言优势平台"不同,它也不是一个 JavaScrip 框架,不同于 CakePHP,D ...
- 初识Node.js之Node与java作为后台服务器的对比
> 文章原创于公众号:程序猿周先森.本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号.  使用Chrome DevTools 使用Visual Studio Code 与JavaScript运行在浏览 ...
- 极简 Node.js 入门 - Node.js 是什么、性能有优势?
极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...
随机推荐
- 如何在两个activity之间传递bitmap
1.需求 在项目开发过程中,打印小票前需要添加打印预览功能,交易数据在打印前转成bitmap然后直接打印,为了显示这个bitmap需要将其传给显示activity. 2.解决方法 把bitmap存储为 ...
- NSThread 子线程 Cocoa NSOperation GCD(Grand Central Dispatch) 多线程
单词:thread 英 θred:n 线.思路.vt 穿过.vi 穿透过 一. 进程.线程 进程:正在进行中的程序被称为进程,负责程序运行的内存分配,每一个进程都有自己独立的虚拟内存空间 线程: ...
- android项目中values中几个文件的作用
最近反编译了几个Android软件,发现一些以前未用到的资源文件:ids.xml——为应用的相关资源提供唯一的资源id.id是为了获得xml中的对象而需要的参数,也就是Object = findVie ...
- jQuery+ASP.NET MVC基于CORS实现带cookie的跨域ajax请求
这是今天遇到的一个实际问题,在这篇随笔中记录一下解决方法. ASP.NET Web API提供了CORS支持,但ASP.NET MVC默认不支持,需要自己动手实现.可以写一个用于实现CORS的Acti ...
- Android(蓝牙)
因近期项目需求调试了Android蓝牙通讯接口,主要是两个终端作为服务端和客户端的通信,本文将部分重要知识点记录如下. 蓝牙是短距离无线通信,通常分经典蓝牙和低功耗蓝牙(即蓝牙4.0),两类蓝牙协议各 ...
- STC12C5A60S2笔记4(复位)
1. 基本特性 STC 单片机有5种复位方式: 1) 热启动复位: 1.1)外部RST引脚复位 第一功能复位脚,即管脚9 RST/P4.7,该管脚拉高维持24个时钟周期+10ms后,单片机进入复位状态 ...
- C#可扩展编程之MEF学习笔记(一):MEF简介及简单的Demo
在文章开始之前,首先简单介绍一下什么是MEF,MEF,全称Managed Extensibility Framework(托管可扩展框架).单从名字我们不难发现:MEF是专门致力于解决扩展性问题的框架 ...
- jquery插件——点击交换元素位置(带动画效果)
一.需求的诞生 在我们的网页或者web应用中,想要对列表中的元素进行位置调整(或者说排序)是一个常见的需求.实现方式大概就以下两种,一种是带有类似“上移”.“下移”的按钮,点击可与相邻元素交换位置,另 ...
- Ember.js实现单页面应用程序
1.1.1 摘要 单页应用程序 (SPA) 是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序. SPA使用AJAX和HTML5创建流畅且响应迅速的Web应用程序,不会经常 ...
- Azure China (5) 管理Azure China Powershell
<Windows Azure Platform 系列文章目录> 本文介绍的是国内由世纪互联运维的Azure China Cloud Update 2015-09-01 发现一个新的命令,在 ...