목차
1. 사고에 대해 이야기해보자
二、RSA和AES基础知识
1、非对称加密和对称加密
2、RSA基础知识
1. 비대칭 암호화 및 대칭 암호화
2. of RSA
3、AES基础知识
三、加密策略
1、主要思路:
2、涉及工具类:
3、加密策略
4、交互方式
四、服务器自动解密
Java java지도 시간 SpringBoot가 RAS+AES 자동 인터페이스 암호 해독을 구현하는 방법

SpringBoot가 RAS+AES 자동 인터페이스 암호 해독을 구현하는 방법

May 20, 2023 pm 04:04 PM
aes springboot

    1. 사고에 대해 이야기해보자

    인터페이스 안전은 진부하다

    설을 앞두고 비행기 전투에 관한 H5 미니 게임을 만들었는데, 무한 모드에서는 사용자 포인트가 적립되어야 하기 때문이다. 인터페이스 보안을 위해 나와 프런트엔드는 전달되는 매개변수가 다음과 같다고 합의했습니다. 사용자의 무제한 모드 포인트 + "우리가 합의한 숫자" + 합계 Base64로 암호화된 사용자 ID 의 서버에 대한 요청을 해독하여 다음과 같이 사용자의 무제한 모드 포인트를 얻었습니다. Base64加密,请求到服务器我再解密,出用户无限模式的积分;如下:

    {
        "integral": "MTExMTM0NzY5NQ==",
    }
    로그인 후 복사

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

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

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

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

    他把Base64接口参数改了

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

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

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

    二、RSA和AES基础知识

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

    非对称加密

    非对称加密算法是一种密钥的保密方法。 非对称加密算法需要两个密钥:公开密钥(publickey:简称公钥)和私有密钥(privatekey:简称私钥)。公钥和私钥是成对出现的,使用公钥加密数据后,只有对应的私钥才能进行解密。由于使用两个不同的密钥进行加密和解密,因此这种算法被称为非对称加密算法。

    对称加密

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

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

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

    2、RSA基础知识

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

    大概的流程:

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

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

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

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

    分别是ENCRYPTION_OAEPENCRYPTION_PKCS1ENCRYPTION_NONE

        /**
         * 生成密钥对
         * @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();
        }
    로그인 후 복사
    로그인 후 복사

    그러나 구정 기간 동안 운영자는 갑자기 찾아와서 무한모드 포인트 순위 목록 점수가 틀렸다고 하더군요:
    • 이거 참 이상하네요, 2위는 10,000포인트가 넘고, 1위는 400,000포인트가 넘네요! ! ! !

      처음에는 복호화에 문제가 있는 줄 알고 여러번 읽어보았으나 코드 두세줄만 있어도 문제가 없을 수가 없었습니다! ! !

      오랜 시간 동안 로그를 살펴보았지만 이 사용자가 내 인터페이스 매개변수를 변경했다는 사실을 알게 되었습니다. . . .
    • 그가 Base64 인터페이스 매개변수를 변경했습니다

      이제 문제가 끝났으니 너무 단순하게 생각하라고 했던 사용자를 비난할 수 없습니다. 인터페이스 보안도 안 되어있습니다

      그래서 새해가 지나면 처음으로 출근합니다. 한 가지는 인터페이스 암호화 작업을 시작하는 것입니다
    • 현재 일반적으로 사용되는 암호화 방법은 대칭 암호화와 비대칭 암호화입니다. 가장 중요한 것은 어떤 암호화와 복호화 방법을 사용할 것인지, 어떤 방식으로 암호화할 것인지입니다. 이러한 암호화 전략은 제 기술 수준과 인터페이스 속도를 고려하여 RAS 비대칭 암호화와 AES 대칭 암호화를 함께 사용합니다! ! ! !

      2. RSA 및 AES에 대한 기본 지식

      1. 비대칭 암호화 및 대칭 암호화

    • 비대칭 암호화

    비대칭 암호화 알고리즘은 핵심 기밀 유지 방법입니다. 비대칭 암호화 알고리즘에는 공개 키(공개 키: 공개 키라고 함)와 개인 키(개인 키: 개인 키라고 함)의 두 가지 키가 필요합니다. 공개 키와 개인 키는 쌍으로 나타납니다. 공개 키를 사용하여 데이터를 암호화한 후에는 해당 개인 키로만 해독할 수 있습니다. 암호화와 복호화에는 서로 다른 두 개의 키가 사용되므로 이 알고리즘을 비대칭 암호화 알고리즘이라고 합니다.

      대칭 암호화
    • 암호화 키와 복호화 키가 동일합니다. 자신의 키가 다른 사람에게 알려지면 전혀 비밀이 없습니다.
    • AES는 대칭 암호화 알고리즘입니다

      . 장점: 빠른 암호화 속도; : 비밀키를 분실하면 암호문을 복호화하기 쉽고 보안이 상대적으로 취약합니다

    RSA는 비대칭 암호화 알고리즘

    , 장점: 보안성: 느린 암호화 속도

      2. of RSA

    • RSA——비대칭 암호화는 공개 키와 개인 키를 생성합니다. 공개 키는 클라이언트 측에 있고 개인 키는 서버 측에 있습니다. 공개키는 암호화에 사용되고, 개인키는 복호화에 사용됩니다.

      대략적인 프로세스:
    • 클라이언트가 서버에 메시지를 보냅니다. 클라이언트는 공개 키로 정보를 암호화하여 서버에 보내고, 서버는 개인 키를 사용하여 이를 비밀로 유지합니다.

      서버 클라이언트에게 메시지를 보냅니다. 서버는 개인 키를 사용하여 정보를 암호화하여 클라이언트에 보내고, 클라이언트는 공개 키를 사용하여 이를 비밀로 유지합니다
    • 물론 키의 보안은 다음과 같습니다. 중간에 보장되며 디지털 서명, 인증서 서명 등과 같은 데이터 보안을 보장하는 많은 작업이 있습니다. 여기서는 이에 대해 이야기하지 않겠습니다.

    • RSA 암호화 및 암호 해독 알고리즘은 세 가지 채우기 모드
    • ,

      를 지원합니다. ENCRYPTION_OAEP, ENCRYPTION_PKCS1, ENCRYPTION_NONE , RSA 패딩은 공개 키와 길이가 동일해야 합니다. 🎜🎜🎜🎜🎜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로 트랜스코딩되는 문자 형식입니다. 바이너리 형식으로는 읽고 이해하기가 쉽지 않습니다. 일반적으로 키는 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/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个加密算法的优点来操作:

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

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

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

    2、涉及工具类:

    util包下:

    ActivityRSAUtilAES256UtilRequestDecryptionUtil

    3、加密策略

    SpringBoot가 RAS+AES 자동 인터페이스 암호 해독을 구현하는 방법

    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参数,调用接口

    SpringBoot가 RAS+AES 자동 인터페이스 암호 해독을 구현하는 방법

    后端:

    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、如果带有注解,通过ProceedingJoinPoint类getArgs()方法获取请求的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);
        }
    }
    로그인 후 복사

    위 내용은 SpringBoot가 RAS+AES 자동 인터페이스 암호 해독을 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    본 웹사이트의 성명
    본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

    핫 AI 도구

    Undresser.AI Undress

    Undresser.AI Undress

    사실적인 누드 사진을 만들기 위한 AI 기반 앱

    AI Clothes Remover

    AI Clothes Remover

    사진에서 옷을 제거하는 온라인 AI 도구입니다.

    Undress AI Tool

    Undress AI Tool

    무료로 이미지를 벗다

    Clothoff.io

    Clothoff.io

    AI 옷 제거제

    Video Face Swap

    Video Face Swap

    완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

    뜨거운 도구

    메모장++7.3.1

    메모장++7.3.1

    사용하기 쉬운 무료 코드 편집기

    SublimeText3 중국어 버전

    SublimeText3 중국어 버전

    중국어 버전, 사용하기 매우 쉽습니다.

    스튜디오 13.0.1 보내기

    스튜디오 13.0.1 보내기

    강력한 PHP 통합 개발 환경

    드림위버 CS6

    드림위버 CS6

    시각적 웹 개발 도구

    SublimeText3 Mac 버전

    SublimeText3 Mac 버전

    신 수준의 코드 편집 소프트웨어(SublimeText3)

    Springboot가 Jasypt를 통합하여 구성 파일 암호화를 구현하는 방법 Springboot가 Jasypt를 통합하여 구성 파일 암호화를 구현하는 방법 Jun 01, 2023 am 08:55 AM

    Jasypt 소개 Jasypt는 개발자가 최소한의 노력으로 프로젝트에 기본 암호화 기능을 추가할 수 있게 해주며 암호화 작동 방식에 대한 깊은 이해가 필요하지 않은 단방향 및 양방향 암호화에 대한 높은 보안을 제공합니다. 표준 기반 암호화 기술. 비밀번호, 텍스트, 숫자, 바이너리 암호화... Spring 기반 애플리케이션, 개방형 API와의 통합에 적합하며 모든 JCE 공급자와 함께 사용할 수 있습니다... 다음 종속성을 추가합니다: com.github.ulisesbocchiojasypt-spring-boot-starter2. Jasypt의 이점은 코드가 유출되더라도 데이터 소스를 보장할 수 있어 시스템 보안을 보호합니다.

    SpringBoot가 Redisson을 통합하여 지연 대기열을 구현하는 방법 SpringBoot가 Redisson을 통합하여 지연 대기열을 구현하는 방법 May 30, 2023 pm 02:40 PM

    사용 시나리오 1. 주문이 성공적으로 이루어졌으나 30분 이내에 결제가 이루어지지 않았습니다. 결제 시간이 초과되어 주문이 자동으로 취소되었습니다. 2. 주문이 서명되었으며 서명 후 7일 동안 평가가 수행되지 않았습니다. 주문 시간이 초과되어 평가되지 않으면 시스템은 기본적으로 긍정적 평가로 설정됩니다. 3. 판매자가 5분 동안 주문을 받지 않으면 주문이 취소됩니다. 문자 메시지 알림이 전송됩니다... 지연이 길고 실시간 성능이 낮은 시나리오의 경우 작업 예약을 사용하여 정기적인 폴링 처리를 수행할 수 있습니다. 예: xxl-job 오늘은 다음을 선택하겠습니다.

    Redis를 사용하여 SpringBoot에서 분산 잠금을 구현하는 방법 Redis를 사용하여 SpringBoot에서 분산 잠금을 구현하는 방법 Jun 03, 2023 am 08:16 AM

    1. Redis는 분산 잠금 원칙과 분산 잠금이 필요한 이유를 구현합니다. 분산 잠금에 대해 이야기하기 전에 분산 잠금이 필요한 이유를 설명해야 합니다. 분산 잠금의 반대는 독립형 잠금입니다. 다중 스레드 프로그램을 작성할 때 공유 변수를 동시에 작동하여 발생하는 데이터 문제를 방지하기 위해 일반적으로 잠금을 사용하여 공유 변수를 상호 제외합니다. 공유 변수의 사용 범위는 동일한 프로세스에 있습니다. 동시에 공유 리소스를 운영해야 하는 여러 프로세스가 있는 경우 어떻게 상호 배타적일 수 있습니까? 오늘날의 비즈니스 애플리케이션은 일반적으로 마이크로서비스 아키텍처입니다. 이는 하나의 애플리케이션이 여러 프로세스를 배포한다는 의미이기도 합니다. 여러 프로세스가 MySQL에서 동일한 레코드 행을 수정해야 하는 경우 잘못된 작업으로 인해 발생하는 더티 데이터를 방지하려면 배포가 필요합니다. 현재 소개할 스타일은 잠겨 있습니다. 포인트를 얻고 싶다

    springboot가 파일을 jar 패키지로 읽은 후 파일에 액세스할 수 없는 문제를 해결하는 방법 springboot가 파일을 jar 패키지로 읽은 후 파일에 액세스할 수 없는 문제를 해결하는 방법 Jun 03, 2023 pm 04:38 PM

    Springboot가 파일을 읽지만 jar 패키지로 패키징한 후 최신 개발에 액세스할 수 없습니다. springboot가 파일을 jar 패키지로 패키징한 후 파일을 읽을 수 없는 상황이 발생합니다. 그 이유는 패키징 후 파일의 가상 경로 때문입니다. 유효하지 않으며 읽기를 통해서만 액세스할 수 있습니다. 파일은 리소스 publicvoidtest(){Listnames=newArrayList();InputStreamReaderread=null;try{ClassPathResourceresource=newClassPathResource("name.txt");Input 아래에 있습니다.

    여러 테이블을 추가하기 위해 SQL 문을 사용하지 않고 Springboot+Mybatis-plus를 구현하는 방법 여러 테이블을 추가하기 위해 SQL 문을 사용하지 않고 Springboot+Mybatis-plus를 구현하는 방법 Jun 02, 2023 am 11:07 AM

    Springboot+Mybatis-plus가 다중 테이블 추가 작업을 수행하기 위해 SQL 문을 사용하지 않을 때 내가 직면한 문제는 테스트 환경에서 생각을 시뮬레이션하여 분해됩니다. 매개 변수가 있는 BrandDTO 개체를 생성하여 배경으로 매개 변수 전달을 시뮬레이션합니다. Mybatis-plus에서 다중 테이블 작업을 수행하는 것은 매우 어렵다는 것을 Mybatis-plus-join과 같은 도구를 사용하지 않으면 해당 Mapper.xml 파일을 구성하고 냄새나고 긴 ResultMap만 구성하면 됩니다. 해당 SQL 문을 작성합니다. 이 방법은 번거로워 보이지만 매우 유연하며 다음을 수행할 수 있습니다.

    SpringBoot와 SpringMVC의 비교 및 ​​차이점 분석 SpringBoot와 SpringMVC의 비교 및 ​​차이점 분석 Dec 29, 2023 am 11:02 AM

    SpringBoot와 SpringMVC는 모두 Java 개발에서 일반적으로 사용되는 프레임워크이지만 둘 사이에는 몇 가지 분명한 차이점이 있습니다. 이 기사에서는 이 두 프레임워크의 기능과 용도를 살펴보고 차이점을 비교할 것입니다. 먼저 SpringBoot에 대해 알아봅시다. SpringBoot는 Spring 프레임워크를 기반으로 하는 애플리케이션의 생성 및 배포를 단순화하기 위해 Pivotal 팀에서 개발되었습니다. 독립 실행형 실행 파일을 구축하는 빠르고 가벼운 방법을 제공합니다.

    SpringBoot가 Redis를 사용자 정의하여 캐시 직렬화를 구현하는 방법 SpringBoot가 Redis를 사용자 정의하여 캐시 직렬화를 구현하는 방법 Jun 03, 2023 am 11:32 AM

    1. RedisAPI 기본 직렬화 메커니즘인 RedisTemplate1.1을 사용자 정의합니다. API 기반 Redis 캐시 구현은 데이터 캐싱 작업에 RedisTemplate 템플릿을 사용합니다. 여기서 RedisTemplate 클래스를 열고 클래스의 소스 코드 정보를 봅니다. 키 선언, 값의 다양한 직렬화 방법, 초기 값은 비어 있음 @NullableprivateRedisSe

    SpringBoot+Dubbo+Nacos 개발 실습 튜토리얼 SpringBoot+Dubbo+Nacos 개발 실습 튜토리얼 Aug 15, 2023 pm 04:49 PM

    이 글에서는 dubbo+nacos+Spring Boot의 실제 개발에 대해 이야기하기 위해 자세한 예제를 작성하겠습니다. 이 기사에서는 이론적 지식을 너무 많이 다루지는 않지만 dubbo를 nacos와 통합하여 개발 환경을 신속하게 구축하는 방법을 설명하는 가장 간단한 예를 작성합니다.

    See all articles