实际项目中为了系统安全,我们经常需要对请求数据和响应数据做加密处理,这里以spring后台,vue前台的java web为例,记录一个实现过程
一、为什么要结合AES和RSA?
因为AES是对称加密,即加密解密用的秘钥是一样,这样一来AES的秘钥保管尤其重要,但是AES有个很好的优点,就是处理效率高。而RSA是不对称加密,即加密解密用的秘钥不一样,分别叫公钥和私钥,通常用公钥加密,然后用私钥解密,其中公钥可以公开,只需要保管好私钥即可,而相比AES而言RSA速度慢效率低。所以,通常我们结合这两种加密方式的优点来完成数据的安全传输。
二、AES和RSA的结合使用过程
1.前端随机动态生成aesKey:因为AES的加密解密秘钥需要一致,如果整个系统写死AES的秘钥会很不安全,所以每次请求动态生成aesKey会比较好
2.前端用RSA对动态aesKey加密:动态aesKey需要传到后端供解密,传输过程用RSA加密
3.前端保存动态aesKey:因为同一个请求的响应需要一样的aesKey解密,所以前端还得把动态aesKey保存下来,可以再随机生成一个id,然后按键值对的方式保存在前端变量中{id: aesKey}
4.把加密的aesKey和id放到请求头
5.后端用RSA私钥解密得明文aesKey:后端从请求头取出加密的aesKey,然后用私钥解密拿到明文的aesKey,然后对请求数据解密
6.后端用明文aesKey加密响应数据
7.后端把请求过来的id放到响应头
8.前端根据响应头的id,取到对应的aesKey,对响应数据解密
9.前端删除动态的aesKey
三、具体实现案例
1.前端实现AES和RSA的公共方法
aesUtils.js:
const CryptoJS = require('crypto-js')
function GetRandomNum (n) {
let chars = [
'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F',
'G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V',
'W','X','Y','Z','.','?','~','!','@','#','$','%','^','&','*']
if(n == null) {
n = 16
}
let res = ""
for(let i = 0; i < n ; i++) {
let id = Math.ceil(Math.random()*46)
res += chars[id]
}
return res
}
function GetUuid () {
let s = []
const hexDigits = '0123456789abcdef'
for (let i = 0; i < 36; i++) {
let indexStart = Math.floor(Math.random() * 0x10)
s[i] = hexDigits.substring(indexStart, indexStart+1)
}
s[14] = '4'
let indexStart = (s[19] & 0x3) | 0x8
s[19] = hexDigits.substring(indexStart, indexStart+1)
s[8] = s[13] = s[18] = s[23] = '-'
return s.join('')
}
function Decrypt (word, key, iv) {
let key = CryptoJS.enc.Utf8.parse(key)
let base64 = CryptoJS.enc.Base64.parse(word)
let src = CryptoJS.enc.Base64.stringify(base64)
var decrypt = CryptoJS.AES.decrypt(src, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.ZeroPadding })
var decryptedStr = decrypt.toString(CryptoJS.enc.Utf8)
return decryptedStr.toString()
}
function Encrypt (word, key, iv) {
let key = CryptoJS.enc.Utf8.parse(key)
let src = CryptoJS.enc.Utf8.parse(word)
var encrypted = CryptoJS.AES.encrypt(src, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.ZeroPadding })
return CryptoJS.enc.Base64.stringify(encrypted.ciphertext)
}
export default {
Decrypt,
Encrypt,
GetRandomNum,
GetUuid,
}
rsaUtils.js:
import JSEncrypt from 'jsencrypt'
const pubKey = `MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMYWwlqtkWIdA0I/54TP/k1VLgyNwzQB1IvrVKdNfobivHzN02VFGAED1hDSLDiSp4yYrFcXmMFReJJOJ1zjvWECAwEAAQ==`
const encrypt = new JSEncrypt()
encrypt.setPublicKey(pubKey)
function Encrypt(str) {
let data = encrypt.encrypt(str.toString())
return data
}
function Decrypt(str) {
let data = encrypt.decrypt(str.toString())
return data
}
export default {
Encrypt,
Decrypt,
}
2.前端在拦截请求处,生成AES随机秘钥并加密请求数据,再用RSA加密AES秘钥并放到请求头
fetch.js:
const aesKeys = {}
instance.interceptors.request.use(function(request) {
let randomKey = aes.GetRandomNum()
let reqData
if (request.data instanceof Object) {
reqData = JSON.stringify(request.data)
} else {
reqData = request.data
}
request.data = aes.Encrypt(reqData, randomKey, randomKey)
let uuid = aes.GetUuid()
let encryptAesKey = rsa.Encrypt(randomKey)
request.headers['EncryptAesKey'] = encryptAesKey
request.headers['uuid'] = uuid
aesKeys[uuid] = randomKey
return request
}, function(err) {
Message({
message: err,
type: 'error'
})
return Promise.reject(err)
})
instance.interceptors.response.use(function(response) {
try {
let uuid = response.headers['uuid']
let aesKey = aesKeys[uuid]
delete aesKeys[uuid]
return JSON.parse(aes.Decrypt(response.data, aesKey, aesKey))
} catch (e) {
return response.data
}
}, function(err) {
Message({
message: err,
type: 'error'
})
return Promise.reject(err)
})
3.后端实现AES和RSA的公共方法
AesUtils.java:
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class AesUtil {
public static String encrypt(String data, String key, String iv) throws Exception {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
int blockSize = cipher.getBlockSize();
byte[] dataBytes = data.getBytes();
int plaintextLength = dataBytes.length;
if (plaintextLength % blockSize != 0) {
plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
}
byte[] plaintext = new byte[plaintextLength];
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
byte[] encrypted = cipher.doFinal(plaintext);
return new Base64().encodeToString(encrypted);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String desEncrypt(String data, String key, String iv) throws Exception {
try {
byte[] encrypted1 = new Base64().decode(data);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original);
return originalString.trim();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String encryptWithKey(String data, String key) throws Exception {
return encrypt(data, key, key);
}
public static String desEncryptWithKey(String data, String key) throws Exception {
return desEncrypt(data, key, key);
}
}
RsaUtils.java:
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.springframework.util.Base64Utils;
import javax.crypto.Cipher;
public class RSAUtils {
/** 算法名称 */
private static final String ALGORITHM = "RSA";
/** 默认密钥大小 */
private static final int KEY_SIZE = 4096;
/** 密钥对生成器 */
private static KeyPairGenerator keyPairGenerator = null;
private static KeyFactory keyFactory = null;
/** 缓存的密钥对 */
private static KeyPair keyPair = null;
/** Base64 编码/解码器 JDK1.8 */
private static Base64.Decoder decoder = Base64.getDecoder();
private static Base64.Encoder encoder = Base64.getEncoder();
private static final String PRI_KEY = "MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAxhbCWq2RYh0DQj/nhM/+TVUuDI3DNAHUi+tUp01+huK8fM3TZUUYAQPWENIsOJKnjJisVxeYwVF4kk4nXOO9YQIDAQABAkEArOrHJBLpm0UKSDWyq2xJaEZYGVtSsD58xNtcHWN3dNRFWLZWZ9+D31OT0yE0T+dUhBVQFzHh3uDPd3Ax4STIwQIhAOPcBuFP4hoLcCPGvnvl+Co79XRKVkFtlduimiMzxg65AiEA3o2CjUz6TN51P8Q/kkPLHZHj4kB3ZPjNLNdKQusfj+kCIDe6z9/5psZR99p4OIybIYhK4+zOZaxY/ica7PIhLpbZAiEAupudZC2vktTVK2q6g0IlBd5WXlf/xMJ6B6ddtU7BYEECIEzbfR1Ac5zTrxTQ5icmD/ZRChgfhdxToGa21SQscW+K";
public static final String PEM_CODE_PUB = "MEEwDQYJKoZIhvcNAQEBBQADMAAwLQImDr5/bK6tmdEMYTJXsD/AXIOwE2a9/bfkPvtWUR7vzkvB33tPcEsCAwEAAQ==";
public static final String PEM_CODE_PRI = "";
/** 初始化密钥工厂 */
static {
try {
keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
keyFactory = KeyFactory.getInstance(ALGORITHM);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
private RSAUtils() {
}
public static synchronized Map<String, Object> generateKeyPair() {
try {
keyPairGenerator.initialize(KEY_SIZE,
new SecureRandom(UUID.randomUUID().toString().replaceAll("-", "").getBytes()));
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception e) {
e.printStackTrace();
}
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
String publicKeyString = encoder.encodeToString(rsaPublicKey.getEncoded());
String privateKeyString = encoder.encodeToString(rsaPrivateKey.getEncoded());
Map<String, Object> keyPairMap = new HashMap<String, Object>();
keyPairMap.put("public", publicKeyString);
keyPairMap.put("private", privateKeyString);
return keyPairMap;
}
public static synchronized Map<String, Object> generatePKS1KeyPair() {
Map<String, Object> keyPairMap = new HashMap<String, Object>();
try {
keyPairGenerator.initialize(KEY_SIZE,
new SecureRandom(UUID.randomUUID().toString().replaceAll("-", "").getBytes()));
keyPair = keyPairGenerator.generateKeyPair();
PublicKey pub = keyPair.getPublic();
byte[] pubBytes = pub.getEncoded();
SubjectPublicKeyInfo spkInfo = SubjectPublicKeyInfo.getInstance(pubBytes);
ASN1Primitive pubprimitive = spkInfo.parsePublicKey();
byte[] publicKeyPKCS1 = pubprimitive.getEncoded();
System.out.println(publicKeyPKCS1.toString());
PrivateKey priv = keyPair.getPrivate();
byte[] privBytes = priv.getEncoded();
PrivateKeyInfo pkInfo = PrivateKeyInfo.getInstance(privBytes);
ASN1Encodable encodable = pkInfo.parsePrivateKey();
ASN1Primitive priprimitive = encodable.toASN1Primitive();
byte[] privateKeyPKCS1 = priprimitive.getEncoded();
System.out.println(privateKeyPKCS1.toString());
keyPairMap.put("public", publicKeyPKCS1);
keyPairMap.put("private", privateKeyPKCS1.toString());
} catch (Exception e) {
e.printStackTrace();
}
return keyPairMap;
}
public static PublicKey getPublicKey(String pubKey) {
try {
byte[] keyBytes = decoder.decode(pubKey);
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes);
return keyFactory.generatePublic(x509EncodedKeySpec);
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
return null;
}
public static PrivateKey getPrivateKey(String priKey) {
try {
byte[] keyBytes = decoder.decode(priKey);
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
return (java.security.interfaces.RSAPrivateKey) keyFactory
.generatePrivate(pkcs8EncodedKeySpec);
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
return null;
}
public static String encryptByPublic(byte[] content, PublicKey publicKey) {
if (publicKey == null) {
publicKey = (PublicKey) getPublicKey(PEM_CODE_PUB);
}
try {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
//该密钥能够加密的最大字节长度
int splitLength = ((RSAPublicKey) publicKey).getModulus().bitLength() / 8 - 11;
byte[][] arrays = splitBytes(content, splitLength);
StringBuffer stringBuffer = new StringBuffer();
for (byte[] array : arrays) {
stringBuffer.append(Base64Utils.encodeToString(cipher.doFinal(array)));
}
return stringBuffer.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String encryptByPrivate(byte[] content, PrivateKey privateKey) {
if (privateKey == null) {
privateKey = (PrivateKey) getPrivateKey(PEM_CODE_PRI);
}
try {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
//该密钥能够加密的最大字节长度
int splitLength = ((RSAPrivateKey) privateKey).getModulus().bitLength() / 8 - 11;
byte[][] arrays = splitBytes(content, splitLength);
StringBuffer stringBuffer = new StringBuffer();
for (byte[] array : arrays) {
stringBuffer.append(Base64Utils.encodeToString(cipher.doFinal(array)));
}
return stringBuffer.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String decryptByPrivate(String content, PrivateKey privateKey) {
if (privateKey == null) {
privateKey = (PrivateKey) getPrivateKey(PEM_CODE_PRI);
}
try {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
//该密钥能够加密的最大字节长度
int splitLength = ((RSAPrivateKey) privateKey).getModulus().bitLength() / 8;
byte[] contentBytes = Base64Utils.decodeFromString(content);
byte[][] arrays = splitBytes(contentBytes, splitLength);
StringBuffer stringBuffer = new StringBuffer();
for (byte[] array : arrays) {
stringBuffer.append(new String(cipher.doFinal(array)));
}
return stringBuffer.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static byte[][] splitBytes(byte[] bytes, int splitLength) {
//bytes与splitLength的余数
int remainder = bytes.length % splitLength;
//数据拆分后的组数,余数不为0时加1
int quotient = remainder != 0 ? bytes.length / splitLength + 1
: bytes.length / splitLength;
byte[][] arrays = new byte[quotient][];
byte[] array = null;
for (int i = 0; i < quotient; i++) {
//如果是最后一组(quotient-1),同时余数不等于0,就将最后一组设置为remainder的长度
if (i == quotient - 1 && remainder != 0) {
array = new byte[remainder];
System.arraycopy(bytes, i * splitLength, array, 0, remainder);
} else {
array = new byte[splitLength];
System.arraycopy(bytes, i * splitLength, array, 0, splitLength);
}
arrays[i] = array;
}
return arrays;
}
}
4.后端重写HttpServletRequestWrapper中的getInputStream(),解密拿到请求的明文
import org.apache.commons.lang3.StringUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class XssRequestWrappers extends HttpServletRequestWrapper {
public XssRequestWrappers(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
Map<String, String> headerMap = getHeadersInfo((HttpServletRequest) super.getRequest());
String encryptAeskey = headerMap.get("encryptaeskey");
String aesKey = RSAUtils.decryptByPrivate(encryptAeskey, RSAUtils.getPrivateKey(CommConstants.RSA.PRI_KEY));
ServletInputStream servletInputStream = super.getInputStream();
try {
return decryptReqData(servletInputStream, aesKey);
} catch (Exception e) {
e.printStackTrace();
}
return servletInputStream;
}
private ServletInputStream decryptReqData(ServletInputStream servletInputStream, String aesKey)
throws Exception {
StringBuilder jb = new StringBuilder();
String line;
BufferedReader reader = new BufferedReader(new InputStreamReader(servletInputStream));
while ((line = reader.readLine()) != null) {
jb.append(line);
}
String encryptReqData = jb.toString();
if(StringUtils.isNotBlank(encryptReqData)) {
String reqData = AesUtil.desEncryptWithKey(encryptReqData, aesKey);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(reqData.getBytes(StandardCharsets.UTF_8));
return new ServletInputStream() {
@Override
public int read() {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
};
}
return servletInputStream;
}
private Map<String, String> getHeadersInfo(HttpServletRequest request) {
Map<String, String> map = new HashMap<>();
Enumeration<String> headerNames = request.getHeaderNames();
Locale.setDefault(Locale.ENGLISH);
while (headerNames.hasMoreElements()) {
String key = headerNames.nextElement().toLowerCase(Locale.ENGLISH);
String value = request.getHeader(key);
map.put(key, value);
}
return map;
}
}
5.通过ResponseBodyAdvice给响应数据加密处理
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.annotation.ControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
@ControllerAdvice
public class ResponseBodyAdvice implements org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice<Object> {
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
try {
ServletServerHttpRequest request = (ServletServerHttpRequest)serverHttpRequest;
HttpServletRequest servletRequest = request.getServletRequest();
Map<String, String> headerNames = RequestUtils.getHeadersInfo(servletRequest);
String encryptAeskey = headerNames.get("encryptaeskey");
String aesKey = RSAUtils.decryptByPrivate(encryptAeskey, RSAUtils.getPrivateKey(CommConstants.RSA.PRI_KEY));
String uuid = headerNames.get("uuid");
serverHttpResponse.getHeaders().add("uuid", uuid);
ArrayList<String> list = new ArrayList<>();
list.add("uuid");
serverHttpResponse.getHeaders().setAccessControlExposeHeaders(list);
return AesUtil.encryptWithKey(String.valueOf(o), aesKey);
} catch (Exception e) {
e.printStackTrace();
}
return o;
}
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
}