非对称加密

  上一篇文章中讲到对称加密,客户端和服务端使用的都是同一个密钥key。这样存在一定安全风险,如果客户端如app被人逆向破解或反编译,那么密钥key就可能会被暴露。在这种情况我们就会想到非对称加密的方式,非对称加密更安全,但性能更低,大约为对称加密的1%,即如果对称加密需要花1s时间完成,那么同样方式使用非对称加密就需要100s的时间来完成。 

  非对称加密要用到两个密钥,一个公钥(客户端拥有),一个私钥(服务端拥有)。公钥是公开的,私钥是保密的。加密时客户端通过公钥进行加密,服务端接收到加密数据使用私钥进行解密。其流程如下:

  

【RSA算法】

  非对称加密中使用最广泛的算法是RSA算法,具体原理可参考阮一峰教程 RSA算法原理

【命令行生成公钥和私钥】

  1. 生成私钥,输入如下命令,对应目录下将生成 private.pem 私钥文件。1024表示加密强度。

openssl genrsa -out private.pem 1024

  2. 生成公钥,如下命令,从中可以看出,公钥是从私钥中提取。

openssl rsa -in private.pem -out public.pem -outform PEM -pubout

  3. 使用公钥加密(创建一个文件123.txt, 里面输入1234567内容),加密生成文件为123.bin

openssl rsautl -encrypt -pubin -inkey public.pem -in 123.txt -out 123.bin

  4. 使用私钥进行解密,解密内容输出到dec.txt。

openssl rsautl -decrypt -inkey private.pem -in 123.bin -out dec.txt

【私钥转PCK#8】

  在有些平台使用私钥解密时可能需要转换成PCK#8的私钥格式

  1. openssl 敲回车

  2. pkcs8 -topk8 -inform PEM -in private.pem -outform PEM -nocrypt -out private_key_pkcs8.pem

【数字证书】

  上面讲到公钥放在客户端中相当于是公开的,也有些网站直接会提供一个公钥下载链接等。公钥公开有时候会被伪装,即中间人攻击。所以一般非对称加密会搭配证书进行使用。

  数字证书就是对公钥进行数字签名,让别人无法伪造公钥。证书里面有很多信息,如姓名,组织,地址等,以及属于此人的公钥,并有认证机构施加的数字签名,只要看到公钥证书,我们就可以知道认证机构认证公钥的确属于此人。

  1. 创建证书请求(要通过上面的私钥来先生成一个证书请求),证书请求格式为 csr。

 openssl req -new -key private.pem -out rsacert.csr

  输入上面命令后会要求输入一系列信息,姓名,地址,公司等,都可以跳过

  2. 生成证书并签名,有效日期为100年,输出的 rsacert.crt 就是证书。

openssl x509 -req -days 36500 -in rsacert.csr -signkey private.pem -out rsacert.crt

  3. 格式转换,在 ios 开发中要转换成der格式证书才可以使用

openssl x509 -outform der -in rsacert.crt -out rsacert.der

 

【前后端通信思路】

  非对称加密放在前后端通信中可以有这样一种实现思路:有两对公钥和私钥,如公钥A,私钥A;公钥B,私钥B。服务端可以放私钥A,公钥B;前端可以放私钥B,公钥A。

  前端发送请求给服务端时用公钥A加密,服务端接收到请求后用私钥A进行解密。服务端返回数据用公钥B加密,前端接收到返回值用私钥B进行解密。这样即使客户端被破解反编译,那么服务端的私钥也是无法被破解的。

【代码演示】

  上一篇博客中用对称加密演示了请求通信的加解密,这里的代码加解密就不演示 http请求了。这里就只用 node.js 简单演示一下用公钥加密,用私钥解密

  1. 根据前面所讲的方法生成公钥和私钥,将公钥和私钥放入 node.js 工程中,如下:(这一步也可以不使用命令行生成,可以直接使用crypto库生成)

  

  2. 使用 crypto 加密解密如下

let crypto = require('crypto');
let fs = require('fs');

let privateKey = fs.readFileSync('./other/private.pem').toString();
let publicKey = fs.readFileSync('./other/public.pem').toString();

var data = "lijinshi嘿嘿哈哈";
var dataBuffer = new Buffer(data);

// 公钥加密
let encBuffer = crypto.publicEncrypt(publicKey, dataBuffer);
let encString = encBuffer.toString("base64");
console.log(encString);

// 私钥解密
let privateObj = {
  key: privateKey,
  passphrase: "",
  padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
};
let decBuffer = crypto.privateDecrypt(privateObj, new Buffer(encString, 'base64'));
console.log(decBuffer.toString());

  3. 运行打印如下:

  

  4. 小补充,我在实验时发现填入其他填充模式会导致解密失败,这方面还有待研究。这里填的是 RSA_PKCS1_OAEP_PADDING , 测试过程中发现每次加密的字符串都是不一样的,想起做 ios rsa 加密也出现过一样的现象,这都是填充模式不同引起的。每次加密生成的密文都不一样,但解密出的明文始终都一样。

【参考文献】

crypto翻译:http://wiki.jikexueyuan.com/project/iojs-api-doc/crypto.html

crypto官方教程:https://nodejs.org/api/crypto.html

 

posted @ 2017-12-13 21:33  布尔-  阅读(2410)  评论(0编辑  收藏  举报