当前位置:   article > 正文

SpringBoot 实现 RAS+AES 自动接口解密

对称加密和非对称加密的区别是什么

一、讲个事故

接口安全老生常谈了

过年之前做了过一款飞机大战的H5小游戏,里面无限模式-需要保存用户的积分,因为使用的Body传参,参数是可见的。

为了接口安全我,我和前端约定了传递参数是:用户无限模式的积分+“我们约定的一个数字”+用户id的和,在用Base64加密,请求到服务器我再解密,出用户无限模式的积分;如下:

  1. {
  2.     "integral""MTExMTM0NzY5NQ==",
  3. }

可是过年的时候,运营突然找我说无限模式积分排行榜分数不对:

197c9e60d7e64af8acefb374bb66468d.png b02ef23d0ccb968bc90d3a4fbac00c46.png

这就很诡异了,第二名才一万多分,第一名就40多万分!!!!

一开始我以为是我解密有问题,反复看了好几变,可就两三行代码不可能有问题的!!!

没办法我去翻了好久的日志,才发现这个用户把我接口参数给改了。。。。

他把Base64接口参数改了

378f1e55058b42fe33ad085168182c18.png 0581c12da4b92fcbcadd0d7398721254.png

事已至此,我也不能怪用户,谁让我把人家想得太简单,接口安全也没到位

所以年后上班第一件是就是把接口加密的工作搞起来

目前常用的加密方式就对称性加密和非对称性加密,加密解密的操作的肯定是大家知道的,最重要的使用什么加密解密方式,制定什么样的加密策略;考虑到我技术水平和接口的速度,采用的是RAS非对称加密和AES对称加密一起用!!!!

二、RSA和AES基础知识

1、非对称加密和对称加密

非对称加密

非对称加密算法是一种密钥的保密方法。非对称加密算法需要两个密钥:公开密钥(publickey:简称公钥)和私有密钥(privatekey:简称私钥)。

公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。

对称加密

加密秘钥和解密秘钥是一样,当你的密钥被别人知道后,就没有秘密可言了

AES 是对称加密算法,优点:加密速度快;缺点:如果秘钥丢失,就容易解密密文,安全性相对比较差

RSA 是非对称加密算法 , 优点:安全 ;缺点:加密速度慢

2、RSA基础知识

RSA——非对称加密,会产生公钥和私钥,公钥在客户端,私钥在服务端。公钥用于加密,私钥用于解密。

大概的流程:

客户端向服务器发送消息:客户端用公钥加密信息,发送给服务端,服务端再用私钥机密

服务器向客户端发送消息:服务端用私钥加密信息,发送给客户端,客户端再用公钥机密

当然中间要保障密钥的安全,还有很多为了保障数据安全的操作,比如数字签名,证书签名等等,在这我们就先不说了;

RSA加密解密算法支持三种填充模式,

分别是ENCRYPTION_OAEPENCRYPTION_PKCS1ENCRYPTION_NONERSA填充是为了和公钥等长。

  • ENCRYPTION_OAEP:最优非对称加密填充,是RSA加密和RSA解密最新最安全的推荐填充模式。

  • ENCRYPTION_PKCS1:随机填充数据模式,每次加密的结果都不一样,是RSA加密和RSA解密使用最为广泛的填充模式。

  • ENCRYPTION_NONE:不填充模式,是RSA加密和RSA解密使用较少的填充模式。

RSA 常用的加密填充模式

  • RSA/None/PKCS1Padding

  • RSA/ECB/PKCS1Padding

知识点:

Java 默认的 RSA 实现是 RSA/None/PKCS1Padding

在创建RSA秘钥对时,长度最好选择 2048的整数倍,长度为1024在已经不很安全了

一般由服务器创建秘钥对,私钥保存在服务器,公钥下发至客户端

