springboot实现AES+RSA混合加密接口

实现原理:

通过过滤器和切面实现对接口的入参和出参的加解密(注:本来是想都写到过滤器里面实现,但是出参写入的时候,会提前断开,造成返回数据缺失)

代码:

结构

 EncryptionFilter 过滤器

@Slf4j
public class EncryptionFilter extends OncePerRequestFilter {
    private static final String PRIVATE_KEY = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDfH37gz54KUCr_85tLqTn5zlMtd1Xo0LqrDeDdupTE3fV7nqlL8S-CkMAvV17exRMqOVuSuk1QIbfUt-2ThAz6ZA1ZzQVmwe10XrjJgbe5WeywTDmxEWGn_J5eXrV8-nKojaYQdIdVBKEkPDHnhH8wB_2QBFQlQqg7AjfBNb6jrlMggK_GGNzFCqb8p2qF_WxpWjJuz7xjvzApiR6FB5d0OT4ucFjOIKkfOov1aQKjTvOEBVgsGQaJ5e20m2vRujJ34NegvrksoUekg822QJAwkDcg9hjZ4ZUqh8ERhVVhxGfUNRfkHB3QUtdGlc2EArBD-l9B4cjGZjX9kwnc-eWjAgMBAAECggEAXJmWr7iaz47r-aPhnKoT_J_rl2ACkIgoIJu26m7oFThFDFQR0Qm8_QQULtGk9vF1fb2mSqP1OD92KttWxzUzqatiexWCm136raQuLrseQ9eVSqmn-9vJ2s0V8PZ_fen3Mgrvk76498jfE8nDsGEl5Ao86stRHNGoPi0ydIZYo1cMluw1XryFIyBniz7_k3VLEFuuFiSqsM3zKvlJmo_2vPNIwjG3iTL9Qiu6hBYYb_vLHa4QQLduhFRetM3BHVyMVwqavsY-p5OMqJnOeL7c59mn7zprK8jzXjEq2uKMwUFVPCf2eCGhc67LcTR3F8nbdkP35Vv72q70VGtzsfIPgQKBgQDxmYTDkILBxA84Kxr3QGshFnfOvCwZFQfe2_df8bfVhwwS8OdAi7P2JELZI6044-9SucfUoItBlbrpikH-Sm7DtIA8nhawwYsBrEAIN9JhYGTgO49yFrgtuVCArhb4HeAvhW3etYEO2n3m1GrgrHsaxEDFzZaUmabqfy6zumEo4wKBgQDsbAxrW6jMk3eXVOFPwW05dBVQbtFfeE_EC_jW2cZRra6gNacyfed5z0bUPlC4a0sBVcEa7nzJpxV7JGy-BQ_9FALtxHrl1Jqwqf-nw-XmFqa79uUPzLHvSTDEIRTSJYleoRXT0ng3_n9-s1tZAOV1QHshNiMnPHxwx-5J57KsQQKBgQDDGE9HhRix09reZqCd7N0VmQkiXn3Vu0_hvQsj5ROoUCeF3BdB3g5yw5FfuxmUiSunR_KAVyxcx8Zp9IWaDQ0o6Edtq9Ny5LeVoD0M5dbzX13WXQJXBNWxxqWlY--UaLuyZDL21ubi0bk9f2bXCzFVygjsMjpYwUMytYVHCUiQNQKBgDFyi8psekqHUUzycMlokDqi_845z0qjdDNxuZLeK29r3wkdD6Is2RwN8Sd0_RcFQcO-tsu2M51Nd92wiZnYnzZ0WAR4wqeNJulqNNFW1J-h_y9y_Qen7VM5wJxUuvEU7r0-_by22XQEo4_RXXqpCFTUrqwMkZ-kM_a2qi0E68bBAoGBAKyKYsqXnfpRBI2WwTXmxSE-V1hOtVqH6g5fzPp8Uvc8VdIqTfLPt6X4cwaH2qSDBndJYIRY5dr7AYqVCtcc84IT69vhdzytPlwQ6pibYxclrPGIjOp75GzlM-EKSR0wg2tTSzGP-hKzNVPCIQ9Xcyh48FCPWfxQmgvmQHUoIngp";// 示例私钥
    private static final String FILTERED_ATTRIBUTE = "isFiltered";
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws IOException {
        //标记为通过了过滤器过滤
        request.setAttribute(FILTERED_ATTRIBUTE, Boolean.TRUE);
        CachedBodyHttpServletRequest cachedRequest = new CachedBodyHttpServletRequest(request);
        // 获取请求体中的数据
        String requestBody = new String(cachedRequest.getCachedBody(), StandardCharsets.UTF_8);

        // 将请求体字符串转换为BaseGeneral对象
        BaseGeneral baseGeneral = JSONUtil.toBean(requestBody, BaseGeneral.class);

        // 使用私钥解密data
        try {
            String decryptedData = CryptoUtil.decrypt(baseGeneral, PRIVATE_KEY);
            // 将解密后的请求体传递给过滤链
            filterChain.doFilter(new CachedBodyHttpServletRequest(request, decryptedData.getBytes()), response);
        } catch (Exception e) {
            log.error("解密失败", e);
            throw new RuntimeException(e);
        }
    }
}

 EncryptionAspect 切面,加密处理返回数据

