【前后端接口AES+RSA混合加解密详解(vue+SpringBoot)附完整源码】

前后端接口AES+RSA混合加解密

     为什么需要对前后端接口加解密?主要是甲方要求。

一、AES加密原理和为什么不使用AES加密

  AES是最常见的对称加密算法,加密和解密用到的密钥是相同的,这种加密方式加密速度非常快,适合经常发送数据的场合,且加密长文本比较方便。缺点是密钥的传输比较麻烦,只能将一把公钥分别在前后端代码中各存放一份,还有一个遇到的问题下面会讲到,那就是美国对AES加密密钥长度的限制。优点是加密效率高,缺点是安全性低。

二、RSA加密原理和为什么不使用rsa加密

RSA也就是非对称加密算法,是使用不同密钥进行加密和解密的算法,也称为公私钥加密。公钥和私钥是同时生成的,公钥用来加密,私钥用来解密,加解密的密钥是成对出现。但是不推荐用RSA加密请求报文,因为RSA加密后的报文会很长,经常会出现超过请求体长度限制。优点是安全性高,缺点是RSA的加密效率低。

三、AES和RSA混合加密的原理

我们可以结合两者的优点,AES的加密效率高,那我们可以使用aes来加密报文,而RSA的灵活性和安全性来加密aes的密钥。总的思路就是利用RSA来加密传输AES的密钥,用 AES的密钥来加密请求报文。

四、代码样例

前端

1. 请求增加加密标识
  首先这个功能我们的出发点是可以灵活配置,所以我们在需要加密的请求头添加一个加密标识代码如下:
import axios from '@common/plugins/Axios'

saveStudent: data => {
        return axios.request({
            url: `/demo/saveStudent`,
            method: 'post',
            headers: {
            //需要加密的请求在头部塞入标识
                isEncrypt: 1
            },
            data
        })
    }
2. 前端加密工具类

import JSEncrypt from 'jsencrypt'
import CryptoJS from 'crypto-js'

//
// 加密
export function rsaEncrypt(Str, afterPublicKey) {
    const encryptor = new JSEncrypt()
    encryptor.setPublicKey(afterPublicKey) // 设置公钥
    return encryptor.encrypt(Str) // 对数据进行加密
}

// 解密
export function rsaDecrypt(Str, frontPrivateKey) {
    const encryptor = new JSEncrypt()
    encryptor.setPrivateKey(frontPrivateKey) // 设置私钥
    return encryptor.decrypt(Str) // 对数据进行解密
}

export function aesEncrypt(aeskey, Str) {
    // 设置一个默认值,如果第二个参数为空采用默认值,不为空则采用新设置的密钥
    var key = CryptoJS.enc.Utf8.parse(aeskey)
    var srcs = CryptoJS.enc.Utf8.parse(Str)
    var encrypted = CryptoJS.AES.encrypt(srcs, key, {
    // 切记   需要和后端算法模式一致
        mode: CryptoJS.mode.ECB,
        padding: CryptoJS.pad.Pkcs7
    })

    return encrypted.toString()
}

export function aesDecrypt(aeskey, Str) {
    var key = CryptoJS.enc.Utf8.parse(aeskey)
    var decrypt = CryptoJS.AES.decrypt(Str, key, {
     // 切记   需要和后端算法模式一致
        mode: CryptoJS.mode.ECB,
        padding: CryptoJS.pad.Pkcs7
    })
    return CryptoJS.enc.Utf8.stringify(decrypt).toString()
}
/**
 * 获取16位随机码AES
 * @returns {string}
 */