DER是RSA密钥的二进制格式,PEM是DER转码为Base64的字符格式,由于DER是二进制格式,不便于阅读和理解。一般而言,密钥都是通过PEM的格式进行存储的

  1. /**
  2.  * 生成密钥对
  3.  * @param keyLength  密钥长度
  4.  * @return KeyPair
  5.  */
  6. public static KeyPair getKeyPair(int keyLength) {
  7.     try {
  8.         KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");   //默认:RSA/None/PKCS1Padding
  9.         keyPairGenerator.initialize(keyLength);
  10.         return keyPairGenerator.generateKeyPair();
  11.     } catch (NoSuchAlgorithmException e) {
  12.         throw new RuntimeException("生成密钥对时遇到异常" +  e.getMessage());
  13.     }
  14. }
  15. /**
  16.  * 获取公钥
  17.  */
  18. public static byte[] getPublicKey(KeyPair keyPair) {
  19.     RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
  20.     return rsaPublicKey.getEncoded();
  21. }
  22. /**
  23.  * 获取私钥
  24.  */
  25. public static byte[] getPrivateKey(KeyPair keyPair) {
  26.     RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
  27.     return rsaPrivateKey.getEncoded();
  28. }
3、AES基础知识

AES 简介

AES加密解密算法是一种可逆的对称加密算法,这类算法在加密和AES解密时使用相同的密钥,或是使用两个可以简单地相互推算的密钥,一般用于服务端对服务端之间对数据进行加密解密。它是一种为了替代原先DES、3DES而建立的高级加密标准(Advanced Encryption Standard)。

作为可逆且对称的块加密,AES加密算法的速度比公钥加密等加密算法快很多,在很多场合都需要AES对称加密,但是要求加密端和解密端双方都使用相同的密钥是AES算法的主要缺点之一。

AES加密解密

AES加密需要:明文 + 密钥+ 偏移量(IV)+密码模式(算法/模式/填充) AES解密需要:密文 + 密钥+ 偏移量(IV)+密码模式(算法/模式/填充)

AES的算法模式一般为 AES/CBC/PKCS5PaddingAES/CBC/PKCS7Padding

AES常见的工作模式:

  • 电码本模式(ECB)

  • 密码分组链接模式(CBC)

  • 计算器模式(CTR)

  • 密码反馈模式(CFB)

  • 输出反馈模式(OFB)

除了ECB无须设置初始化向量IV而不安全之外,其它AES工作模式都必须设置向量IV,其中GCM工作模式较为特殊。

AES填充模式

块密码只能对确定长度的数据块进行处理,而消息的长度通常是可变的,因此需要选择填充模式。

  • 填充区别:在ECB、CBC工作模式下最后一块要在加密前进行填充,其它不用选择填充模式;

  • 填充模式:AES支持的填充模式为PKCS7和NONE不填充。其中PKCS7标准是主流加密算法都遵循的数据填充算法。AES标准规定的区块长度为固定值128Bit,对应的字节长度为16位,这明显和PKCS5标准规定使用的固定值8位不符,虽然有些框架特殊处理后可以通用PKCS5,但是从长远和兼容性考虑,推荐PKCS7。

AES密钥KEY和初始化向量IV

初始化向量IV可以有效提升安全性,但是在实际的使用场景中,它不能像密钥KEY那样直接保存在配置文件或固定写死在代码中,一般正确的处理方式为:在加密端将IV设置为一个16位的随机值,然后和加密文本一起返给解密端即可。

  • 密钥KEY:AES标准规定区块长度只有一个值,固定为128Bit,对应的字节为16位。AES算法规定密钥长度只有三个值,128Bit、192Bit、256Bit,对应的字节为16位、24位和32位,其中密钥KEY不能公开传输,用于加密解密数据;

  • 初始化向量IV:该字段可以公开,用于将加密随机化。同样的明文被多次加密也会产生不同的密文,避免了较慢的重新产生密钥的过程,初始化向量与密钥相比有不同的安全性需求,因此IV通常无须保密。然而在大多数情况中,不应当在使用同一密钥的情况下两次使用同一个IV,一般推荐初始化向量IV为16位的随机值。

