【5】JMicro其于RSA及AES加密实现安全服务调用
JMicro是基于Java实现的微服务平台,最近花了两个周未实现服务间安全调用支持。
JMicro服务调用分两个部份,分别为内部服务间相互调用和外部客户端通过API网关调用JMicro集群内部服务,前者支持双向加密加签,并且支持全RSA加密(效率底,安全性高)及RSA+AES混合加密解密,后者只支持RSA+AES混合加密解密,类似于HTTPS的功能。
内部RPC实现安全通信配置
内部服务间安全通信配置比较简单,如下为JMicro系统登陆接口:
upSsl=true表示客户端请求登陆时,上行数据需要做安全加密处理,客户端使用自己的私钥签名,用服务端公钥对数据或AES密钥做加密处理;
downSsl=true表示服务端返回登陆成功数据时(任何情况下RPC调用失败都不做加密处理),下行数据需要做安全加密处理,服务端使用己方私钥加签,用客户端公钥或AES动态密钥对下行数据做加密处理。
EncType=0表示RSA+AES混合加密解密,数据发送方生成动态密钥S并用对方公钥对S进行RSA加密,S的密文和数据一起发送给对方。用密钥S对数据做AES加密。此方式加密解密效率非常高,但要保证密钥足够复杂,不易被暴力破解。
EncType=1表示全RSA加密,直接用对方公钥对参数做RSA非对称加密,用自己的私钥加签,对方收到数据时,用对方自己私钥解密数据,用发送方公钥验签,此方式安全性高,但效率较底,使用于对安全性要求非常高,数据量少的场景。
JMicro密码生成算法
完整代码:
https://github.com/mynewworldyyl/jmicro/blob/master/apics/src/main/java/cn/jmicro/api/rsa/EncryptUtils.java
JMicro实现一套动态密码生成算法,可以根据需要生成指定长度的密码或IV值。
首先定义3个常量
//一级随机种子
private static final Random RSEED = new Random(System.currentTimeMillis());
//密码表长度
public static final int CHAR_TABLE_LEN = 512;
//密码表
public static final char[] USABLE_CHAR = new char[CHAR_TABLE_LEN];
系统启动时,生成密码表,代码如下
static {
createPwdTable();
}
private static void createPwdTable() {
int len = CHAR_TABLE_LEN;
Random r = new Random(RSEED.nextInt());
//StringBuffer sb = new StringBuffer();
for(int i = 0; i < len; i++) {
int rv = r.nextInt();
if(rv < 0) {
rv = -rv;
}
int c = (rv % 85) + 33;
USABLE_CHAR[i] = (char)c;
//sb.append((char)c);
}
}
以一级随机数生成随机种子再实例化一个随机数,这样保证每调用一次createPwdTable方法,生成的随机密码表都不一样,系统按照一定时间间隔调用createPwdTable方法刷新密码表。
基于以上的密码表,生成指定长度密码,代码如下:
public static String generatorStrPwd(int len) {
StringBuffer data = new StringBuffer();
Random r = new Random(System.currentTimeMillis());
for(int i = 0; i < len; i++) {
int idx = r.nextInt(1024)%CHAR_TABLE_LEN;
data.append(USABLE_CHAR[idx]);
}
return data.toString();
}
公钥管理
JMicro支持N个动态服务系统,每个系统都可以有其独立的私钥和公钥,且每个系统的每个服务在被调用之前都不知道当前系统有多少个系统多少个服务在运行,所以不可能预先在每个系统中配置好对方的公钥,因此公钥的安全分发成为JMicro安全系统的一个核心问题。正如HTTPS证书一样,需要权威的证书中心来维护,JMicro目前不支持从公开的证书中心取证书,一方面因为成本原因,另一方面,JMicro是一个由N个JVM的M个服务组成的微服务系统,需要N个证书,并且支持证书动态更新,因此JMicro实现一个独特的证书管理系统,并且抽象出标准的获取证书的RPC接口。接口定义如下:
package cn.jmicro.api.security; import java.util.List; import cn.jmicro.api.Resp;
import cn.jmicro.codegenerator.AsyncClientProxy; @AsyncClientProxy
public interface ISecretService {
/**
* 根据实例前缀拿取公钥
* @param instancePrefix 服务前缀,不同的前缀有不同的公钥,相同前缀只能一个公钥启用
* @return
*/
Resp<String> getPublicKeyByInstance(String instancePrefix); /**
* 我的公钥列表,只能拿取当前账号下的公钥列表
* @return
*/
Resp<List<JmicroPublicKey>> publicKeysList(); /**
* 创建一个公私钥,同时可选给私钥加密保护
* 如果创建成功,返回公钥和私给调用者,服务端只保留公钥,不存储私钥,所以创建者一定要保留好私钥及私钥密码,私钥一旦丢失,
* 将无法找回,只能重新生成。
* @param instancePrefix 服务前缀
* @param password 私钥密码
* @return
*/
Resp<JmicroPublicKey> createSecret(String instancePrefix,String password); /**
* 删除未启用的公钥,删除后将无法恢复
* 启用中的公钥不能删除
* @param id
* @return
*/
Resp<Boolean> deletePublicKey(Long id); /**
* 更新公钥前缀
* 启用中的公钥不能更新
* @param id
* @param instancePrefix
* @return
*/
Resp<Boolean> updateInstancePrefix(Long id,String instancePrefix); /**
* 增加一个线下生成的公钥到系统中
* @param instancePrefix
* @param publicKey
* @return
*/
Resp<JmicroPublicKey> addPublicKeyForInstancePrefix(String instancePrefix, String publicKey); /**
* 启用公钥,公钥启用后,其他系统就可以根据前缀取得公钥,从而可以与前缀所对应的系统做安全通信
* 同一个前缀同一时刻只能有一个公钥被启用
* @param id
* @param enStatus
* @return
*/
Resp<Boolean> enablePublicKey(Long id,boolean enStatus);
}
除了getPublicKeyByInstance RPC方法之外,其他方法为可选实现,用于对公钥做维护。getPublicKeyByInstance用于服务请求方与服务提供方做交互之前获取对方的公钥,以便能与对方做安全通信。如果是双向安全通信,服务方收到客户端请求后,也要根据客户端实例前缀获取客户端的公钥做返回数据加密及上行数据验签。
下图为JMicro系统实现代码
可以看出,此方法本身也是双向安全通信的RSA+AES混合加密模式,从而保证任何调用此方法取得的公钥都是可信任的,不可能存在“中间人”修改作假的问题。
调用此方法的前提条件是安全中心的公钥需要预配置到客户端系统中,因为安全中心只需要一对公私钥,任何系统都可以提前获得此公钥。以下为JMicro默认两个预先配
置好的公钥,APICS模块为JMicro的最基础模块,所以JMicro微服务应用都可以安全地获得公钥接口权限。我们使用的电脑的操作系统是不是预先保存有权威证书管理中心的公钥?
注意:因为考虑到JMicro系统安全原因,JMicro公钥管理系统没有做开源,但是只需要根据以上接口的定义实现相关逻辑,就可以做同样的功能。
Java客户端通过Api网关调用JMicro服务
前面说过,API网关与外部系统通信,只支持RSA+AES混合模式,严格来讲,是API网关与基于Web浏览器为基础的网页端通信,只支持RSA+AES混合模式,而Java客户端与API网关通信,可以支持全RSA加密模式。因为在WEB应用中,如果需要支持全RSA模式,则需要为WEB端分配一对公私钥,而WEB端的全部资源,需要从服务端加载,我们不可能把私钥加载到用户的浏览器吧,如果私钥这样加载,那还有什么安全性可言呢?所以在WEB端不支持全RSA加密模式或双向RSA加密模式,并非技术上实现不了,而是RSA加密算法本身的特性所决定其没有意义,甚至存在涉密风险!
还有一个很有意思的问题,有很多没搞明白RSA加密算法原理的同学经常会有用私钥加密公钥解密这种想法,这也是没有意义的?为什么呢?因为公钥是公开的,大家都可以获取到,用私钥加密和没加密就没什么区别了!
对于Java客户端与服务网关通信,通过以下配置即可实现双向安全配置,encType可以是0或1:
@BeforeClass
public static void setUp() { ApiGatewayConfig config = new ApiGatewayConfig(Constants.TYPE_SOCKET,"192.168.56.1",9092);
config.setUpSsl(true);//上行加密
config.setDownSsl(true);//下行加密
config.setEncType(0);//AES加密数据 ApiGatewayClient.initClient(config);
socketClient = ApiGatewayClient.getClient();
}
浏览器WEB客户端通过Api网关调用JMicro服务
在网页初始化时,通过以下代码启用安全通信支持,客户端请求Api网关的全部请求参数将被RSA+AES加密,Api网关返回数据也将被加密并签名,客户端做解密并验签。
window.jm.config.sslEnable = true;
window.jm.rpc.init(window.jm.config.ip,window.jm.config.port);
下面说下JMicro使用JSEncrypt和CryptoJS方式,有需要的可以直接复制代码使用,否则很多细节调起来相当麻烦。
首先是客户端请求加密,通过JSEncrypt做RSA非对称加密AES密钥,然后用AES密钥加密数据
encrypt: function (msg){
if(!this.pwdTable) {
this.init();
} msg.setUpSsl(true);
msg.setDownSsl(true);
msg.setEncType(false); let opts = {
mode : CryptoJS.mode.CBC ,
padding : CryptoJS.pad.Pkcs7,
keySize : this.keySize,
iv :null ,
salt : null
}; let iv = jm.eu.genStrPwd(16); //通过密码表生成16个字节的动态偏移量
opts.iv = CryptoJS.enc.Utf8.parse(iv); //将偏移量转为UTF8编码,服务端使用时也要相应地使用utf8字节数组
msg.salt = jm.utils.toUTF8Array(iv); //将偏移量和数据一起发送给服务端,注意是utf8字节编码 if(!this.pwd || new Date().getTime() - this.lastUpdatePwdTime > 1000*60*5 ) {
//首次进来或超过5分钟更新一次密码
this.pwd = jm.eu.genStrPwd(16);//生成密码,方式和IV相同,但是功能不一样,参考前面关于密码表的说明
msg.setSec(true);//告诉服务端,有AES密码更新
msg.sec = this.encryptRas(this.pwd);//对AES密码做RSA加密
} let b64Str = jm.utils.byteArr2Base64(msg.payload);//将要发送的字节数据转为base64字符串格式,因为AES只接受字符串加密,同时方便服务器更好地处理解码
var encrypted = CryptoJS.AES.encrypt(b64Str, CryptoJS.enc.Utf8.parse(this.pwd),opts);//开始加密,密钥转为UTF8格式,保证Java服务端相同编码
msg.payload = this.wordToByteBuffer(encrypted.ciphertext);//encrypted.ciphertext是一个以WordArray,也就是一个整数数组,要将此整数数据转为字节数组
},
RAS加密密钥encryptRas
encryptRas:function(strContent) {
if(!this.pwdTable) {
//初始化密码表
this.init();
}
let rst = this.rsae.encrypt(strContent);
return jm.utils.toUTF8Array(rst); //rst是base64编码后的十六进制字符串,此处对这个base64字符串做utf8编码转为字节数组
},
对应Java端的解密密钥
//对密钥做utf8解码为字符串,结果是密码密文的base64编码
String b64Str = new String(msg.getSec(),Constants.CHARSET);
//对密文做base64解码,得到密文的字节数组,
byte[] sec = Base64.getDecoder().decode(b64Str);
//解密密文,得到字节码形式的AES密码明文,字节码是密码的UTF8编码后的字节数组
secrect = EncryptUtils.decryptRsa(myPriKey,sec, 0, sec.length);
解密出密码明文后,开始用这个密码解密数据
SecretKey originalKey = new SecretKeySpec(secrect, 0, secrect.length, EncryptUtils.KEY_AES);
ByteBuffer bb = (ByteBuffer) msg.getPayload();
//msg.getSalt() 是客户端传过来的IV值的utf8编码后的数组,
byte[] d = EncryptUtils.decryptAes(bb.array(), 0, bb.limit(), msg.getSalt(), k.key);
if(msg.isFromWeb()) {
//因为WEB端是将数据做Base64编码为字符串后做的加密,所以Java端同样要将结果做Base64解码
d = Base64.getDecoder().decode(d);
}
Java端对返回给WEB端的数据做加密
//因为WEB端验签时只认字符串,所以加签前把数据转为Base64字符串
byte[] b64Data = Base64.getEncoder().encode(bb.array());
sign = EncryptUtils.sign(b64Data, 0, b64Data.length, this.myPriKey);
数据AES加密
byte[] edata = EncryptUtils.encryptAes(bb.array(), 0, bb.limit(), salt, sec.key);
JS端做解密
let b64str = jm.utils.byteArr2Base64(msg.payload);
var decrypted = CryptoJS.AES.decrypt(b64str,utf8pwd,opts);
let dedata = this.byteBuffer2ByteArray(this.wordToByteBuffer(decrypted));
JS验签
if(!this.rsae.verify(jm.utils.byteArr2Base64(dedata), msg.sign, CryptoJS.MD5)) {
throw "Invalid sign";
}
完整代码可以参考
https://github.com/mynewworldyyl/jmicro/blob/master/apics/src/main/java/cn/jmicro/api/rsa/EncryptUtils.java
https://github.com/mynewworldyyl/jmicro/blob/master/api/src/main/java/cn/jmicro/api/security/SecretManager.java
https://github.com/mynewworldyyl/jmicro/blob/master/mng.web/public/js/rpc.js
JMicro 微服务管理系统: http://jmicro.cn/
【5】JMicro其于RSA及AES加密实现安全服务调用的更多相关文章
- c# .NET RSA结合AES加密服务端和客户端请求数据
这几天空闲时间就想研究一下加密,环境是web程序,通过js请求后台返回数据,我想做的事js在发送请求前将数据加密,服务端收到后解密,待服务端处理完请求后,将处理结果加密返回给客户端,客户端在解密,于是 ...
- java使用RSA与AES加密解密
首先了解下,什么是堆成加密,什么是非对称加密? 对称加密:加密与解密的密钥是相同的,加解密速度很快,比如AES 非对称加密:加密与解密的秘钥是不同的,速度较慢,比如RSA 先看代码(先会用在研究) 相 ...
- RSA、AES加密解密
RSA #!/usr/bin/env python # -*- coding:utf-8 -*- import rsa import base64 # ######### 1. 生成公钥私钥 #### ...
- JAVA RSA加密AES加密
RSA加密: import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; import javax.crypto.Cipher; imp ...
- java 加密工具类(MD5、RSA、AES等加密方式)
1.加密工具类encryption MD5加密 import org.apache.commons.codec.digest.DigestUtils; /** * MD5加密组件 * * @autho ...
- 你真的了解字典(Dictionary)吗? C# Memory Cache 踩坑记录 .net 泛型 结构化CSS设计思维 WinForm POST上传与后台接收 高效实用的.NET开源项目 .net 笔试面试总结(3) .net 笔试面试总结(2) 依赖注入 C# RSA 加密 C#与Java AES 加密解密
你真的了解字典(Dictionary)吗? 从一道亲身经历的面试题说起 半年前,我参加我现在所在公司的面试,面试官给了一道题,说有一个Y形的链表,知道起始节点,找出交叉节点.为了便于描述,我把上面 ...
- polarssl rsa & aes 加密与解密
上周折腾加密与解密,用了openssl, crypto++, polarssl, cyassl, 说起真的让人很沮丧,只有openssl & polarssl两个库的RSA & AES ...
- polarssl rsa & aes 加密与解密<转>
上周折腾加密与解密,用了openssl, crypto++, polarssl, cyassl, 说起真的让人很沮丧,只有openssl & polarssl两个库的RSA & AES ...
- c#RSA的SHA1加密与AES加密、解密
前言:公司项目对接了一个对数据保密性要求较高的java公司.api接口逻辑是这样的:他们提供 SHA1私钥 与 AES的秘钥.我们需要将 传递查询参数 通过SHA1 私钥加密再转换成 十六进制 字符串 ...
随机推荐
- Spring Environment对象获取属性
String[] activeProfiles = env.getActiveProfiles();//获取当前是启用哪一个个配置文件 System.out.println(Arrays.toStri ...
- 微信小程序 LBS 能力全面解析
分享之前我们先来看看地图能力在小程序架构体现中所处的位置. 小程序架构图解 如图标黄处为地图能力所处的一个位置,举个例子,比如调用定位能力获取用户当前位置的一个流程: 首先调用 JS API wx.g ...
- 我把这个贼好用的Excel导出工具开源了!!
写在前面 不管是传统软件企业还是互联网企业,不管是管理软件还是面向C端的互联网应用.都不可避免的会涉及到报表操作,而对于报表业务来说,一个很重要的功能就是将数据导出到Excel.如果我们在业务代码中, ...
- jvm优化案例
案例1 survivor区太小,每次Minor GC存活的对象进入老年代,导致老年代可用空间不足,经常发生FULL GC,导致系统变慢 案例问题描述 有一个数据计算系统,从mysql和其他数据源提取数 ...
- 怀疑安装MySQL之后,导致OrCAD Capture、Allegro就打不开
记得在异常出现之前,只安装了MySQL,之后OrCAD Capture.Allegro就打不开了. Capture.exe - 系统错误 allegro.exe - 系统错误 我尝试在Cadence的 ...
- lua 源码阅读 1.1 -> 2.1
lua 1.1 阅读1. hash.c 中 a) 对建立的 Hash *array 用 listhead 链式结构来管理,新增lua_hashcollector,用来做 Hash 的回收处理. ps: ...
- Makefile常用函数(转)
一.字符串处理函数 1.$(subst FROM,TO,TEXT) 函数名称:字符串替换函数-subst. 函数功能:把字串"TEXT"中的"FROM"字符替换 ...
- linux网卡驱动程序架构
以cs89x0网卡驱动为例:
- 2016年 实验三 B2C模拟实验
实验三 B2C模拟实验 [实验目的] 掌握网上购物的基本流程和B2C平台的运营 [实验条件] ⑴.个人计算机一台 ⑵.计算机通过局域网形式接入互联网. (3).奥派电子商务应用软件 [知识准备] 本实 ...
- 实验五 css进阶应用
实验五 css进阶应用 实验目的: 掌握CSS在列表中的应用,能利用CSS将列表做成精美的导航栏: 掌握CSS在表单元素中的应用: 掌握SPRY菜单的制作方法和CSS代码修改. 实验内容: 1. 制作 ...