实际项目中为了系统安全,我们经常需要对请求数据和响应数据做加密处理,这里以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
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
keyPairMap = new HashMap(); - keyPairMap.put("public", publicKeyString);
- keyPairMap.put("private", privateKeyString);
- return keyPairMap;
- }
-
- public static synchronized Map
generatePKS1KeyPair() { - Map
keyPairMap = new HashMap(); - 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
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
getHeadersInfo(HttpServletRequest request) { - Map
map = new HashMap<>(); - Enumeration
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
-
- @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
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
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;
- }
- }