三、加密策略

RAS、AES加密解密的操作都是一样,如果有效的结合到一起才能达到更好的加密效果很重要;

上面说到:

AES 是对称加密算法,优点:加密速度快;缺点:如果秘钥丢失,就容易解密密文,安全性相对比较差

RSA 是非对称加密算法, 优点:安全 ;缺点:加密速度慢

1、主要思路:

那么我们就结合2个加密算法的优点来操作:

  • 因为接口传递的参数有多有少,当接口传递的参数过多时,使用RSA加密会导致加密速度慢,所以我们使用AES加密加密接口参数

  • 因为AES的密钥key和偏移量VI都是固定的所以可以使用RSA加密

  • 客户端将AES加密后的密文和RSA加密后的密文,传递给服务器即可。

2、涉及工具类:

util包下:

  • ActivityRSAUtil

  • AES256Util

  • RequestDecryptionUtil

3、加密策略
b6c18e4de278f42dc2db9beeec919e26.png
4、交互方式

前端:

1、客户端随机生成2个16为的AES密钥和AES偏移量

2、使用AES加密算法加密真实传递参数,得到参数密文“asy”

3、将AES密钥、AES偏移量和当前时间戳,格式如下:

  • key:密钥

  • keyVI:偏移量

  • time:请求时间,用户判断是否重复请求

  1. {
  2.   "key":"0t7FtCDKofbEVpSZS",
  3.   "keyVI":"0t7WESMofbEVpSZS",
  4.   "time":211213232323323
  5. }
  6. //转成JSON字符串

4、AES信息密钥信息,再使用RSA公钥加密,得到AES密钥的密文“sym”

5、将“sym”和“asy”作为body参数,调用接口

5d439ac4f5e2b297e74929b9d3be27f9.png

后端:

1、在接口接收参数中,多增加2个字段接收加密后的“sym”和“asy” (名字可以自己定,能接收到就行)

2、使用RequestDecryptionUtil.getRequestDecryption()方法解密,返回解密后的真实传递参数

四、服务器自动解密

因为不是每个接口都需求加密解密,我们可以自定义一个注解,将需要解密的接口上加一个这个注解,

1、自定义解密注解:@RequestRSA
  1. import java.lang.annotation.Documented;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. @Target({ElementType.TYPE, ElementType.METHOD})
  7. @Retention(RetentionPolicy.RUNTIME)
  8. @Documented
  9. public @interface RequestRSA {
  10. }
