인터페이스 안전은 진부하다
설을 앞두고 비행기 전투에 관한 H5 미니 게임을 만들었는데, 무한 모드에서는 사용자 포인트가 적립되어야 하기 때문이다. 인터페이스 보안을 위해 나와 프런트엔드는 전달되는 매개변수가 다음과 같다고 합의했습니다. 사용자의 무제한 모드 포인트 + "우리가 합의한 숫자" + 합계 Base64
로 암호화된 사용자 ID 의 서버에 대한 요청을 해독하여 다음과 같이 사용자의 무제한 모드 포인트를 얻었습니다. Base64
加密,请求到服务器我再解密,出用户无限模式的积分;如下:
{ "integral": "MTExMTM0NzY5NQ==", }
可是过年的时候,运营突然找我说无限模式积分排行榜分数不对:
这就很诡异了,第二名才一万多分,第一名就40多万分!!!!
一开始我以为是我解密有问题,反复看了好几变,可就两三行代码不可能有问题的!!!
没办法我去翻了好久的日志,才发现这个用户把我接口参数给改了。。。。
他把Base64
接口参数改了
事已至此,我也不能怪用户,谁让我把人家想得太简单,接口安全也没到位
所以年后上班第一件是就是把接口加密的工作搞起来
目前常用的加密方式就对称性加密和非对称性加密,加密解密的操作的肯定是大家知道的,最重要的使用什么加密解密方式,制定什么样的加密策略;考虑到我技术水平和接口的速度,采用的是RAS非对称加密和AES对称加密一起用!!!!
非对称加密
非对称加密算法是一种密钥的保密方法。 非对称加密算法需要两个密钥:公开密钥(publickey:简称公钥)和私有密钥(privatekey:简称私钥)。公钥和私钥是成对出现的,使用公钥加密数据后,只有对应的私钥才能进行解密。由于使用两个不同的密钥进行加密和解密,因此这种算法被称为非对称加密算法。
对称加密
加密秘钥和解密秘钥是一样,当你的密钥被别人知道后,就没有秘密可言了
AES 是对称加密算法,优点:加密速度快;缺点:如果秘钥丢失,就容易解密密文,安全性相对比较差
RSA 是非对称加密算法 , 优点:安全 ;缺点:加密速度慢
RSA
——非对称加密,会产生公钥和私钥,公钥在客户端,私钥在服务端。公钥用于加密,私钥用于解密。
大概的流程:
客户端向服务器发送消息: 客户端用公钥加密信息,发送给服务端,服务端再用私钥机密
服务器向客户端发送消息:服务端用私钥加密信息,发送给客户端,客户端再用公钥机密
当然中间要保障密钥的安全,还有很多为了保障数据安全的操作,比如数字签名,证书签名等等,在这我们就先不说了;
RSA加密解密算法支持三种填充模式,
分别是ENCRYPTION_OAEP
、ENCRYPTION_PKCS1
、ENCRYPTION_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(); }
처음에는 복호화에 문제가 있는 줄 알고 여러번 읽어보았으나 코드 두세줄만 있어도 문제가 없을 수가 없었습니다! ! !
오랜 시간 동안 로그를 살펴보았지만 이 사용자가 내 인터페이스 매개변수를 변경했다는 사실을 알게 되었습니다. . . .Base64
인터페이스 매개변수를 변경했습니다이제 문제가 끝났으니 너무 단순하게 생각하라고 했던 사용자를 비난할 수 없습니다. 인터페이스 보안도 안 되어있습니다
그래서 새해가 지나면 처음으로 출근합니다. 한 가지는 인터페이스 암호화 작업을 시작하는 것입니다2. RSA 및 AES에 대한 기본 지식
비대칭 암호화 알고리즘은 핵심 기밀 유지 방법입니다. 비대칭 암호화 알고리즘에는 공개 키(공개 키: 공개 키라고 함)와 개인 키(개인 키: 개인 키라고 함)의 두 가지 키가 필요합니다. 공개 키와 개인 키는 쌍으로 나타납니다. 공개 키를 사용하여 데이터를 암호화한 후에는 해당 개인 키로만 해독할 수 있습니다. 암호화와 복호화에는 서로 다른 두 개의 키가 사용되므로 이 알고리즘을 비대칭 암호화 알고리즘이라고 합니다.
AES는 대칭 암호화 알고리즘입니다
. 장점: 빠른 암호화 속도; : 비밀키를 분실하면 암호문을 복호화하기 쉽고 보안이 상대적으로 취약합니다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(); }
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 是非对称加密算法 , 优点:安全 ;缺点:加密速度慢
那么我们就结合2个加密算法的优点来操作:
1、因为接口传递的参数有多有少,当接口传递的参数过多时,使用RSA加密会导致加密速度慢,所以我们使用AES加密加密接口参数
2、因为AES的密钥key和偏移量VI都是固定的所以可以使用RSA加密
3、客户端将AES加密后的密文和RSA加密后的密文,传递给服务器即可。
util包下:
ActivityRSAUtilAES256UtilRequestDecryptionUtil
前端:
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、如果带有注解,通过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 중국어 웹사이트의 기타 관련 기사를 참조하세요!