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

接口安全老生常谈了

目前常用的加密方式就对称性加密和非对称性加密,加密解密的操作的肯定是大家知道的,最重要的使用什么加密解密方式,制定什么样的加密策略;考虑到我技术水平和接口的速度,采用的是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的格式进行存储的

/**
 * 生成密钥对
 * @param keyLength  密钥长度
 * @return KeyPair
 */
public static KeyPair getKeyPair(int keyLength) {
    try {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");   //默认:RSA/None/PKCS1Padding
        keyPairGenerator.initialize(keyLength);
        return keyPairGenerator.generateKeyPair();
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("生成密钥对时遇到异常" +  e.getMessage());
    }
}

/**
 * 获取公钥
 */
public static byte[] getPublicKey(KeyPair keyPair) {
    RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
    return rsaPublicKey.getEncoded();
}

/**
 * 获取私钥
 */
public static byte[] getPrivateKey(KeyPair keyPair) {
    RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
    return rsaPrivateKey.getEncoded();
}
3、AES基础知识

AES 简介

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

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

AES加密解密

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

AES的算法模式一般为 AES/CBC/PKCS5Padding 或 AES/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、加密策略

图片

4、交互方式

前端:

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

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

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

  • key:密钥

  • keyVI:偏移量

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

{
  "key":"0t7FtCDKofbEVpSZS",
  "keyVI":"0t7WESMofbEVpSZS",
  "time":211213232323323
}
//转成JSON字符串

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

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

图片

后端:

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

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

四、服务器自动解密

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

1、自定义解密注解:@RequestRSA
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestRSA {
}
2、创建一个aop切片
  1. AOP判断controller接收到请求是否带有@RequestRSA注解

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

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

  4. 获取接口入参的类

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

import com.alibaba.fastjson.JSONObject;
import app.activity.common.interceptor.RequestRSA;
import app.activity.util.RequestDecryptionUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @module
 * @author: qingxu.liu
 * @date: 2023-02-08 16:41
 * @copyright  请求验证RSA & AES  统一验证切面
 **/
@Aspect
@Component
@Order(2)
@Slf4j
public class RequestRSAAspect {

    /**
     * 1> 获取请求参数
     * 2> 获取被请求接口的入参类型
     * 3> 判断是否为get请求 是则跳过AES解密判断
     * 4> 请求参数解密->封装到接口的入参
     */

    @Pointcut("execution(public * app.activity.controller.*.*(..))")
    public void requestRAS() {
    }

    @Around("requestRAS()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //=======AOP解密切面通知=======
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method methods = methodSignature.getMethod();
        RequestRSA annotation = methods.getAnnotation(RequestRSA.class);
        if (Objects.nonNull(annotation)){
            //获取请求的body参数
            Object data = getParameter(methods, joinPoint.getArgs());
            String body = JSONObject.toJSONString(data);
            //获取asy和sym的值
            JSONObject jsonObject = JSONObject.parseObject(body);
            String asy = jsonObject.get("asy").toString();
            String sym = jsonObject.get("sym").toString();
            //调用RequestDecryptionUtil方法解密,获取解密后的真实参数
            JSONObject decryption = RequestDecryptionUtil.getRequestDecryption(sym, asy);
            //获取接口入参的类
            String typeName = joinPoint.getArgs()[0].getClass().getTypeName();
            System.out.println("参数值类型:"+ typeName);
            Class<?> aClass = joinPoint.getArgs()[0].getClass();
            //将获取解密后的真实参数,封装到接口入参的类中
            Object o = JSONObject.parseObject(decryption.toJSONString(), aClass);
            Object[] as = {o};
            return joinPoint.proceed(as);
        }
        return joinPoint.proceed();
    }

    /**
     * 根据方法和传入的参数获取请求参数 获取的是接口的入参
     */
    private Object getParameter(Method method, Object[] args) {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            //将RequestBody注解修饰的参数作为请求参数
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                argList.add(args[i]);
            }
        }
        if (argList.size() == 0) {
            return null;
        } else if (argList.size() == 1) {
            return argList.get(0);
        } else {
            return argList;
        }
    }
}
3、RequestDecryptionUtil 解密类

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