export function get16RandomNum() {
    var 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','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'
    ]
    var nums = ''
    //这个地方切记要选择16位,因为美国对密钥长度有限制,选择32位的话加解密会报错,需要根据jdk版本去修改相关jar包,有点恼火,选择16位就不用处理。
    for (var i = 0; i < 16; i++) {
        var id = parseInt(Math.random() * 61)
        nums += chars[id]
    }
    return nums
}
//获取rsa密钥对
export function getRsaKeys() {
    return new Promise((resolve, reject) => {
        window.crypto.subtle
            .generateKey(
                {
                    name: 'RSA-OAEP',
                    modulusLength: 2048, //can be 1024, 2048, or 4096
                    publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
                    hash: { name: 'SHA-512' } //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
                },
                true, //whether the key is extractable (i.e. can be used in exportKey)
                ['encrypt', 'decrypt'] //must be ["encrypt", "decrypt"] or ["wrapKey", "unwrapKey"]
            )
            .then(function(key) {
                window.crypto.subtle
                    .exportKey('pkcs8', key.privateKey)
                    .then(function(keydata1) {
                        window.crypto.subtle
                            .exportKey('spki', key.publicKey)
                            .then(function(keydata2) {
                                var privateKey = RSA2text(keydata1, 1)

                                var publicKey = RSA2text(keydata2)

                                resolve({ privateKey, publicKey })
                            })
                            .catch(function(err) {
                                reject(err)
                            })
                    })
                    .catch(function(err) {
                        reject(err)
                    })
            })
            .catch(function(err) {
                reject(err)
            })
    })
}
function RSA2text(buffer, isPrivate = 0) {
    var binary = ''
    var bytes = new Uint8Array(buffer)
    var len = bytes.byteLength
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode(bytes[i])
    }
    var base64 = window.btoa(binary)

    let text = base64.replace(/[^\x00-\xff]/g, '$&\x01').replace(/.{64}\x01?/g, '$&\n')

    return text
}
3.前端axios请求统一封装,和返回统一封装
import Axios from 'axios'
import { getRsaKeys, rsaEncrypt, rsaDecrypt, aesDecrypt, aesEncrypt, get32RandomNum } from '@common/util'
/**
 * axios实例
 * @type {AxiosInstance}
 */
const instance = Axios.create({
    headers: {
        x_requested_with: 'XMLHttpRequest'
    }
})
let frontPrivateKey
/**
 * axios请求过滤器
 */
instance.interceptors.request.use(
    async config => {
        if (sessionStorage.getItem('X-Access-Token')) {
            // 判断是否存在token,如果存在的话,则每个http header都加上token
            config.headers['X-Access-Token'] = sessionStorage.getItem('X-Access-Token')
        }
        if (config.headers['isEncrypt']) {
            config.headers['Content-Type'] = 'application/json;charset=utf-8'
            if (config.method === 'post' || config.method === 'put') {
                const { privateKey, publicKey } = await getRsaKeys()
                let afterPublicKey = sessionStorage.getItem('afterPublicKey')
                frontPrivateKey = privateKey
                //每次请求生成aeskey
                let aesKey = get16RandomNum()
               //用登陆后后端生成并返回给前端的的RSA密钥对的公钥将AES16位密钥进行加密
                let aesKeyByRsa = rsaEncrypt(aesKey, afterPublicKey)
               //使用AES16位的密钥将请求报文加密(使用的是加密前的aes密钥)
                if (config.data) {
                    let data = aesEncrypt(aesKey, JSON.stringify(config.data))
                    config.data = {
                        data: data,
                        aeskey: aesKeyByRsa,
                        frontPublicKey: publicKey
                    }
                }

                if (config.params) {
                    let data = aesEncrypt(aesKey, JSON.stringify(config.params))
                    config.params = {
                        params: data,
                        aeskey: aesKeyByRsa,
                        frontPublicKey: publicKey
                    }
                }
            }
        }

        config.url ="你的后端接口请求地址" + config.url

        return config
    },
    err => {
        return Promise.reject(err)
    }
)
/**
 * axios响应过滤器
 */
instance.interceptors.response.use(
    response => {
        //后端返回的通过rsa加密后的aes密钥
        let aesKeyByRsa = response.data.aesKeyByRsa
       
        if (aesKeyByRsa) {
        //通过rsa的私钥对后端返回的加密的aeskey进行解密
            let aesKey = rsaDecrypt(aesKeyByRsa, frontPrivateKey)
            //使用解密后的aeskey对加密的返回报文进行解密
            response.data.data = JSON.parse(JSON.parse(aesDecrypt(aesKey, response.data.data)))
         
            return response.data
        }else{ 
           return response
           }      
        }
    },
    error => {
        if (error.response.status === 500) {
            const { data } = error.response
            if (data !== null && data !== undefined) {
                if (error.response.data.trace.includes('账号已被冻结或注销,请联系管理员!')) 
                    doLogout('账号已被冻结或注销,请联系管理员!')
                }
                if (error.response.data.trace.includes('用户不存在!')) {
                    doLogout('用户不存在!')
                }
                if (error.response.data.trace.includes('Token失效')) {
                    doLogout('Token失效,请重新登录!')
                }
            } else {
                Notification.error({
                    title: '提示',
                    message: error.response.data.error
                })
            }
        } else {
            return Promise.reject(error.response)
        }
    }
)