2、创建一个aop切片
  1. AOP判断controller接收到请求是否带有@RequestRSA注解

  2. 如果带有注解,通过ProceedingJoinPointgetArgs()方法获取请求的body参数,

  3. 将body参数,传为JSONObject类,获取到"asy"和"sym"属性,再调用RequestDecryptionUtil解密获取接口传递的真实参数

  4. 获取接口入参的类

  5. 将获取解密后的真实参数,封装到接口入参的类中

  1. import com.alibaba.fastjson.JSONObject;
  2. import app.activity.common.interceptor.RequestRSA;
  3. import app.activity.util.RequestDecryptionUtil;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.aspectj.lang.ProceedingJoinPoint;
  6. import org.aspectj.lang.annotation.Around;
  7. import org.aspectj.lang.annotation.Aspect;
  8. import org.aspectj.lang.annotation.Pointcut;
  9. import org.aspectj.lang.reflect.MethodSignature;
  10. import org.springframework.core.annotation.Order;
  11. import org.springframework.stereotype.Component;
  12. import org.springframework.web.bind.annotation.RequestBody;
  13. import java.lang.reflect.Method;
  14. import java.lang.reflect.Parameter;
  15. import java.util.ArrayList;
  16. import java.util.List;
  17. import java.util.Objects;
  18. /**
  19.  * @module
  20.  * @author: qingxu.liu
  21.  * @date: 2023-02-08 16:41
  22.  * @copyright  请求验证RSA & AES  统一验证切面
  23.  **/
  24. @Aspect
  25. @Component
  26. @Order(2)
  27. @Slf4j
  28. public class RequestRSAAspect {
  29.     /**
  30.      * 1> 获取请求参数
  31.      * 2> 获取被请求接口的入参类型
  32.      * 3> 判断是否为get请求 是则跳过AES解密判断
  33.      * 4> 请求参数解密->封装到接口的入参
  34.      */
  35.     @Pointcut("execution(public * app.activity.controller.*.*(..))")
  36.     public void requestRAS() {
  37.     }
  38.     @Around("requestRAS()")
  39.     public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
  40.         //=======AOP解密切面通知=======
  41.         MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
  42.         Method methods = methodSignature.getMethod();
  43.         RequestRSA annotation = methods.getAnnotation(RequestRSA.class);
  44.         if (Objects.nonNull(annotation)){
  45.             //获取请求的body参数
  46.             Object data = getParameter(methods, joinPoint.getArgs());
  47.             String body = JSONObject.toJSONString(data);
  48.             //获取asy和sym的值
  49.             JSONObject jsonObject = JSONObject.parseObject(body);
  50.             String asy = jsonObject.get("asy").toString();
  51.             String sym = jsonObject.get("sym").toString();
  52.             //调用RequestDecryptionUtil方法解密,获取解密后的真实参数
  53.             JSONObject decryption = RequestDecryptionUtil.getRequestDecryption(sym, asy);
  54.             //获取接口入参的类
  55.             String typeName = joinPoint.getArgs()[0].getClass().getTypeName();
  56.             System.out.println("参数值类型:"+ typeName);
  57.             Class<?> aClass = joinPoint.getArgs()[0].getClass();
  58.             //将获取解密后的真实参数,封装到接口入参的类中
  59.             Object o = JSONObject.parseObject(decryption.toJSONString(), aClass);
  60.             Object[] as = {o};
  61.             return joinPoint.proceed(as);
  62.         }
  63.         return joinPoint.proceed();
  64.     }
  65.     /**
  66.      * 根据方法和传入的参数获取请求参数 获取的是接口的入参
  67.      */
  68.     private Object getParameter(Method method, Object[] args) {
  69.         List<Object> argList = new ArrayList<>();
  70.         Parameter[] parameters = method.getParameters();
  71.         for (int i = 0; i < parameters.length; i++) {
  72.             //将RequestBody注解修饰的参数作为请求参数
  73.             RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
  74.             if (requestBody != null) {
  75.                 argList.add(args[i]);
  76.             }
  77.         }
  78.         if (argList.size() == 0) {
  79.             return null;
  80.         } else if (argList.size() == 1) {
  81.             return argList.get(0);
  82.         } else {
  83.             return argList;
  84.         }
  85.     }
  86. }
3、RequestDecryptionUtil 解密类

1、使用privateKey私钥对”sym“解密获取到客户端加密的AES密钥,偏移量、时间等信息

  1. {
  2.   "key":"0t7FtSMofbEVpSZS",
  3.   "keyVI":"0t7FtSMofbEVpSZS",
  4.   "time":211213232323323
  5. }

2、获取当前时间戳,与time比较是否超过一分钟(6000毫秒),超过就抛出“Request timed out, please try again”异常

