上一篇文章中讲到对称加密,客户端和服务端使用的都是同一个密钥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