export default instance

后端

1.后端加密工具类
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Random;

/**
 * @ClassName AESUtils   一定要选择16位密钥长度,也就是KEY_LENGTH=16*8,36的话就需要修改环境的jar包。
 * @Description TODO
 * @Author L
 * @Date 2024/1/26 17:27
 */
public class AESUtils {
    /**
     * 加密算法AES
     */
    private static final String KEY_ALGORITHM = "AES";

    /**
     * key的长度,Wrong key size: must be equal to 128, 192 or 256
     * 传入时需要16、24、36
     */
    private static final int KEY_LENGTH = 16 * 8;

    /**
     * 算法名称/加密模式/数据填充方式
     * 默认:AES/ECB/PKCS5Padding
     */
    private static final String ALGORITHMS = "AES/ECB/PKCS5Padding";

    /**
     * 后端AES的key,由静态代码块赋值
     */
    public static String key;


    static {
        key = getKey();
    }

    /**
     * 获取key
     */
    public static String getKey() {
        int length = KEY_LENGTH / 8;
        StringBuilder uid = new StringBuilder(length);
        //产生32位的强随机数
        Random rd = new SecureRandom();
        for (int i = 0; i < length; i++) {
            //产生0-2的3位随机数
            switch (rd.nextInt(3)) {
                case 0:
                    //0-9的随机数
                    uid.append(rd.nextInt(10));
                    break;
                case 1:
                    //ASCII在65-90之间为大写,获取大写随机
                    uid.append((char) (rd.nextInt(26) + 65));
                    break;
                case 2:
                    //ASCII在97-122之间为小写,获取小写随机
                    uid.append((char) (rd.nextInt(26) + 97));
                    break;
                default:
                    break;
            }
        }
        return uid.toString();
    }

    /**
     * AES 加密
     *
     * @param content    加密的字符串
     * @param encryptKey key值
     */
    public static String encrypt(String content, String encryptKey) throws Exception {
        //设置Cipher对象
        Cipher cipher = Cipher.getInstance(ALGORITHMS);
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), KEY_ALGORITHM));

        //调用doFinal
        // 转base64
        return Base64.encodeBase64String(cipher.doFinal(content.getBytes(StandardCharsets.UTF_8)));

    }

    /**
     * AES 解密
     *
     * @param encryptStr 解密的字符串
     * @param decryptKey 解密的key值
     */
    public static String decrypt(String encryptStr, String decryptKey) throws Exception {
        //base64格式的key字符串转byte
        byte[] decodeBase64 = Base64.decodeBase64(encryptStr);


        //设置Cipher对象
        Cipher cipher = Cipher.getInstance(ALGORITHMS);
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), KEY_ALGORITHM));
        //调用doFinal解密
        return new String(cipher.doFinal(decodeBase64));

}
}
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @ClassName RSAUtils
 * @Description TODO
 * @Author L
 * @Date 2024/1/26 17:28
 */
public class RSAUtils {

    /**
     * 加密算法RSA
     */
    private static final String KEY_ALGORITHM = "RSA";

    /**
     * 算法名称/加密模式/数据填充方式
     * 默认:RSA/ECB/PKCS1Padding
     */
    private static final String ALGORITHMS = "RSA/ECB/PKCS1Padding";

    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 245;

    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 256;

    /**
     * RSA 位数 如果采用2048 上面最大加密和最大解密则须填写:  245 256
     */
    private static final int INITIALIZE_LENGTH = 2048;

    /**
     * 后端RSA的密钥对(公钥和私钥)Map,由静态代码块赋值
     */
    private static final Map<String, String> map = new LinkedHashMap<>(2);

    /**
     * 生成密钥对(公钥和私钥)
     */