3、没有超时,将获取的到AES密钥和偏移量,再对“asy”解密获取接口传递的真实参数

  1. import com.alibaba.fastjson.JSONObject;
  2. import app.activity.common.rsa.RSADecodeData;
  3. import app.common.exception.ServiceException;
  4. import java.security.interfaces.RSAPrivateKey;
  5. import java.util.Objects;
  6. /**
  7.  * @module
  8.  * @author: qingxu.liu
  9.  * @date: 2023-02-09 17:43
  10.  * @copyright
  11.  **/
  12. public class RequestDecryptionUtil {
  13.     private final static String publicKey = "RSA生成的公钥";
  14.     private final static String privateKey = "RSA生成的私钥";
  15.     private final static Integer timeout = 60000;
  16.     /**
  17.      *
  18.      * @param sym RSA 密文
  19.      * @param asy AES 密文
  20.      * @param clazz 接口入参类
  21.      * @return Object
  22.      */
  23.     public static <T> Object getRequestDecryption(String sym, String asy, Class<T> clazz){
  24.         //验证密钥
  25.         try {
  26.             //解密RSA
  27.             RSAPrivateKey rsaPrivateKey = ActivityRSAUtil.getRSAPrivateKeyByString(privateKey);
  28.             String RSAJson = ActivityRSAUtil.privateDecrypt(sym, rsaPrivateKey);
  29.             RSADecodeData rsaDecodeData = JSONObject.parseObject(RSAJson, RSADecodeData.class);
  30.             boolean isTimeout = Objects.nonNull(rsaDecodeData)  && Objects.nonNull(rsaDecodeData.getTime()) && System.currentTimeMillis() -  rsaDecodeData.getTime() < timeout;
  31.             if (!isTimeout){
  32.                 throw new ServiceException("Request timed out, please try again."); //请求超时
  33.             }
  34.             //解密AES
  35.             String AESJson = AES256Util.decode(rsaDecodeData.getKey(),asy,rsaDecodeData.getKeyVI());
  36.             System.out.println("AESJson: "+AESJson);
  37.             return JSONObject.parseObject(AESJson,clazz);
  38.         } catch (Exception e) {
  39.             throw new RuntimeException("RSA decryption Exception:  " +e.getMessage());
  40.         }
  41.     }
  42.     public static JSONObject getRequestDecryption(String sym, String asy){
  43.         //验证密钥
  44.         try {
  45.             //解密RSA
  46.             RSAPrivateKey rsaPrivateKey = ActivityRSAUtil.getRSAPrivateKeyByString(privateKey);
  47.             String RSAJson = ActivityRSAUtil.privateDecrypt(sym, rsaPrivateKey);
  48.             RSADecodeData rsaDecodeData = JSONObject.parseObject(RSAJson, RSADecodeData.class);
  49.             boolean isTimeout = Objects.nonNull(rsaDecodeData)  && Objects.nonNull(rsaDecodeData.getTime()) && System.currentTimeMillis() -  rsaDecodeData.getTime() < timeout;
  50.             if (!isTimeout){
  51.                 throw new ServiceException("Request timed out, please try again."); //请求超时
  52.             }
  53.             //解密AES
  54.             String AESJson = AES256Util.decode(rsaDecodeData.getKey(),asy,rsaDecodeData.getKeyVI());
  55.             System.out.println("AESJson: "+AESJson);
  56.             return JSONObject.parseObject(AESJson);
  57.         } catch (Exception e) {
  58.             throw new RuntimeException("RSA decryption Exception:  " +e.getMessage());
  59.         }
  60.     }
  61. }