{
  "key":"0t7FtSMofbEVpSZS",
  "keyVI":"0t7FtSMofbEVpSZS",
  "time":211213232323323
}

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

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

import com.alibaba.fastjson.JSONObject;
import app.activity.common.rsa.RSADecodeData;
import app.common.exception.ServiceException;

import java.security.interfaces.RSAPrivateKey;
import java.util.Objects;

/**
 * @module
 * @author: qingxu.liu
 * @date: 2023-02-09 17:43
 * @copyright
 **/
public class RequestDecryptionUtil {

    private final static String publicKey = "RSA生成的公钥";
    private final static String privateKey = "RSA生成的私钥";
    private final static Integer timeout = 60000;

    /**
     *
     * @param sym RSA 密文
     * @param asy AES 密文
     * @param clazz 接口入参类
     * @return Object
     */
    public static <T> Object getRequestDecryption(String sym, String asy, Class<T> clazz){
        //验证密钥
        try {
            //解密RSA
            RSAPrivateKey rsaPrivateKey = ActivityRSAUtil.getRSAPrivateKeyByString(privateKey);
            String RSAJson = ActivityRSAUtil.privateDecrypt(sym, rsaPrivateKey);
            RSADecodeData rsaDecodeData = JSONObject.parseObject(RSAJson, RSADecodeData.class);
            boolean isTimeout = Objects.nonNull(rsaDecodeData)  && Objects.nonNull(rsaDecodeData.getTime()) && System.currentTimeMillis() -  rsaDecodeData.getTime() < timeout;
            if (!isTimeout){
                throw new ServiceException("Request timed out, please try again."); //请求超时
            }
            //解密AES
            String AESJson = AES256Util.decode(rsaDecodeData.getKey(),asy,rsaDecodeData.getKeyVI());
            System.out.println("AESJson: "+AESJson);
            return JSONObject.parseObject(AESJson,clazz);
        } catch (Exception e) {
            throw new RuntimeException("RSA decryption Exception:  " +e.getMessage());
        }
    }

    public static JSONObject getRequestDecryption(String sym, String asy){
        //验证密钥
        try {
            //解密RSA
            RSAPrivateKey rsaPrivateKey = ActivityRSAUtil.getRSAPrivateKeyByString(privateKey);
            String RSAJson = ActivityRSAUtil.privateDecrypt(sym, rsaPrivateKey);
            RSADecodeData rsaDecodeData = JSONObject.parseObject(RSAJson, RSADecodeData.class);
            boolean isTimeout = Objects.nonNull(rsaDecodeData)  && Objects.nonNull(rsaDecodeData.getTime()) && System.currentTimeMillis() -  rsaDecodeData.getTime() < timeout;
            if (!isTimeout){
                throw new ServiceException("Request timed out, please try again."); //请求超时
            }
            //解密AES
            String AESJson = AES256Util.decode(rsaDecodeData.getKey(),asy,rsaDecodeData.getKeyVI());
            System.out.println("AESJson: "+AESJson);
            return JSONObject.parseObject(AESJson);
        } catch (Exception e) {
            throw new RuntimeException("RSA decryption Exception:  " +e.getMessage());
        }
    }
}

4、ActivityRSAUtil 工具类

import org.apache.commons.io.IOUtils;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * @module
 * @author: qingxu.liu
 * @date: 2023-02-07 16:54
 * @copyright
 **/
public class ActivityRSAUtil {

    /**
     * 字符集
     */
    public static String CHARSET = "UTF-8";