    public static Map<String,String> genKeyPair() throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(INITIALIZE_LENGTH);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        // 获取公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        // 获取私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        // 得到公钥字符串
        String publicKeyString = Base64.encodeBase64String(publicKey.getEncoded());
        // 得到私钥字符串
        String privateKeyString = Base64.encodeBase64String((privateKey.getEncoded()));
        map.put("publicKey",publicKeyString);
        map.put("privateKey",privateKeyString);
        return map;
    }
    public static  String getPrivateKey(){
        return map.get("privateKey");
    }
    public  static  String getPublicKey(){
        return  map.get("publicKey");
    }
    /**
     * RSA私钥解密
     * @param data BASE64编码过的密文
     * @param privateKey 私钥(BASE64编码)
     * @return utf-8编码的明文
     */
    public static byte[] decryptByPrivateKey(byte[] data, String privateKey) throws Exception {
        //base64格式的key字符串转Key对象
        Key privateK = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)));
        Cipher cipher = Cipher.getInstance(ALGORITHMS);
        cipher.init(Cipher.DECRYPT_MODE, privateK);

        //分段进行解密操作
        return encryptAndDecryptOfSubsection(data, cipher, MAX_DECRYPT_BLOCK);
    }

    /**
     * RSA公钥加密
     * @param data BASE64编码过的密文
     * @param publicKey 公钥(BASE64编码)
     * @return utf-8编码的明文
     */
    public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
        //base64格式的key字符串转Key对象
        Key publicK = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(publicKey)));
        Cipher cipher = Cipher.getInstance(ALGORITHMS);
        cipher.init(Cipher.ENCRYPT_MODE, publicK);
        //分段进行加密操作
        return encryptAndDecryptOfSubsection(data, cipher, MAX_ENCRYPT_BLOCK);
    }

    /**
     * RSA公钥解密
     * @param data BASE64编码过的密文
     * @param publicKey RSA公钥
     * @return utf-8编码的明文
     */
    public static byte[] pubKeyDec(byte[] data, String publicKey) throws Exception {
        //base64格式的key字符串转Key对象
        Key privateK = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(publicKey)));
        Cipher cipher = Cipher.getInstance(ALGORITHMS);
        cipher.init(Cipher.DECRYPT_MODE, privateK);

        //分段进行解密操作
        return encryptAndDecryptOfSubsection(data, cipher, MAX_DECRYPT_BLOCK);
    }


    /**
     * RSA私钥加密
     * @param data 待加密的明文
     * @param privateKey RSA私钥
     * @return  经BASE64编码后的密文
     */
    public static byte[] privKeyEnc(byte[] data, String privateKey) throws Exception {

        //base64格式的key字符串转Key对象
        Key publicK = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)));
        Cipher cipher = Cipher.getInstance(ALGORITHMS);
        cipher.init(Cipher.ENCRYPT_MODE, publicK);

        //分段进行加密操作
        return encryptAndDecryptOfSubsection(data, cipher, MAX_ENCRYPT_BLOCK);
    }

    /**
     * 分段进行加密、解密操作
     */
    private static byte[] encryptAndDecryptOfSubsection(byte[] data, Cipher cipher, int encryptBlock) throws Exception {
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > encryptBlock) {
                cache = cipher.doFinal(data, offSet, encryptBlock);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * encryptBlock;
        }
        out.close();
        return out.toByteArray();
    }

}
import org.apache.commons.codec.binary.Base64;
/**
 * @ClassName ApiSecurityUtils
 * @Description TODO
 * @Author L
 * @Date 2024/1/26 17:39
 */
public class ApiSecurityUtils {
    /**
     *
     * @param aesKeyByRsa 经过rsa加密的aeskey
     * @param decryptStr 经过aes加密的数据
     * @return 解密后的数据
     */
    public  static String decrypt(String aesKeyByRsa,String decryptStr,String privateKey) throws Exception {
        byte[] bytes = RSAUtils.decryptByPrivateKey(Base64.decodeBase64(aesKeyByRsa), privateKey);
        String aesKey = new String(bytes);
        return AESUtils.decrypt(decryptStr, aesKey);
    }