4、ActivityRSAUtil 工具类

  1. import org.apache.commons.io.IOUtils;
  2. import javax.crypto.Cipher;
  3. import java.io.ByteArrayOutputStream;
  4. import java.security.*;
  5. import java.security.interfaces.RSAPrivateKey;
  6. import java.security.interfaces.RSAPublicKey;
  7. import java.security.spec.PKCS8EncodedKeySpec;
  8. import java.security.spec.X509EncodedKeySpec;
  9. import java.util.Base64;
  10. /**
  11.  * @module
  12.  * @author: qingxu.liu
  13.  * @date: 2023-02-07 16:54
  14.  * @copyright
  15.  **/
  16. public class ActivityRSAUtil {
  17.     /**
  18.      * 字符集
  19.      */
  20.     public static String CHARSET = "UTF-8";
  21.     /**
  22.      * 生成密钥对
  23.      * @param keyLength  密钥长度
  24.      * @return KeyPair
  25.      */
  26.     public static KeyPair getKeyPair(int keyLength) {
  27.         try {
  28.             KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");   //默认:RSA/None/PKCS1Padding
  29.             keyPairGenerator.initialize(keyLength);
  30.             return keyPairGenerator.generateKeyPair();
  31.         } catch (NoSuchAlgorithmException e) {
  32.             throw new RuntimeException("生成密钥对时遇到异常" +  e.getMessage());
  33.         }
  34.     }
  35.     /**
  36.      * 获取公钥
  37.      */
  38.     public static byte[] getPublicKey(KeyPair keyPair) {
  39.         RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
  40.         return rsaPublicKey.getEncoded();
  41.     }
  42.     /**
  43.      * 获取私钥
  44.      */
  45.     public static byte[] getPrivateKey(KeyPair keyPair) {
  46.         RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
  47.         return rsaPrivateKey.getEncoded();
  48.     }
  49.     /**
  50.      * 公钥字符串转PublicKey实例
  51.      * @param publicKey 公钥字符串
  52.      * @return          PublicKey
  53.      * @throws Exception e
  54.      */
  55.     public static PublicKey getPublicKey(String publicKey) throws Exception {
  56.         byte[] publicKeyBytes = Base64.getDecoder().decode(publicKey.getBytes());
  57.         X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
  58.         KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  59.         return keyFactory.generatePublic(keySpec);
  60.     }
  61.     /**
  62.      * 私钥字符串转PrivateKey实例
  63.      * @param privateKey  私钥字符串
  64.      * @return PrivateKey
  65.      * @throws Exception e
  66.      */
  67.     public static PrivateKey getPrivateKey(String privateKey) throws Exception {
  68.         byte[] privateKeyBytes = Base64.getDecoder().decode(privateKey.getBytes());
  69.         PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
  70.         KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  71.         return keyFactory.generatePrivate(keySpec);
  72.     }
  73.     /**
  74.      * 获取公钥字符串
  75.      * @param keyPair KeyPair
  76.      * @return  公钥字符串
  77.      */
  78.     public static String getPublicKeyString(KeyPair keyPair){
  79.         RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公钥
  80.         return new String(org.apache.commons.codec.binary.Base64.encodeBase64(publicKey.getEncoded()));
  81.     }
  82.     /**
  83.      * 获取私钥字符串
  84.      * @param keyPair  KeyPair
  85.      * @return 私钥字符串
  86.      */
  87.     public static String getPrivateKeyString(KeyPair keyPair){
  88.         RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私钥
  89.         return new String(org.apache.commons.codec.binary.Base64.encodeBase64((privateKey.getEncoded())));
  90.     }
  91.     /**
  92.      * 公钥加密
  93.      * @param data        明文
  94.      * @param publicKey   公钥
  95.      * @return            密文
  96.      */
  97.     public static String publicEncrypt(String data, RSAPublicKey publicKey) {
  98.         try {
  99.             Cipher cipher = Cipher.getInstance("RSA");
  100.             cipher.init(Cipher.ENCRYPT_MODE, publicKey);
  101.             byte[] bytes = rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength());
  102.             return new String(org.apache.commons.codec.binary.Base64.encodeBase64(bytes));
  103.         } catch (Exception e) {
  104.             throw new RuntimeException("加密字符串[" + data + "]时遇到异常"+  e.getMessage());
  105.         }
  106.     }
  107.     /**
  108.      * 私钥解密
  109.      * @param data        密文
  110.      * @param privateKey  私钥
  111.      * @return            明文
  112.      */
  113.     public static String privateDecrypt(String data, RSAPrivateKey privateKey) {
  114.         try {
  115.             Cipher cipher = Cipher.getInstance("RSA");
  116.             cipher.init(Cipher.DECRYPT_MODE, privateKey);
  117.             return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.getDecoder().decode(data), privateKey.getModulus().bitLength()), CHARSET);
  118.         } catch (Exception e) {
  119.             throw new RuntimeException("privateKey解密字符串[" + data + "]时遇到异常"+  e.getMessage());
  120.         }
  121.     }
  122.     /**
  123.      * 私钥加密
  124.      * @param content 明文
  125.      * @param privateKey 私钥
  126.      * @return 密文
  127.      */
  128.     public static String encryptByPrivateKey(String content, RSAPrivateKey privateKey){
  129.         try {
  130.             Cipher cipher = Cipher.getInstance("RSA");
  131.             cipher.init(Cipher.ENCRYPT_MODE, privateKey);
  132.             byte[] bytes = rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE,content.getBytes(CHARSET), privateKey.getModulus().bitLength());
  133.             return new String(org.apache.commons.codec.binary.Base64.encodeBase64(bytes));
  134.         } catch (Exception e) {
  135.             throw new RuntimeException("privateKey加密字符串[" + content + "]时遇到异常" +  e.getMessage());
  136.         }
  137.     }
  138.     /**
  139.      * 公钥解密
  140.      * @param content  密文
  141.      * @param publicKey 私钥
  142.      * @return  明文
  143.      */
  144.     public static String decryByPublicKey(String content, RSAPublicKey publicKey){
  145.         try {
  146.             Cipher cipher = Cipher.getInstance("RSA");
  147.             cipher.init(Cipher.DECRYPT_MODE, publicKey);
  148.             return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.getDecoder().decode(content), publicKey.getModulus().bitLength()), CHARSET);
  149.         } catch (Exception e) {
  150.             throw new RuntimeException("publicKey解密字符串[" + content + "]时遇到异常" +e.getMessage());
  151.         }
  152.     }
  153.     public static RSAPublicKey getRSAPublicKeyByString(String publicKey){
  154.         try {
  155.             X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
  156.             KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  157.             return (RSAPublicKey)keyFactory.generatePublic(keySpec);
  158.         } catch (Exception e) {
  159.             throw new RuntimeException("String转PublicKey出错" + e.getMessage());
  160.         }
  161.     }
  162.     public static RSAPrivateKey getRSAPrivateKeyByString(String privateKey){
  163.         try {
  164.             PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
  165.             KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  166.             return (RSAPrivateKey)keyFactory.generatePrivate(pkcs8EncodedKeySpec);
  167.         } catch (Exception e) {
  168.             throw new RuntimeException("String转PrivateKey出错" + e.getMessage());
  169.         }
  170.     }
  171.     //rsa切割解码  , ENCRYPT_MODE,加密数据   ,DECRYPT_MODE,解密数据
  172.     private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {
  173.         int maxBlock = 0;  //最大块
  174.         if (opmode == Cipher.DECRYPT_MODE) {
  175.             maxBlock = keySize / 8;
  176.         } else {
  177.             maxBlock = keySize / 8 - 11;
  178.         }
  179.         ByteArrayOutputStream out = new ByteArrayOutputStream();
  180.         int offSet = 0;
  181.         byte[] buff;
  182.         int i = 0;
  183.         try {
  184.             while (datas.length > offSet) {
  185.                 if (datas.length - offSet > maxBlock) {
  186.                     //可以调用以下的doFinal()方法完成加密或解密数据:
  187.                     buff = cipher.doFinal(datas, offSet, maxBlock);
  188.                 } else {
  189.                     buff = cipher.doFinal(datas, offSet, datas.length - offSet);
  190.                 }
  191.                 out.write(buff, 0, buff.length);
  192.                 i++;
  193.                 offSet = i * maxBlock;
  194.             }
  195.         } catch (Exception e) {
  196.             throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常: " + e.getMessage());
  197.         }
  198.         byte[] resultDatas = out.toByteArray();
  199.         IOUtils.closeQuietly(out);
  200.         return resultDatas;
  201.     }
  202. }