    /**
     * 生成密钥对
     * @param keyLength  密钥长度
     * @return KeyPair
     */
    public static KeyPair getKeyPair(int keyLength) {
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");   //默认:RSA/None/PKCS1Padding
            keyPairGenerator.initialize(keyLength);
            return keyPairGenerator.generateKeyPair();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("生成密钥对时遇到异常" +  e.getMessage());
        }
    }

    /**
     * 获取公钥
     */
    public static byte[] getPublicKey(KeyPair keyPair) {
        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
        return rsaPublicKey.getEncoded();
    }

    /**
     * 获取私钥
     */
    public static byte[] getPrivateKey(KeyPair keyPair) {
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
        return rsaPrivateKey.getEncoded();
    }

    /**
     * 公钥字符串转PublicKey实例
     * @param publicKey 公钥字符串
     * @return          PublicKey
     * @throws Exception e
     */
    public static PublicKey getPublicKey(String publicKey) throws Exception {
        byte[] publicKeyBytes = Base64.getDecoder().decode(publicKey.getBytes());
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(keySpec);
    }

    /**
     * 私钥字符串转PrivateKey实例
     * @param privateKey  私钥字符串
     * @return PrivateKey
     * @throws Exception e
     */
    public static PrivateKey getPrivateKey(String privateKey) throws Exception {
        byte[] privateKeyBytes = Base64.getDecoder().decode(privateKey.getBytes());
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(keySpec);
    }

    /**
     * 获取公钥字符串
     * @param keyPair KeyPair
     * @return  公钥字符串
     */
    public static String getPublicKeyString(KeyPair keyPair){
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公钥
        return new String(org.apache.commons.codec.binary.Base64.encodeBase64(publicKey.getEncoded()));
    }

    /**
     * 获取私钥字符串
     * @param keyPair  KeyPair
     * @return 私钥字符串
     */
    public static String getPrivateKeyString(KeyPair keyPair){
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私钥
        return new String(org.apache.commons.codec.binary.Base64.encodeBase64((privateKey.getEncoded())));
    }


    /**
     * 公钥加密
     * @param data        明文
     * @param publicKey   公钥
     * @return            密文
     */
    public static String publicEncrypt(String data, RSAPublicKey publicKey) {
        try {
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] bytes = rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength());
            return new String(org.apache.commons.codec.binary.Base64.encodeBase64(bytes));
        } catch (Exception e) {
            throw new RuntimeException("加密字符串[" + data + "]时遇到异常"+  e.getMessage());
        }
    }

    /**
     * 私钥解密
     * @param data        密文
     * @param privateKey  私钥
     * @return            明文
     */
    public static String privateDecrypt(String data, RSAPrivateKey privateKey) {
        try {
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.getDecoder().decode(data), privateKey.getModulus().bitLength()), CHARSET);
        } catch (Exception e) {
            throw new RuntimeException("privateKey解密字符串[" + data + "]时遇到异常"+  e.getMessage());
        }
    }


    /**
     * 私钥加密
     * @param content 明文
     * @param privateKey 私钥
     * @return 密文
     */
    public static String encryptByPrivateKey(String content, RSAPrivateKey privateKey){

        try {
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            byte[] bytes = rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE,content.getBytes(CHARSET), privateKey.getModulus().bitLength());
            return new String(org.apache.commons.codec.binary.Base64.encodeBase64(bytes));
        } catch (Exception e) {
            throw new RuntimeException("privateKey加密字符串[" + content + "]时遇到异常" +  e.getMessage());
        }
    }

    /**
     * 公钥解密
     * @param content  密文
     * @param publicKey 私钥
     * @return  明文
     */
    public static String decryByPublicKey(String content, RSAPublicKey publicKey){
        try {
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.getDecoder().decode(content), publicKey.getModulus().bitLength()), CHARSET);
        } catch (Exception e) {
            throw new RuntimeException("publicKey解密字符串[" + content + "]时遇到异常" +e.getMessage());
        }
    }

    public static RSAPublicKey getRSAPublicKeyByString(String publicKey){
        try {
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return (RSAPublicKey)keyFactory.generatePublic(keySpec);
        } catch (Exception e) {
            throw new RuntimeException("String转PublicKey出错" + e.getMessage());
        }
    }


    public static RSAPrivateKey getRSAPrivateKeyByString(String privateKey){
        try {
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return (RSAPrivateKey)keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        } catch (Exception e) {
            throw new RuntimeException("String转PrivateKey出错" + e.getMessage());
        }
    }


    //rsa切割解码  , ENCRYPT_MODE,加密数据   ,DECRYPT_MODE,解密数据
    private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {
        int maxBlock = 0;  //最大块
        if (opmode == Cipher.DECRYPT_MODE) {
            maxBlock = keySize / 8;
        } else {
            maxBlock = keySize / 8 - 11;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] buff;
        int i = 0;
        try {
            while (datas.length > offSet) {
                if (datas.length - offSet > maxBlock) {
                    //可以调用以下的doFinal()方法完成加密或解密数据:
                    buff = cipher.doFinal(datas, offSet, maxBlock);
                } else {
                    buff = cipher.doFinal(datas, offSet, datas.length - offSet);
                }
                out.write(buff, 0, buff.length);
                i++;
                offSet = i * maxBlock;
            }
        } catch (Exception e) {
            throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常: " + e.getMessage());
        }
        byte[] resultDatas = out.toByteArray();
        IOUtils.closeQuietly(out);
        return resultDatas;
    }
}