    /**
     *
     * @param encryptStr 要加密的数据
     * @param frontPublicKey  前端公钥
     * @return 加密后的数据
     */
    public static ApiEncryptRes encrypt(String encryptStr, String frontPublicKey) throws Exception {
        String aesKey = AESUtils.getKey();
        String data = AESUtils.encrypt(encryptStr, aesKey);
        ApiEncryptRes apiEncryptRes = new ApiEncryptRes();
        String aesKeyByRsa = Base64.encodeBase64String(RSAUtils.encryptByPublicKey(aesKey.getBytes(), frontPublicKey));
        apiEncryptRes.setAesKeyByRsa(aesKeyByRsa);
        apiEncryptRes.setData(data);
        return apiEncryptRes;
    }

}


import java.security.MessageDigest;

public class MD5Util {
	//十六进制下数字到字符的映射数组
	private final static String[] hexDigits = {"0", "1", "2", "3", "4",
			"5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};


	/**
	 * 把inputString加密
	 */
	public static String md5(String inputString) {
		return encodeByMD5(inputString);
	}
	/**
	 * 对字符串进行MD5加密
	 */
	private static String encodeByMD5(String originString) {
		if (originString != null) {
			try {
				//创建具有指定算法名称的信息摘要
				MessageDigest md = MessageDigest.getInstance("MD5");
				//使用指定的字节数组对摘要进行最后更新,然后完成摘要计算
				byte[] results = md.digest(originString.getBytes("utf-8"));
				//将得到的字节数组变成字符串返回
				String resultString = byteArrayToHexString(results);
				return resultString.toUpperCase();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}
		return null;
	}
	/**
	 * 转换字节数组为十六进制字符串
	 *
	 * @param
	 * @return 十六进制字符串
	 */
	private static String byteArrayToHexString(byte[] b) {
		StringBuffer resultSb = new StringBuffer();
		for (int i = 0; i < b.length; i++) {
			resultSb.append(byteToHexString(b[i]));
		}
		return resultSb.toString();
	}

	private static String byteToHexString(byte b) {
		int n = b;
		if (n < 0) {
			n += 256;
		}
		int d1 = n / 16;
		int d2 = n % 16;
		return hexDigits[d1] + hexDigits[d2];
	}

}

2. 请求的返回实体封装
import lombok.Data;

/**
 * @ClassName ApiEncryptRes  用于返回前端解密返回体的aeskey和返回体
 * @Description TODO
 * @Author L
 * @Date 2024/1/26 17:51
 */
@Data
public class ApiEncryptRes {
    private String aesKeyByRsa;
    private String data;
    private String frontPublicKey;

}

3. 配置过滤器对请求进行加解密操作
import cn.hutool.core.convert.Convert;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSONObject;
import com.l.common.encryptAPI.utils.ApiSecurityUtils;
import com.l.common.encryptAPI.util.MD5Util;
import com.l.common.encryptAPI.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
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.*;


import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.http.ContentType;


import lombok.SneakyThrows;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.util.WebUtils;


import java.util.TreeMap;

/**
 * @ClassName requestWrapper
 * @Description TODO
 * @Author L
 * @Date 2024/1/31 17:58
 */

/**
 * 确保request可以多次读取
 */

@Slf4j
public class EncryptRequestWrapper extends HttpServletRequestWrapper {



    //2024-02-02由于在过滤器中无法直接通过Autowired获取Bean,因此需要通过spring上下文来获取IOC管理的实体类
//    @Autowired
//    private RedisUtil redisUtil;

    protected FastByteArrayOutputStream cachedContent;
    protected String JSPublicKey;
    protected EncryptRequestWrapper(HttpServletRequest request ) {

        super(request);
        RedisUtil redisUtils =getBean(RedisUtil.class, request);
        this.copyBody(redisUtils);


    }

    /**
     *
     * 此处主要是用于读取 RedisUtil
     * @param clazz
     * @param request
     * @param <T>
     * @return
     */
    public <T> T getBean(Class<T> clazz, HttpServletRequest request){
        WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
        return applicationContext.getBean(clazz);
    }

    public static EncryptRequestWrapper newOrGetMultiReadHttpServletRequest(HttpServletRequest request) {
        EncryptRequestWrapper multiReadHttpServletRequest = getMultiReadHttpServletRequest(request);
        if (null != multiReadHttpServletRequest) {
            return multiReadHttpServletRequest;
        } else {
            String contentType = request.getContentType();
            if (contentType != null && contentType.contains(ContentType.MULTIPART.getValue())) {
                // 将转化后的 request 放入过滤链中
                request = new StandardServletMultipartResolver().resolveMultipart(request);
            }
            return new EncryptRequestWrapper(request);
        }
    }

    public static EncryptRequestWrapper getMultiReadHttpServletRequest(HttpServletRequest request) {
        EncryptRequestWrapper nativeRequest = WebUtils.getNativeRequest(request, EncryptRequestWrapper.class);
        if (null != nativeRequest) {
            return nativeRequest;
        } else if (request instanceof EncryptRequestWrapper) {
            return (EncryptRequestWrapper) request;
        } else {
            return null;
        }
    }
//重新读取请求,因为request只可以被读取一次,需要重新设置为可多次读取
    @SneakyThrows
    protected void copyBody(RedisUtil redisUtils) {
        int length = this.getContentLength();
        if (length > 0) {
            cachedContent = IoUtil.read(getRequest().getInputStream());
            if (StringUtils.isNotBlank(this.getHeader("Isencrypt"))&&StringUtils.isNotBlank(this.getHeader("X-Access-Token"))) {
                String body = new String(cachedContent.toByteArray());
                log.info("------------------- body = " + body + "------------------------");
                if (StringUtils.isNotBlank(body)) {
                    JSONObject jsonBody = JSONObject.parseObject(body);
                    if(null != jsonBody){
                    String dataEncrypt = jsonBody.getString("data");
                    String aeskey = jsonBody.getString("aeskey");
                    JSPublicKey = jsonBody.getString("frontPublicKey");
                    String data;
                    JSONObject json = null;
                    log.info("------------------- dataEncrypt = " + dataEncrypt + "------------------------");
                    log.info("------------------- aesKey = " + aeskey + "------------------------");
                        String token = this.getHeader("X-Access-Token");
                        String md5Token = MD5Util.md5(token);
                        String privateKey = Convert.toStr(redisUtils.get(md5Token + "privateKey"));
                        data = ApiSecurityUtils.decrypt(aeskey, dataEncrypt,privateKey);
                    // 如果数据不为空就编译
                    if (StringUtils.isNotBlank(data)) {
                        //如果参数为空前端传回undefined
                        if ("undefined".equalsIgnoreCase(data)) {
                            json = new JSONObject();
                        } else {
                            json = JSONObject.parseObject(data);
                        }
                    }
                        if (json != null) {
                            body = json.toJSONString();
                        }
                }
                    log.info("------------------- body = " + body + "------------------------");
                    cachedContent.reset();
                    cachedContent.write( body.getBytes(), 0,  body.getBytes().length);

            }
            }
        } else {
            cachedContent = new FastByteArrayOutputStream();
        }
    }

    @Override
    public int getContentLength() {
        if (null != cachedContent) {
            return cachedContent.size();
        }
        return super.getContentLength();
    }

    public byte[] getByteArrayBody() {
        return cachedContent.toByteArray();
    }

    public String getBody() {
        String body = null;
        if (cachedContent != null && cachedContent.size() > 0) {
            body = new String(cachedContent.toByteArray());
        }
        return body;
    }

    public String getSortBody() {
        return JSONObject.toJSONString(this.getBody(TreeMap.class));
    }

    public <T> T getBody(Class<T> type) {
        return cn.hutool.json.JSONUtil.toBean(this.getBody(), type);
    }

//这个方法为获取前端给后端用于加密aeskey的rsa公钥
    public  String getJSPublicKey(){
        return JSPublicKey;
    }
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (this.getContentLength() < 1) {
            return super.getInputStream();
        }
        return new ResettableServletInputStream(new ByteArrayInputStream(cachedContent.toByteArray()));
    }

    private class ResettableServletInputStream extends ServletInputStream {

        private final InputStream sourceStream;

        private boolean finished = false;


        /**
         * Create a DelegatingServletInputStream for the given source stream.
         *
         * @param sourceStream the source stream (never {@code null})
         */
        public ResettableServletInputStream(InputStream sourceStream) {
            Assert.notNull(sourceStream, "Source InputStream must not be null");
            this.sourceStream = sourceStream;
        }

        /**
         * Return the underlying source stream (never {@code null}).
         */
        public final InputStream getSourceStream() {
            return this.sourceStream;
        }


        @Override
        public int read() throws IOException {
            int data = this.sourceStream.read();
            if (data == -1) {
                this.finished = true;
            }
            return data;
        }

        @Override
        public int available() throws IOException {
            return this.sourceStream.available();
        }

        @Override
        public void close() throws IOException {
            super.close();
            this.sourceStream.close();
        }

        @Override
        public boolean isFinished() {
            return finished;
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(ReadListener readListener) {

        }
    }



}

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;

import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
/** * 返回值输出代理类
 * @ClassName ResponseWrapper
 * @Description TODO
 * @Author L
 * @Date 2024/1/31 18:58
 */

@Slf4j
public class ResponseWrapper extends HttpServletResponseWrapper {
    ByteArrayOutputStream _stream = new ByteArrayOutputStream();
    PrintWriter _pw = new PrintWriter(_stream);

    public ResponseWrapper(HttpServletResponse resp) throws IOException {
        super(resp);
    }

    /**
     * 覆盖getWriter()方法,将字符流缓冲到本地
     */
    @Override
    public PrintWriter getWriter() throws IOException {
        return _pw;
    }

    /**
     * 覆盖getOutputStream()方法,将字节流缓冲到本地
     */
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return new ServletOutputStream() {
            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setWriteListener(WriteListener writeListener) {

            }

            @Override
            public void write(int b) throws IOException {
                _stream.write(b);
            }
        };
    }

    /**
     * 把缓冲区内容写入输出流后关闭
     *
     * @author xxj
     */
    public void flush() {
        try {
            _pw.flush();
            _pw.close();
            _stream.flush();
            _stream.close();
        } catch (IOException e) {
            log.error("", e);
        }
    }

    /**
     * 获取字节流
     *
     * @return
     */
    public ByteArrayOutputStream getByteArrayOutputStream() {
        return _stream;
    }

    /**
     * 将换出区内容转为文本输出
     *
     * @return
     */
    public String getTextContent() {
        flush();
        return _stream.toString();
    }

    public static boolean isTextContentType(String contentType) {
        boolean flag = false;
        if (StrUtil.isBlank(contentType)) {
            return false;
        } else {
            flag = StrUtil.startWithIgnoreCase(contentType, "text");
            if (!flag) {
                flag = isJsonContentType(contentType);
            }
        }
        return flag;
    }

    public static boolean isJsonContentType(String contentType) {
        return !StrUtil.isBlank(contentType)
                && (StrUtil.containsIgnoreCase(contentType, "application/problem+json")
                || StrUtil.containsIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE));
    }
}

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.l.common.encryptAPI.pojo.ApiEncryptRes;
import com.l.common.encryptAPI.utils.ApiSecurityUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.web.servlet.filter.OrderedFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;