@Aspect
@Component
@Slf4j
public class EncryptionAspect {
    private static final String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3x9-4M-eClAq__ObS6k5-c5TLXdV6NC6qw3g3bqUxN31e56pS_EvgpDAL1de3sUTKjlbkrpNUCG31Lftk4QM-mQNWc0FZsHtdF64yYG3uVnssEw5sRFhp_yeXl61fPpyqI2mEHSHVQShJDwx54R_MAf9kARUJUKoOwI3wTW-o65TIICvxhjcxQqm_Kdqhf1saVoybs-8Y78wKYkehQeXdDk-LnBYziCpHzqL9WkCo07zhAVYLBkGieXttJtr0boyd-DXoL65LKFHpIPNtkCQMJA3IPYY2eGVKofBEYVVYcRn1DUX5Bwd0FLXRpXNhAKwQ_pfQeHIxmY1_ZMJ3PnlowIDAQAB"; // 示例公钥
    private static final String FILTERED_ATTRIBUTE = "isFiltered";
    @Around("execution(* com.ugee.emp.restapi..*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request= ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        if (Boolean.TRUE.equals(request.getAttribute(FILTERED_ATTRIBUTE))) {
            HttpServletResponse response = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getResponse();
            try {
                // 继续执行控制器方法
                Object result = joinPoint.proceed();
                // 获取响应体中的数据
                EpmResult epmResult = JSONUtil.toBean(result.toString(), EpmResult.class);
                String data = epmResult.getDataString();

                // 加密响应体
                BaseGeneral encryptedData = CryptoUtil.encrypt(data, PUBLIC_KEY);

                // 设置加密后的数据
                epmResult.setData(encryptedData);
                String jsonStr = JSONUtil.toJsonStr(epmResult);
                // 设置响应内容
                response.setContentType("application/json;charset=UTF-8");
                try (PrintWriter writer = response.getWriter()) {
                    writer.write(jsonStr);
                    writer.flush();
                }
                return null; // 返回null以避免重复写入响应
            } catch (Exception e) {
                log.error("加密失败", e);
                throw new RuntimeException(e);
            }
        }else {
            // 如果请求没有通过过滤器,则直接执行控制器方法
            return joinPoint.proceed();
        }
    }
}

 CachedBodyHttpServletRequest 复制request,因为getInputStream()只能调用一次,request不可变,使用这个进行拷贝

public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {

    private byte[] cachedBody;

    public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        InputStream requestInputStream = request.getInputStream();
        this.cachedBody = toByteArray(requestInputStream);
    }
    public CachedBodyHttpServletRequest(HttpServletRequest request,byte[] cachedBody) throws IOException {
        super(request);
        InputStream requestInputStream = new ByteArrayInputStream(cachedBody);
        this.cachedBody = toByteArray(requestInputStream);
    }

    private byte[] toByteArray(InputStream inputStream) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
        return outputStream.toByteArray();
    }

    // 新增方法,用于获取缓存的请求体字节数组
    public byte[] getCachedBody() {
        // 返回拷贝以避免外部修改原始数组
        return cachedBody.clone();
    }


    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody);
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }

            @Override
            public boolean isFinished() {
                return byteArrayInputStream.available() == 0;
            }

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

            @Override
            public void setReadListener(ReadListener readListener) {
                // 不支持异步读取
            }
        };
    }
}

 WebConfig 添加过滤器的配置bean

@Configuration
public class WebConfig {
    @Bean
    public FilterRegistrationBean<EncryptionFilter> encryptionFilterRegistration() {
        FilterRegistrationBean<EncryptionFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new EncryptionFilter());
        registration.addUrlPatterns("/crypto/*"); // 指定过滤器应用的URL模式
        registration.setName("encryptionFilter");
        registration.setOrder(1); // 设置过滤器的顺序
        return registration;
    }
}

 CryptoUtil 加解密定义的工具类

/**
 * 加解密工具类
 */
@Slf4j
public class CryptoUtil {

    private static final String RSA = "RSA";
    private static final String AES = "AES";
    private static final String TRANSFORMATION_RSA = "RSA/ECB/PKCS1Padding";
    private static final String TRANSFORMATION_AES = "AES/ECB/PKCS5Padding";