5、AES256Util 工具类

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Base64;

/**
 * @module
 * @author: qingxu.liu
 * @date: 2023-02-07 16:14
 * @copyright
 **/

public class AES256Util {

    private static final String AES = "AES";
    /**
     * 初始向量IV, 初始向量IV的长度规定为128位16个字节, 初始向量的来源为随机生成.
     */
    /**
     * 加密解密算法/加密模式/填充方式
     */
    private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding";

    private static final Base64.Encoder base64Encoder = java.util.Base64.getEncoder();
    private static final Base64.Decoder base64Decoder = java.util.Base64.getDecoder();

    //通过在运行环境中设置以下属性启用AES-256支持
    static {
        Security.setProperty("crypto.policy", "unlimited");
    }
    /*
     * 解决java不支持AES/CBC/PKCS7Padding模式解密
     */
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    /**
     * AES加密
     */
    public static String encode(String key, String content,String keyVI) {
        try {
            javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(key.getBytes(), AES);
            javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM);
            cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(keyVI.getBytes()));
            // 获取加密内容的字节数组(这里要设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码
            byte[] byteEncode = content.getBytes(java.nio.charset.StandardCharsets.UTF_8);
            // 根据密码器的初始化方式加密
            byte[] byteAES = cipher.doFinal(byteEncode);
            // 将加密后的数据转换为字符串
            return base64Encoder.encodeToString(byteAES);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * AES解密
     */
    public static String decode(String key, String content,String keyVI) {
        try {
            javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(key.getBytes(), AES);
            javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM);
            cipher.init(javax.crypto.Cipher.DECRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(keyVI.getBytes()));
            // 将加密并编码后的内容解码成字节数组
            byte[] byteContent = base64Decoder.decode(content);
            // 解密
            byte[] byteDecode = cipher.doFinal(byteContent);
            return new String(byteDecode, java.nio.charset.StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * AES加密ECB模式PKCS7Padding填充方式
     * @param str 字符串
     * @param key 密钥
     * @return 加密字符串
     * @throws Exception 异常信息
     */
    public static String aes256ECBPkcs7PaddingEncrypt(String str, String key) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
        byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, AES));
        byte[] doFinal = cipher.doFinal(str.getBytes(StandardCharsets.UTF_8));
        return new String(Base64.getEncoder().encode(doFinal));
    }

    /**
     * AES解密ECB模式PKCS7Padding填充方式
     * @param str 字符串
     * @param key 密钥
     * @return 解密字符串
     * @throws Exception 异常信息
     */
    public static String aes256ECBPkcs7PaddingDecrypt(String str, String key) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
        byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, AES));
        byte[] doFinal = cipher.doFinal(Base64.getDecoder().decode(str));
        return new String(doFinal);
    }
}

