原文地址: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. LintCode 111 Climbing Stairs

    这道题参考了这个网址: http://blog.csdn.net/u012490475/article/details/48845683 /* 首先考虑边界情况,当有1层时,有一种方法. 然后再看2层 ...

  2. node安装笔记

    安装node.js1.下载node可以直接下载二进制,也可以下载源代码再安装.我选择下载二进制: https://nodejs.org/dist/v4.6.0/node-v4.6.0-linux-x6 ...

  3. XMPP即时通信(基础)

      使用第三方框架 XMPPFramework   #import "ViewController.h" #import "XMPPFramework.h" @ ...

  4. FSG报表定义导入

    Copying Report Objects From Another Database (FSG Transfer Program) Run the FSG Transfer program to ...

  5. javascript event兼容性随笔

    一.前言 function ConvertEvent(e, element) { var event = e || window.event; var resultEvent = { event: e ...

  6. 软件工程day4

    使用ps制作了一个icon,将在下个版本中添加,用作程序图标. 参与组例会,得知新功能“吐槽墙”将以聊天室类似的社区形式实现. 提出对现有UI的建议: 对目前的登录窗口的UI不做改动,将标题的“用户登 ...

  7. (转)解释一下SQLSERVER事务日志记录

    本文转载自桦仔的博客http://www.cnblogs.com/lyhabc/archive/2013/07/16/3194220.html 解释一下SQLSERVER事务日志记录 大家知道在完整恢 ...

  8. 基于Winform、WPF等的客户端文件下载

    有时候,我们用C#写一些客户端应用程序需要从服务器下载一些资源,如图片.dll.配置文件等.下面就来说一下,在Winform及WPF中如何下载文件. 我们的资源大多放在自己的网站上,或者从其他网站下载 ...

  9. Python2.6下基于rsa的加密解密

    生成公钥的私钥: # -*- coding: UTF-8 -*- import rsa import base64 (public_key, private_key) = rsa.newkeys(10 ...

  10. windows 8.1 试用感受:蛋疼感大幅降低

    众所周知windows 8 的最大使用感受就是蛋疼. 无论是微软MVP,还是我这样的万年不悔微软小白鼠,普通用户,小白用户,或多或少的都对这款操作系统感到蛋疼. 槽点太多,以至于大家都懒得批判了.好在 ...