    /**
     * 生成RSA公钥和私钥字符串。
     *
     * @return 包含公钥和私钥字符串的类。
     */
    public static RsaKeyPair generateRsaKeyStrings() {
        try {
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA);
            keyGen.initialize(2048);
            KeyPair keyPair = keyGen.generateKeyPair();
            PublicKey publicKey = keyPair.getPublic();
            PrivateKey privateKey = keyPair.getPrivate();

            // 将公钥和私钥转换为字符串形式
            String publicKeyStr = Base64.getUrlEncoder().encodeToString(publicKey.getEncoded());
            String privateKeyStr = Base64.getUrlEncoder().encodeToString(privateKey.getEncoded());

            return new RsaKeyPair(publicKeyStr, privateKeyStr);
        } catch (NoSuchAlgorithmException e) {
            log.error("生成RSA公钥和私钥失败", e);
            throw new RuntimeException("生成RSA公钥和私钥失败", e);
        }
    }

    /**
     * 使用公钥加密数据。
     *
     * @param data      要加密的数据。
     * @param publicKey 公钥字符串。
     * @return 加密后的数据。
     */
    public static String rsaEncrypt(String data, String publicKey) throws Exception {
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.getUrlDecoder().decode(publicKey));
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION_RSA);
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        return Base64.getUrlEncoder().encodeToString(cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)));
    }

    /**
     * 使用私钥解密数据。
     *
     * @param encryptedData 要解密的数据。
     * @param privateKey    私钥字符串。
     * @return 解密后的数据。
     */
    public static String rsaDecrypt(String encryptedData, String privateKey) throws Exception {
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.getUrlDecoder().decode(privateKey));
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION_RSA);
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        // 解密数据
        byte[] decryptedData = cipher.doFinal(Base64.getUrlDecoder().decode(encryptedData));

        // 返回解密后的字符串
        return new String(decryptedData, StandardCharsets.UTF_8);
    }

    /**
     * 使用AES密钥加密数据。
     *
     * @param data 要加密的数据。
     * @param key  AES密钥。
     * @return 加密后的数据。
     */
    public static String aesEncrypt(String data, String key) throws Exception {
        byte[] keyByte = Base64.getUrlDecoder().decode(key);
        SecretKeySpec skeySpec = new SecretKeySpec(keyByte, AES);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION_AES);
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        return Base64.getUrlEncoder().encodeToString(cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)));
    }

    /**
     * 使用AES密钥解密数据。
     *
     * @param encryptedData 要解密的数据。
     * @param key           AES密钥。
     * @return 解密后的数据。
     */
    public static String aesDecrypt(String encryptedData, String key) throws Exception {
        byte[] keyByte = Base64.getUrlDecoder().decode(key);
        SecretKeySpec skeySpec = new SecretKeySpec(keyByte, AES);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION_AES);
        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
        return new String(cipher.doFinal(Base64.getUrlDecoder().decode(encryptedData)), StandardCharsets.UTF_8);
    }

    /**
     * 混合数据解密。
     */
    public static String decrypt(BaseGeneral baseGeneral, String clientPirKey) throws Exception {
        // 先用RSA解密AES密钥
        String aesKey = rsaDecrypt(baseGeneral.getAesKey(), clientPirKey);
        // 再用AES密钥解密数据
        return aesDecrypt(baseGeneral.getData(), aesKey);
    }

    /**
     * 混合数据加密。
     */
    public static BaseGeneral encrypt(String jsonData, String clientPubKey) throws Exception {
        // 先生成AES密钥
        byte[] aesKey = new byte[16];
        SecureRandom secureRandom = new SecureRandom();
        secureRandom.nextBytes(aesKey);
        String aesKeyStr = Base64.getUrlEncoder().encodeToString(aesKey);
        // 再用AES加密数据串
        String encryptedData = aesEncrypt(jsonData, aesKeyStr);
        // 用RSA加密AES密钥
        String encryptedAesKey = rsaEncrypt(aesKeyStr, clientPubKey);
        return new BaseGeneral(encryptedAesKey,encryptedData);
    }
}

 RsaKeyPair,RSA公钥私钥对象

@Data
@AllArgsConstructor
@ToString
public class RsaKeyPair {
    /**
     * 公钥
     */
    private String publicKey;
    /**
     * 私钥
     */
    private String privateKey;
}

 BaseGeneral,定义的加解密数据结构

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class BaseGeneral {
    /**
     * Rsa加密的Aes密钥
     */
    private String aesKey;
    /**
     * Aes加密的请求实体
     */
    private String data;
}

首先,我们可以使用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算法对数据进行分块加密
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值