数据传输加密
在开发应用过程中,客户端与服务端经常需要进行数据传输,涉及到重要隐私信息时,开发者自然会想到对其进行加密,即使传输过程中被“有心人”截取,也不会将信息泄露。对于加密算法,相信不少开发者也有所耳闻,比如MD5加密,Base64加密,DES加密,AES加密,RSA加密等等。在这里我主要向大家介绍一下我在开发过程中使用到的加密算法,RSA加密算法+AES加密算法。简单地介绍一下这两种算法吧
RSA
之所以叫RSA算法,是因为算法的三位发明者RSA是目前最有影响力的公钥加密算法,它能够抵抗到目前为止已知的绝大多数密码攻击,已被ISO推荐为公钥数据加密标准,主要的算法原理就不多加介绍,如果对此感兴趣的话,建议去百度一下RSA算法。需要了解的是RSA算法属于非对称加密算法,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。简单的说是“公钥加密,私钥解密;私钥加密,公钥解密”。
AES
高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。
客户端传输重要信息给服务端,服务端返回的信息不需加密的情况
客户端传输重要信息给服务端,服务端返回的信息不需加密,例如绑定银行卡的时候,需要传递用户的银行卡号,手机号等重要信息,客户端这边就需要对这些重要信息进行加密,使用RSA公钥加密,服务端使用RSA解密,然后返回一些普通信息,比如状态码code,提示信息msg,提示操作是成功还是失败。这种场景下,仅仅使用RSA加密是可以的。
客户端传输重要信息给服务端,服务端返回的信息需加密的情况
客户端传输重要信息给服务端,服务端返回的信息需加密,例如客户端登录的时候,传递用户名和密码等资料,需要进行加密,服务端验证登录信息后,返回令牌token需要进行加密,客户端解密后保存。此时就需要结合这两种算法了。至于整个流程是怎样的,在下面会慢慢通过例子向你介绍,因为如果一开始就这么多文字类的操作,可能会让读者感到一头雾水。
在Spring Cloud 项目中使用 RSA+AES 进行加密
粗略总结一下过程就是:客户端存放 RSA 的公钥,将 AES 的秘钥(客户端随机生成)通过RSA的公钥加密,业务数据使用AES 的秘钥进行加密,然后数据传送到服务端。服务端使用RSA私钥对RSA公钥加密过得AES秘钥进行解密得到AES秘钥。然后使用AES秘钥对业务数据解密,返回数据的时候使用AES 秘钥进行加密。
在Spring Cloud中加解密放在Gateway中,我的项目中gateway 使用的是zuul。
1.首先在gateway中加一个前置过滤器
@Slf4j
@Component
public class SignDecryptFilter extends BaseFilter {
@Autowired
private CheckRequireDecryptUtils checkRequireDecryptUtils;
//前置过滤器
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 3;
}
@Override
public boolean shouldFilter() {
/*return checkRequireDecryptUtils.checkRequireDecrypt();*/
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
getLogger().info("SignDecryptFilter>>>Options>>>{}", request.getMethod());
if (!RequestContext.getCurrentContext().getBoolean(NEXT_SHOULD_FILTER)) {
return false;
}
if (request.getMethod().equals("OPTIONS")) {
return false;
}
return RequestContext.getCurrentContext().getBoolean(NEXT_SHOULD_FILTER);
}
@Override
public Object run() {
//验签并解密数据
checkRequireDecryptUtils.checkAnddecryptWith();
return null;
}
}
public void checkAnddecryptWith() {
RequestContext ctx = RequestContext.getCurrentContext();
try {
this.checkAnddecrypt();
// throw new DecryptException(DecryptException.IllegalInputStream);
} catch (Exception e) {
ctx.setSendZuulResponse(false);//不需要进行路由,也就是不会调用api服务提供者
ctx.setResponseStatusCode(401);
ctx.set("isOK", false);//可以把一些值放到ctx中,便于后面的filter获取使用
ResultModel model = new ResultModel();
model.setMsg("异常信息:" + e.getMessage());
model.setCode(String.valueOf(DecryptException.IllegalInputStream));
model.setData("异常类:" + e.getClass());
ctx.setResponseBody(JSON.toJSONString(model));
/*ctx.setResponseBody(ResultFactory
.resultMsg(Commond.R_FAIL, "异常类:" + e.getClass(),
"异常信息:" + e.getMessage()));//返回内容给客户端 // 返回错误内容*/
//ctx.setResponseBody(e.getMessage());//返回内容给客户端 // 返回错误内容
ctx.getResponse().setContentType("application/json;charset=UTF-8");
}
}
前端参数转换的实体类
@Data
public class RsaRequestDto {
/**
* 时间戳
*/
private String timestamp;
/**
* 随机字符串
*/
private String nonce;
/**
* 业务数据加密后的密文
*/
private String encrypt;
/**
* 被RSA公钥加密过得AES秘钥字符串
*/
private String echokey;
/**
* 签名
*/
private String signature;
}
public void checkAnddecrypt() throws Exception {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
InputStream in = null;
String body = null;
try {
in = ctx.getRequest().getInputStream();
body = StreamUtils.copyToString(in, Charset.forName("UTF-8"));
} catch (IOException e) {
logger.error("解密接口数据获取入参异常,异常类{},异常信息{}", e.getClass(), e.getMessage());
throw new DecryptException(DecryptException.IllegalInputStream);
}
RsaRequestDto rsaRequestDto = null;
if (!StringUtils.isEmpty(request.getContentType()) && request.getContentType()
.contains("application/json")) {
try {
rsaRequestDto = JSONObject.parseObject(body, RsaRequestDto.class);
} catch (Exception e) {
logger.error("接口解密,解析json对象失败,将直接调用业务接口");
}
}
if (!this.checkRequireDecrypt()) {
//校验是否输入了加密参数
if (rsaRequestDto != null && !StringUtils.isEmpty(rsaRequestDto.getEncrypt())) {
throw new DecryptException(DecryptException.IllegalInputStream);
}
return;
}
//验证签名并解密业务数据
String newBody = commonEncryption.checkAnddecrypt(rsaRequestDto);
final byte[] reqBodyBytes = newBody.getBytes();
ctx.setRequest(new HttpServletRequestWrapper(request) {
@Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStreamWrapper(reqBodyBytes);
}
@Override
public int getContentLength() {
return reqBodyBytes.length;
}
@Override
public long getContentLengthLong() {
return reqBodyBytes.length;
}
});
}
public String checkAnddecrypt(RsaRequestDto rsaRequestDto) throws Exception {
//判断是否为空,为空抛异常
if (rsaRequestDto == null || StringUtils.isBlank(rsaRequestDto.getEncrypt()) || StringUtils
.isBlank(rsaRequestDto.getEchokey()) || StringUtils.isBlank(rsaRequestDto.getNonce())
|| StringUtils.isBlank(rsaRequestDto.getSignature()) || StringUtils
.isBlank(rsaRequestDto.getTimestamp())) {
throw new DecryptException(DecryptException.IllegalInputStream);
}
//验证签名
String signOriginStr = getStrForSign(rsaRequestDto.getTimestamp(), rsaRequestDto.getNonce(),
rsaRequestDto.getEncrypt());
boolean status = RSACoder
.verify(signOriginStr.getBytes(), appConfigProperties.getPublicKeyForSign(),
rsaRequestDto.getSignature());
if (!status) {
throw new DecryptException(DecryptException.ValidateSignatureError);
}
//通过RSA的私钥解密获得前端 RSA公钥加密过得AES秘钥
byte[] key = RSACoder.decryptByPrivateKey(rsaRequestDto.getEchokey(),
appConfigProperties.getPrivateKeyForDataKey());
String echokeyStr = new String(key, CHARSET);
// System.out.println("aes 加密随机key " + echokeyStr);
//使用AES 的秘钥解密业务数据
String data = AesUtils.decrypt(rsaRequestDto.getEncrypt(), echokeyStr, echokeyStr);
// System.out.println(data);
return data;
}
返回加密数据的时候再使用个filter 做一下返回后置处理
public RsaRequestDto encrypt(String data) throws Exception {
RsaRequestDto dto = new RsaRequestDto();
String timestamp = String.valueOf(System.currentTimeMillis());
String nonce = RSACoder.getRandomStr();
//生成加密业务数据随机密匙(使用RSA 的算法生成一个AES的秘钥)
String echokeyStr = RSACoder.getRandomStr();
//加密业务数据随机密匙(使用RSA私钥加密AES秘钥)
String echokey = RSACoder
.encryptBASE64(
RSACoder.encryptByPublicKey(echokeyStr, appConfigProperties.getPublicKeyForDataKey()));
//使用AES秘钥加密业务数据
String encrypt = AesUtils.encrypt(data, echokeyStr, echokeyStr);
//生成签名
String signature = RSACoder
.sign(getStrForSign(timestamp, nonce, encrypt).getBytes(CHARSET),
appConfigProperties.getPrivateKeyForSign());
dto.setTimestamp(timestamp);
dto.setNonce(nonce);
//AES秘钥加密过得业务数据
dto.setEncrypt(encrypt);
//被加密过得AES秘钥
dto.setEchokey(echokey);
dto.setSignature(signature);
return dto;
}