为什么使用RSA + AES混合加密
1.加密介绍
- RSA加密: 属于非对称加密,公钥用于对数据进行加密,私钥对数据进行解密,两者不可逆。公钥和私钥是同时生成的,且一一对应。比如:客户端拥有公钥,服务端拥有公钥和私钥。客户端将数据通过公钥进行加密后,发送密文给服务端,服务端可以通过私钥和公钥进行解密。
- AES加密: 属于对称加密,简单点说就是,客户端用密码对数据进行AES加密后,服务端用同样的密码对密文进行AES解密。
2.加密思路
- 利用 RSA 来加密传输 AES的密钥,用 AES的密钥 来加密数据。
- 既利用了 RSA 的灵活性,可以随时改动 AES 的密钥;又利用了 AES 的高效性,可以高效传输数据。
3.混合加密原因
- 单纯的使用 RSA(非对称加密)方式,效率会很低,因为非对称加密解密方式虽然很保险,但是过程复杂,耗费时间长,性能不高;
- RSA 优势在于数据传输安全,且对于几个字节的数据,加密和解密时间基本可以忽略,所以用它非常适合加密 AES 秘钥(一般16个字节);
- 单纯的使用 AES(对称加密)方式的话,非常不安全。这种方式使用的密钥是一个固定的密钥,客户端和服务端是一样的,一旦密钥被人获取,那么,我们所发的每一条数据都会被都对方破解;
- AES有个很大的优点,那就是加密解密效率很高,而我们传输正文数据时,正好需要这种加解密效率高的,所以这种方式适合用于传输量大的数据内容;
基于以上特点,就有了我们混合加密的思路
时序图
前端代码:
创建aesUtils.js
import CryptoJS from 'crypto-js'
import {JSEncrypt} from 'jsencrypt'
import cryptoJS from './CryptoJSUtils'
/**
* 创建密钥
* @returns AES密钥
*/
export function createAesKey () {
const expect = 16
let str = Math.random().toString(36).substr(2)
while (str.length < expect) {
str += Math.random().toString(36).substr(2)
}
str = str.substr(0, 16)
return str
}
/**
* AES加密
* @param {*} word 加密字段
* @param {*} keyStr AES密钥
* @returns
*/
export function AESencrypt (word, keyStr) {
keyStr = keyStr || 'abcdefgabcdefg12'
var key = CryptoJS.enc.Utf8.parse(keyStr) // Latin1 w8m31+Yy/Nw6thPsMpO5fg==
var srcs = CryptoJS.enc.Utf8.parse(word)
var encrypted = CryptoJS.DES.encrypt(srcs, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
})
return encrypted.ciphertext.toString()
}
/**
* RSA加密算法
* @param {*} pas
* @returns
*/
export function RSAencrypt (pas, publickey) {
let jse = new JSEncrypt()
jse.setPublicKey(publickey)
return jse.encrypt(pas)
}
/**
* 获取16位随机数,当做aes秘钥key,进行加密操作
* @constructor
*/
export function RsaEncryptData (data) {
// 此处生成十六位随机数进行aes对称加密密钥准备
var randomStr = Math.random().toString().substr(0, 16)
// aes加密
var datas = cryptoJS.encrypt(JSON.stringify(data), randomStr)
datas = datas.toString()
// 声明rsa加密规则
var encrypt = new JSEncrypt()
// 将rsa公钥进行保存
encrypt.setPublicKey('MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCI2zy2kkLxdhx31pu2gRB95QCx5aOvw5yTt44glEPIWhaoqXVeTch9dwAjaoInm6a1BiQHEtE/ccWTPmM7Iktrjcw3siC3dV2/QJkpk8/b52TMCw9R55qXL1+Y1f0z7BCu3ikCfyTw5cxAh5pa3r0YhYmeC+E6J3crmBPzImfYCwIDAQAB')
// 使用公钥对aes的密钥进行加密
var encrypted = encrypt.encrypt(randomStr)
// 创建json对象
let json = {
'requestData': datas,
'encrypted': encrypted
}
return json
}
/**
* 将返回的数据进行解密操作
* @constructor
*/
export function RsaDecryptsData (result) {
// rsa解密,获取到aes秘钥
var decrypt = new JSEncrypt()
decrypt.setPrivateKey('MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALs0Ufy3++1luZf7XtsQiPXORRuv5KC6ec7pkApNc3ckFQfpWjhAiM0yn7tqGl8y1zRRR8/g8dsUCofTLOL1EJB+7xEvUCSmjB6RDowtVOxA9vRCrrxwVoNY881x94GE/Ln2A64xVtbFspq0s9hpP4GU0QXWIHKMV/SzB7DsN37PAgMBAAECgYEArS6VukkqUlANBcCR2+7MBTmxTQ/HXbmk/fmsOxuzecBzhEIoKGnrJIl0o5hglTkfRVL8MB9VHurHYyfFGqDDlJB70FVrCPBrtxPoUtB5aI0SLSkDHX3EWjlOBlCQkMiFhx9cS9PCloDSA2Ahzga3y8Bg3LaXhoZediPgz4PmBaECQQD5+mPahaPnpJcR5CKCjryXlpqic+s0cE33ZtYPwKe163KHsSdCErOsFQ9k01JpHbCZmipzRRC+xT0CZ7DfKLRjAkEAv7bOVC+HO9YM5Qf2eYW2kUv4ssG9c8NhsXBSnKQfaFEKM4xLPtulj16YQevHpjgzr2BIg5arVWW81Nu1YLlJpQJAW7cHXcx8d2fG2ZSXKMmP3houf/4BxMqTgHrlfQAVSESrT6eqnK5Z54AOltKFwPVYrvKGMqabXzLkkHZUyXuYuwJAEkhywOCPewtcy3LI9Knl0VF3dES5tpKJfIyDtGCKhj5ERMo6WtJDpbqVtqOvtJBjjXQXNkVmLYy4R2x0jbbd6QJARGMQhsPUTkac/xf956UBZNkP8Xn/rokR3M2fm+HNPZ9t0EOzdfdIYk7aUUoLqR73v9o9YiSGy5NSwOT+33MCpw==')
var aesKey = decrypt.decrypt(result.encrypted)
// 用aes秘钥进行解密
return eval('(' + cryptoJS.decrypt(result.requestData, aesKey) + ')')
}
创建CryptoJSUtils.js
import CryptoJS from 'crypto-js'
export default {
/**
@param {*需要加密的字符串 注:对象转化为json字符串再加密} word
@param {*aes加密需要的key值,这个key值后端同学会告诉你} keyStr
*/
encrypt (word, keyStr) { // 加密
let key = CryptoJS.enc.Utf8.parse(keyStr)
let srcs = CryptoJS.enc.Utf8.parse(word)
let encrypted = CryptoJS.AES.encrypt(srcs, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}) // 加密模式为ECB,补码方式为PKCS5Padding(也就是PKCS7)
return encrypted.toString()
},
decrypt (word, keyStr) { // 解密
let key = CryptoJS.enc.Utf8.parse(keyStr)
let decrypt = CryptoJS.AES.decrypt(word, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7})
return CryptoJS.enc.Utf8.stringify(decrypt).toString()
}
}
java后台
创建DecodeRequestBodyAdvice.java
package com.wisdom.iotSystem.exception;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.wisdom.iotSystem.annotations.SecurityParameter;
import com.wisdom.iotSystem.utils.AesEncryptUtils;
import com.wisdom.iotSystem.utils.RSAUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.Map;
/**
* @author wzw
* @desc 请求数据解密
* @date 2018/10/29 20:17
*/
@ControllerAdvice(basePackages = "com.wisdom.iotSystem.controller")
public class DecodeRequestBodyAdvice implements RequestBodyAdvice {
private static final Logger logger = LoggerFactory.getLogger(DecodeRequestBodyAdvice.class);
// @Value("${server.private.key}")
private String SERVER_PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIjbPLaSQvF2HHfWm7aBEH3lALHlo6/DnJO3jiCUQ8haFqipdV5NyH13ACNqgiebprUGJAcS0T9xxZM+YzsiS2uNzDeyILd1Xb9AmSmTz9vnZMwLD1HnmpcvX5jV/TPsEK7eKQJ/JPDlzECHmlrevRiFiZ4L4TondyuYE/MiZ9gLAgMBAAECgYBT5QnH5ctx1/TFpeKYs2/XrT2K0HpScfiXOSvAXwNaW5eOVyti3w3rk7qa+1zESQ+d4yDM0UVCvkze4ZzVEEXoyGV4q7HkaGhBYJeE9guWi81G4arsso8er5SvIcirAYGQykn9WvVssbsUjGe0P2Fan05RbGy9JnRpWXagyKMuqQJBAL4OgvQwoJ/djBgx5zRrJZzHySAP+Vr06ZF+6q2N+hBKG4g35Qqi7QWJxvJznlhNqqH58eWxl5Ypr0AmvjUKbl8CQQC4V0r4VBEvtorsCnilkoyiaNhITj/i3plKouKiwUf6Xvgkw2J1iqOKkZ1qlXxyUIzIeuasIIXnkJxjwN4xzt3VAkBW4X1dsYkL65QqT024+a4lAHNhs8uyl7jaKSGQmxGQNsBlQd/zP82INZZ7qPzeswpop0C8VrXMEFwrwEo9JvqTAkEAmVAwj/wLFy2wuMO0t6/8uw6L4wcBZ0RPJZ328/ngTUEzDBBcEPovLg4RaBXPnJuVmx9sPfgGpiLFjslXgwFTyQJAYzUrnA9eqmnLjTHLziCpuDuvaKa0Y/WMbieIGxnsIjXbMX1vtP3zIwr/ykrao5Ln+wv2yHwUSHeHlaxfO35xlg==";
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return body;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
try {
boolean encode = false;
if (methodParameter.getMethod().isAnnotationPresent(SecurityParameter.class)) {
//获取注解配置的包含和去除字段
SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
//入参是否需要解密
encode = serializedField.inDecode();
}
if (encode) {
return new MyHttpInputMessage(inputMessage);
}else{
return inputMessage;
}
} catch (Exception e) {
e.printStackTrace();
logger.error("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密出现异常:"+e.getMessage());
return inputMessage;
}
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return body;
}
class MyHttpInputMessage implements HttpInputMessage {
private HttpHeaders headers;
private InputStream body;
public MyHttpInputMessage(HttpInputMessage inputMessage) throws Exception {
this.headers = inputMessage.getHeaders();
this.body = IOUtils.toInputStream(easpString(IOUtils.toString(inputMessage.getBody(),"utf-8")));
}
@Override
public InputStream getBody() throws IOException {
return body;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
/**
*
* @param requestData
* @return
*/
public String easpString(String requestData) {
if(requestData != null && !requestData.equals("")){
Map<String,String> map = new Gson().fromJson(requestData,new TypeToken<Map<String,String>>() {
}.getType());
// 密文
String data = map.get("requestData");
// 加密的aes秘钥
String encrypted = map.get("encrypted");
if(StringUtils.isEmpty(data) || StringUtils.isEmpty(encrypted)){
throw new RuntimeException("参数【requestData】缺失异常!");
}else{
String content = null ;
String aseKey = null;
try {
aseKey = RSAUtils.decryptDataOnJava(encrypted,SERVER_PRIVATE_KEY);
}catch (Exception e){
throw new RuntimeException("参数【aseKey】解析异常!");
}
try {
content = AesEncryptUtils.decrypt(data, aseKey);
}catch (Exception e){
throw new RuntimeException("参数【content】解析异常!"+e);
}
if (StringUtils.isEmpty(content) || StringUtils.isEmpty(aseKey)){
throw new RuntimeException("参数【requestData】解析参数空指针异常!");
}
return content;
}
}
throw new RuntimeException("参数【requestData】不合法异常!");
}
}
}
创建EncodeResponseBodyAdvice.java
package com.wisdom.iotSystem.exception;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wisdom.iotSystem.annotations.SecurityParameter;
import com.wisdom.iotSystem.utils.AesEncryptUtils;
import com.wisdom.iotSystem.utils.RSAUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* @author monkey
* @desc 返回数据加密
* @date 2018/10/25 20:17
*/
@ControllerAdvice(basePackages = "com.wisdom.iotSystem.controller")
public class EncodeResponseBodyAdvice implements ResponseBodyAdvice {
private final static Logger logger = LoggerFactory.getLogger(EncodeResponseBodyAdvice.class);
// @Value("${client.public.key}")
private String CLIENT_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7NFH8t/vtZbmX+17bEIj1zkUbr+SgunnO6ZAKTXN3JBUH6Vo4QIjNMp+7ahpfMtc0UUfP4PHbFAqH0yzi9RCQfu8RL1AkpowekQ6MLVTsQPb0Qq68cFaDWPPNcfeBhPy59gOuMVbWxbKatLPYaT+BlNEF1iByjFf0swew7Dd+zwIDAQAB";
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
boolean encode = false;
if (methodParameter.getMethod().isAnnotationPresent(SecurityParameter.class)) {
//获取注解配置的包含和去除字段
SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
//出参是否需要加密
encode = serializedField.outEncode();
}
if (encode) {
// logger.info("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行加密");
ObjectMapper objectMapper = new ObjectMapper();
try {
String result = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(body);
// 生成aes秘钥
String aseKey = getRandomString(16);
// rsa加密
String encrypted = RSAUtils.encryptedDataOnJava(aseKey, CLIENT_PUBLIC_KEY);
// aes加密
String requestData = AesEncryptUtils.encrypt(result, aseKey);
Map<String, String> map = new HashMap<>();
map.put("encrypted", encrypted);
map.put("requestData", requestData);
return map;
} catch (Exception e) {
e.printStackTrace();
logger.error("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密出现异常:" + e.getMessage());
}
}
return body;
}
/**
* 创建指定位数的随机字符串
* @param length 表示生成字符串的长度
* @return 字符串
*/
public static String getRandomString(int length) {
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
}
创建接口:SecurityParameter
package com.wisdom.iotSystem.annotations;
import org.springframework.web.bind.annotation.Mapping;
import java.lang.annotation.*;
/**
* @author wzw
* @desc 请求数据解密
* @date 2018/10/25 20:17
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Mapping
@Documented
public @interface SecurityParameter {
/**
* 入参是否解密,默认解密
*/
boolean inDecode() default true;
/**
* 出参是否加密,默认加密
*/
boolean outEncode() default true;
}
测试
/**
* RSA+AES双重加密测试
*
* @return object
*/
@RequestMapping("/testEncrypt")
@ResponseBody
// 数据加密注解
@SecurityParameter
public R testEncrypt(@RequestBody Map<String,Object> info) {
String content = "内容";
info.put("name",nameTest);
return R.ok("请求成功").put("info",info);
}
网络安全工程师(白帽子)企业级学习路线
第一阶段:安全基础(入门)
第二阶段:Web渗透(初级网安工程师)
第三阶段:进阶部分(中级网络安全工程师)
如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!
学习资源分享
![](https://i-blog.csdnimg.cn/blog_migrate/8dbc2002c5d64726867869c5a612e461.jpeg)