亲测100%可用~~~

最后说一句(求关注!别白嫖!)

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/611160.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

动态IP避坑指南:如何挑选合适的动态代理IP?

在如今的网络环境中&#xff0c;使用动态IP代理成为实现隐私保护、访问受限内容和提高网络效率的一种常见方式&#xff0c;选择合适的国外动态IP代理可以让我们的业务处理事半功倍。面对市面上琳琅满目的选择&#xff0c;如何挑选购买适合自己的动态IP代理服务呢&#xff1f;在…

【软件工程】测试

目录 前言软件测试的目标测试准则测试方法测试方案&#xff08;重点&#xff09;白盒测试&#xff08;重点&#xff09;逻辑覆盖测试语句覆盖判定覆盖&#xff08;分支覆盖&#xff09;条件覆盖判定/条件覆盖条件组合覆盖总结 基本路径覆盖法 黑盒测试等价类法边界值分析法 软件…

速卖通ip地址会相互影响吗?如何防止账号关联?

在跨境电商行业&#xff0c;大部分平台都是不允许一个卖家操作多个店铺的&#xff0c;如果被平台检测出账户关联&#xff0c;可能会被封店。在速卖通平台&#xff0c;会通过IP地址来判断是否经营多个账号吗?IP地址会使店铺相互影响吗? 一、速卖通IP地址会关联吗? 首先各位卖…

从零开始学习生成树实验:一步一步走向精通

大家好&#xff0c;这里是G-LAB IT实验室。 ⭕5月18日 CCNAHCIA 新开班来啦&#x1f44f; 现在报名有早鸟价&#xff0c;感兴趣的可咨询 &#x1f447;&#x1f447;&#x1f447; 敲重点! 可小窗客服咨询课程价格 本课程包含线下面授、线上直播、录播、实验、考试习题、…

【数据库原理及应用】期末复习汇总高校期末真题试卷08