@Slf4j
@Component
public class EncryptApiFilter extends OncePerRequestFilter implements OrderedFilter {
    public static final int DEFAULT_ORDER = Integer.MAX_VALUE;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String servletPath = request.getServletPath();

        ResponseWrapper wrapper = new ResponseWrapper(response);
        String requestContent = null;

        if (ServletUtil.isMultipart(request)) {
        } else {
            MultiReadHttpServletRequestWrapper multiReadHttpServletRequestWrapper = MultiReadHttpServletRequestWrapper.newOrGetMultiReadHttpServletRequest(request);
            requestContent = multiReadHttpServletRequestWrapper.getBody();
            request = multiReadHttpServletRequestWrapper;
        }
        log.debug("{}>>>{}", servletPath, requestContent);

        try {
            filterChain.doFilter(request, wrapper);
        } finally {
            try {
                process(request, wrapper, response);
            } catch (Exception e) {
                log.error("AesFilter error", e);
            }
        }
    }

    @SneakyThrows
    protected void process(HttpServletRequest request, ResponseWrapper responseWrapper, HttpServletResponse response) {

        String servletPath = request.getRequestURI();
        String isencrypt = request.getHeader("Isencrypt");
        ServletOutputStream out = response.getOutputStream();
        if (ResponseWrapper.isJsonContentType(responseWrapper.getContentType())) {
            String responseContent = responseWrapper.getTextContent();
            if (StrUtil.isNotEmpty(responseContent)&&StringUtils.isNotBlank(isencrypt)) {
                String JSPublicKey = ((MultiReadHttpServletRequestWrapper)request).getJSPublicKey();
                if (StringUtils.isNotBlank(JSPublicKey)) {
                 ApiEncryptRes apiEncryptRes = ApiSecurityUtils.encrypt(JSON.toJSONString(responseContent), JSPublicKey);
                   responseContent = JSONUtil.toJsonStr(apiEncryptRes);
                }
                log.info("{}<<<{}", servletPath, responseContent);
                byte[] bytes = responseContent.getBytes(StandardCharsets.UTF_8);
                response.setContentLength(bytes.length);
                out.write(bytes);
                out.flush();
                out.close();
            } else {
                responseWrapper.getByteArrayOutputStream().writeTo(out);
                out.flush();
                out.close();
            }
        } else {
            responseWrapper.getByteArrayOutputStream().writeTo(out);
            out.flush();
            out.close();
        }
    }

    @Override
    public void afterPropertiesSet() throws ServletException {
        super.afterPropertiesSet();
    }

    @Override
    public int getOrder() {
        return DEFAULT_ORDER;
    }
}
4.过滤器注册
import com.l.common.encryptAPI.filter.EncryptApiFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @ClassName encryConfig  将加密的过滤器注册,一定要记得,2024-01-01踩坑
 * @Description TODO
 * @Author L
 * @Date 2024/2/1 15:31
 */