5、AES256Util 工具类

  1. import org.bouncycastle.jce.provider.BouncyCastleProvider;
  2. import javax.crypto.Cipher;
  3. import javax.crypto.spec.SecretKeySpec;
  4. import java.nio.charset.StandardCharsets;
  5. import java.security.Security;
  6. import java.util.Base64;
  7. /**
  8.  * @module
  9.  * @author: qingxu.liu
  10.  * @date: 2023-02-07 16:14
  11.  * @copyright
  12.  **/
  13. public class AES256Util {
  14.     private static final String AES = "AES";
  15.     /**
  16.      * 初始向量IV, 初始向量IV的长度规定为128位16个字节, 初始向量的来源为随机生成.
  17.      */
  18.     /**
  19.      * 加密解密算法/加密模式/填充方式
  20.      */
  21.     private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding";
  22.     private static final Base64.Encoder base64Encoder = java.util.Base64.getEncoder();
  23.     private static final Base64.Decoder base64Decoder = java.util.Base64.getDecoder();
  24.     //通过在运行环境中设置以下属性启用AES-256支持
  25.     static {
  26.         Security.setProperty("crypto.policy""unlimited");
  27.     }
  28.     /*
  29.      * 解决java不支持AES/CBC/PKCS7Padding模式解密
  30.      */
  31.     static {
  32.         Security.addProvider(new BouncyCastleProvider());
  33.     }
  34.     /**
  35.      * AES加密
  36.      */
  37.     public static String encode(String key, String content,String keyVI) {
  38.         try {
  39.             javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(key.getBytes(), AES);
  40.             javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM);
  41.             cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(keyVI.getBytes()));
  42.             // 获取加密内容的字节数组(这里要设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码
  43.             byte[] byteEncode = content.getBytes(java.nio.charset.StandardCharsets.UTF_8);
  44.             // 根据密码器的初始化方式加密
  45.             byte[] byteAES = cipher.doFinal(byteEncode);
  46.             // 将加密后的数据转换为字符串
  47.             return base64Encoder.encodeToString(byteAES);
  48.         } catch (Exception e) {
  49.             e.printStackTrace();
  50.         }
  51.         return null;
  52.     }
  53.     /**
  54.      * AES解密
  55.      */
  56.     public static String decode(String key, String content,String keyVI) {
  57.         try {
  58.             javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(key.getBytes(), AES);
  59.             javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM);
  60.             cipher.init(javax.crypto.Cipher.DECRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(keyVI.getBytes()));
  61.             // 将加密并编码后的内容解码成字节数组
  62.             byte[] byteContent = base64Decoder.decode(content);
  63.             // 解密
  64.             byte[] byteDecode = cipher.doFinal(byteContent);
  65.             return new String(byteDecode, java.nio.charset.StandardCharsets.UTF_8);
  66.         } catch (Exception e) {
  67.             e.printStackTrace();
  68.         }
  69.         return null;
  70.     }
  71.     /**
  72.      * AES加密ECB模式PKCS7Padding填充方式
  73.      * @param str 字符串
  74.      * @param key 密钥
  75.      * @return 加密字符串
  76.      * @throws Exception 异常信息
  77.      */
  78.     public static String aes256ECBPkcs7PaddingEncrypt(String str, String key) throws Exception {
  79.         Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
  80.         byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
  81.         cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, AES));
  82.         byte[] doFinal = cipher.doFinal(str.getBytes(StandardCharsets.UTF_8));
  83.         return new String(Base64.getEncoder().encode(doFinal));
  84.     }
  85.     /**
  86.      * AES解密ECB模式PKCS7Padding填充方式
  87.      * @param str 字符串
  88.      * @param key 密钥
  89.      * @return 解密字符串
  90.      * @throws Exception 异常信息
  91.      */
  92.     public static String aes256ECBPkcs7PaddingDecrypt(String str, String key) throws Exception {
  93.         Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
  94.         byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
  95.         cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, AES));
  96.         byte[] doFinal = cipher.doFinal(Base64.getDecoder().decode(str));
  97.         return new String(doFinal);
  98.     }
  99. }

亲测100%可用~~~

转载自:juejin.cn/post/7203931072260915259

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/酷酷是懒虫/article/detail/898282
推荐阅读
相关标签
  

闽ICP备14008679号