试卷 一、选择题(每题 2 分&#xff0c;共 30 分)    1. ___ ____是长期存储在计算机内的有组织,可共享的数据集合. A.数据库管理系统 B.数据库系统 C.数据库 D.文件组织 2. 数据库类型是按照 来划分…

Java医院绩效考核系统源码maven+Visual Studio Code一体化人力资源saas平台系统源码

Java医院绩效考核系统源码mavenVisual Studio Code一体化人力资源saas平台系统源码 医院绩效解决方案包括医院绩效管理&#xff08;BSC&#xff09;、综合奖金核算&#xff08;RBRVS&#xff09;&#xff0c;涵盖从绩效方案的咨询与定制、数据采集、绩效考核及反馈、绩效奖金核…

1.基于python的单细胞数据预处理-归一化

目录 归一化的引入移位对数皮尔森近似残差两个归一化方法的总结 参考&#xff1a; [1] https://github.com/Starlitnightly/single_cell_tutorial [2] https://github.com/theislab/single-cell-best-practices 归一化的引入 在质量控制中&#xff0c;已经从数据集删除了低质…

力扣HOT100 - 739. 每日温度

解题思路&#xff1a; 单调栈 class Solution {public int[] dailyTemperatures(int[] temperatures) {int length temperatures.length;int[] ans new int[length];Deque<Integer> stack new LinkedList<>();for (int i 0; i < length; i) {int temperatu…

【NLP练习】使用seq2seq实现文本翻译

使用seq2seq实现文本翻译 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 from __future__ import unicode_literals, print_function, division from io import open import unicodedata import string impo…

Star-CCM+分配零部件至区域2-根据零部件的特性分组分配零部件至区域

前言 前文已经讲解了将零部件分配至区域的方法。其中有一种方法是"将所有部件分配到一个区域"。在工程应用中&#xff0c;有时会把同一种类型的部件分配到一个区域&#xff0c;因此在一个项目中有可能需要多次进行"将所有部件分配到一个区域"。如在电机温…

分布式与一致性协议之MySQL XA协议

MySQL XA协议 概述 相信很多人都知道MySQL支持单机事务&#xff0c;那么在分布式系统中&#xff0c;涉及多个节点&#xff0c;MySQL又是怎样实现分布式事务的呢&#xff1f; 举个例子&#xff0c;一个业务系统需要接收来自外部的指令&#xff0c;然后访问多个内部其他系统来执…

OpenBayes 一周速览|Apple 开源大模型 OpenELM 上线;字节发布 COCONut 首个全景图像分割数据集,入选 CVPR2024

公共资源速递 This Weekly Snapshots &#xff01; 5 个数据集&#xff1a; * COCONut 大规模图像分割数据集 * THUCNews 新闻数据集 * DuConv 对话数据集 * 安徽电信知道问答数据集 * Sentiment Analysis 中文情感分析数据集 2 个模型&#xff1a; * OpenELM-3B-Inst…

三、配置带HybridCLR的ARCore开发环境

预告 本专栏将介绍如何使用这个支持热更的AR开发插件&#xff0c;快速地开发AR应用。 专栏&#xff1a; Unity开发AR系列 插件简介 通过热更技术实现动态地加载AR场景&#xff0c;简化了AR开发流程&#xff0c;让用户可更多地关注Unity场景内容的制作。 “EnvInstaller…”支…

【前端基础】CSS样式+Vue中绘制时间轴

深度选择器 在 Vue.js 中&#xff0c;/deep/、>>>、:deep 和 ::v-deep 这些都是深度选择器&#xff0c;用于修改子组件的样式。它们主要用于解决作用域样式和组件样式之间的冲突问题。 1. /deep/ 或 >>> /deep/ 和 >>> 是相同的选择器&#xff0c;…

rider自定义代码片段(以C#为例)

1.先看效果 2.在哪设置 File→Settings→Editor→Live Templates→C#3.咋定义 代码片段中的变量用$$包围&#xff0c;而且我们可以自定义变量名称&#xff0c;如CName。选择我们自定义的变量名称我们可以修改变量是否可以被修改以及变量将自动匹配的值。 比如将CName自动填充…

123. SQL优化技巧汇总

文章目录 1 避免使用select *2 用union all代替union3 小表驱动大表4 批量操作5 多用limit6 in中值太多7 增量查询8 高效的分页9 用连接查询代替子查询10 join的表不宜过多11 join时要注意12 控制索引的数量13 选择合理的字段类型14 提升group by的效率15 索引优化 sql优化是一…

07_Flutter使用NestedScrollView+TabBarView滚动位置共享问题修复

07_Flutter使用NestedScrollViewTabBarView滚动位置共享问题修复 一.案发现场 可以看到&#xff0c;上图中三个列表的滑动位置共享了&#xff0c;滑动其中一个列表&#xff0c;会影响到另外两个&#xff0c;这显然不符合要求&#xff0c;先来看下布局&#xff0c;再说明产生这个…

Nginx rewrite项目练习

Nginx rewrite练习 1、访问ip/xcz&#xff0c;返回400状态码&#xff0c;要求用rewrite匹配/xcz a、访问/xcz返回400 b、访问/hello时正常访问xcz.html页面server {listen 192.168.99.137:80;server_name 192.168.99.137;charset utf-8;root /var/www/html;location / {root …

TDN: Temporal Difference Networks for Efficient Action Recognition 论文阅读

TDN: Temporal Difference Networks for Efficient Action Recognition 论文阅读 Abstract1. Introduction2. Related work3. Temporal Difference Networks3.1. Overview3.2. Short-term TDM3.3. Long-term TDM3.4. Exemplar: TDN-ResNet 4. ExperimentsAblation studiesCompa…

智能创作时代:AI引领下的内容生产革命与效率提升

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…
最新文章