@Configuration
public class EncryConfig {
    @Bean
    @ConditionalOnWebApplication
    public EncryptApiFilter encryptApiFilter(ApplicationContext applicationContext) {
        return new EncryptApiFilter();
    }
}

5. 获取RSA公钥接口
import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.extension.api.R;
import com.l.common.encryptAPI.utils.RSAUtils;
import com.l.common.encryptAPI.util.MD5Util;
import com.l.common.encryptAPI.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @ClassName EncryptController
 * @Description TODO
 * @Author L
 * @Date 2024/1/29 9:18
 */
@RestController
@RequestMapping("/encrypt")
@Slf4j
public class EncryptController {
    @Autowired
    private RedisUtil redisUtil;
    @GetMapping("/getPublicKey")
    public R<?> getPublicKey(HttpServletRequest request) throws Exception {
       //获取当前登陆账号对应的token,这行代码就不贴了。
        String publicKey="";
        if (StringUtils.isNotBlank(token)) {
            Map<String, String> stringStringMap = RSAUtils.genKeyPair();
            publicKey = stringStringMap.get("publicKey");
            String privateKey = stringStringMap.get("privateKey");
            String md5Token =  MD5Util.md5(token);
    //这个地方的存放时间根据你的token存放时间走
            redisUtil.set(md5Token + "publicKey", publicKey);
            redisUtil.set(md5Token + "privateKey", privateKey);
            return R.ok(publicKey);
        }
        return R.ok(publicKey);
    }

}

总结:

  • 28
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
首先,我们可以使用RSA算法来加密AES的密钥,然后使用AES算法来加密数据。 1. 生成RSA公私钥对 ```java KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); generator.initialize(2048); KeyPair keyPair = generator.generateKeyPair(); PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); ``` 2. 使用RSA公钥加密AES密钥 ```java // 生成AES密钥 KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); keyGenerator.init(128); SecretKey secretKey = keyGenerator.generateKey(); // 使用RSA公钥加密AES密钥 Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] encryptedKey = cipher.doFinal(secretKey.getEncoded()); ``` 3. 使用AES密钥加密数据 ```java // 使用AES密钥加密数据 byte[] rawData = "hello world".getBytes("UTF-8"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secretKey); byte[] encryptedData = cipher.doFinal(rawData); ``` 4. 使用RSA私钥解密AES密钥 ```java // 使用RSA私钥解密AES密钥 Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] decryptedKey = cipher.doFinal(encryptedKey); SecretKey originalKey = new SecretKeySpec(decryptedKey, 0, decryptedKey.length, "AES"); ``` 5. 使用AES密钥解密数据 ```java // 使用AES密钥解密数据 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, originalKey); byte[] decryptedData = cipher.doFinal(encryptedData); ``` 注意事项: - AES密钥需要保密,不能直接传输或存储。 - RSA加密的数据长度不能超过RSA公钥的长度。因此,如果需要加密的数据较长,可以使用AES算法对数据进行分块加密

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NotFoundObject.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值