SpringBoot實(shí)現(xiàn)RAS+AES自動接口解密
一、講個(gè)事故
接口安全老生常談了
過年之前做了過一款飛機(jī)大戰(zhàn)的H5小游戲,里面無限模式-需要保存用戶的積分,因?yàn)槭褂玫腂ody傳參,參數(shù)是可見的,為了接口安全我,我和前端約定了傳遞參數(shù)是:用戶無限模式的積分+“我們約定的一個(gè)數(shù)字”+用戶id的和,在用Base64加密,請求到服務(wù)器我再解密,出用戶無限模式的積分;如下:
{
"integral": "MTExMTM0NzY5NQ==",
}可是過年的時(shí)候,運(yùn)營突然找我說無限模式積分排行榜分?jǐn)?shù)不對:

這就很詭異了,第二名才一萬多分,第一名就40多萬分!?。。?/p>
一開始我以為是我解密有問題,反復(fù)看了好幾變,可就兩三行代碼不可能有問題的?。?!
沒辦法我去翻了好久的日志,才發(fā)現(xiàn)這個(gè)用戶把我接口參數(shù)給改了。。。。
他把Base64接口參數(shù)改了

事已至此,我也不能怪用戶,誰讓我把人家想得太簡單,接口安全也沒到位
所以年后上班第一件是就是把接口加密的工作搞起來
目前常用的加密方式就對稱性加密和非對稱性加密,加密解密的操作的肯定是大家知道的,最重要的使用什么加密解密方式,制定什么樣的加密策略;考慮到我技術(shù)水平和接口的速度,采用的是RAS非對稱加密和AES對稱加密一起用?。。?!
二、RSA和AES基礎(chǔ)知識
1、非對稱加密和對稱加密
非對稱加密
非對稱加密算法是一種密鑰的保密方法。 非對稱加密算法需要兩個(gè)密鑰:公開密鑰(publickey:簡稱公鑰)和私有密鑰(privatekey:簡稱私鑰)。 公鑰與私鑰是一對,如果用公鑰對數(shù)據(jù)進(jìn)行加密,只有用對應(yīng)的私鑰才能解密。 因?yàn)榧用芎徒饷苁褂玫氖莾蓚€(gè)不同的密鑰,所以這種算法叫作非對稱加密算法。
對稱加密
加密秘鑰和解密秘鑰是一樣,當(dāng)你的密鑰被別人知道后,就沒有秘密可言了
AES 是對稱加密算法,優(yōu)點(diǎn):加密速度快;缺點(diǎn):如果秘鑰丟失,就容易解密密文,安全性相對比較差
RSA 是非對稱加密算法 , 優(yōu)點(diǎn):安全 ;缺點(diǎn):加密速度慢
2、RSA基礎(chǔ)知識
RSA——非對稱加密,會產(chǎn)生公鑰和私鑰,公鑰在客戶端,私鑰在服務(wù)端。公鑰用于加密,私鑰用于解密。
大概的流程:
客戶端向服務(wù)器發(fā)送消息: 客戶端用公鑰加密信息,發(fā)送給服務(wù)端,服務(wù)端再用私鑰機(jī)密
服務(wù)器向客戶端發(fā)送消息:服務(wù)端用私鑰加密信息,發(fā)送給客戶端,客戶端再用公鑰機(jī)密
當(dāng)然中間要保障密鑰的安全,還有很多為了保障數(shù)據(jù)安全的操作,比如數(shù)字簽名,證書簽名等等,在這我們就先不說了;
RSA加密解密算法支持三種填充模式,
分別是ENCRYPTION_OAEP、ENCRYPTION_PKCS1、ENCRYPTION_NONE,RSA填充是為了和公鑰等長。
- ENCRYPTION_OAEP:最優(yōu)非對稱加密填充,是RSA加密和RSA解密最新最安全的推薦填充模式。
- ENCRYPTION_PKCS1:隨機(jī)填充數(shù)據(jù)模式,每次加密的結(jié)果都不一樣,是RSA加密和RSA解密使用最為廣泛的填充模式。
- ENCRYPTION_NONE:不填充模式,是RSA加密和RSA解密使用較少的填充模式。
RSA 常用的加密填充模式
- RSA/None/PKCS1Padding
- RSA/ECB/PKCS1Padding
知識點(diǎn):
- Java 默認(rèn)的 RSA 實(shí)現(xiàn)是 RSA/None/PKCS1Padding
- 在創(chuàng)建RSA秘鑰對時(shí),長度最好選擇 2048的整數(shù)倍,長度為1024在已經(jīng)不很安全了
- 一般由服務(wù)器創(chuàng)建秘鑰對,私鑰保存在服務(wù)器,公鑰下發(fā)至客戶端
- DER是RSA密鑰的二進(jìn)制格式,PEM是DER轉(zhuǎn)碼為Base64的字符格式,由于DER是二進(jìn)制格式,不便于閱讀和理解。一般而言,密鑰都是通過PEM的格式進(jìn)行存儲的
/**
* 生成密鑰對
* @param keyLength 密鑰長度
* @return KeyPair
*/
public static KeyPair getKeyPair(int keyLength) {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); //默認(rèn):RSA/None/PKCS1Padding
keyPairGenerator.initialize(keyLength);
return keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("生成密鑰對時(shí)遇到異常" + 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基礎(chǔ)知識
AES 簡介 AES加密解密算法是一種可逆的對稱加密算法,這類算法在加密和AES解密時(shí)使用相同的密鑰,或是使用兩個(gè)可以簡單地相互推算的密鑰,一般用于服務(wù)端對服務(wù)端之間對數(shù)據(jù)進(jìn)行加密解密。它是一種為了替代原先DES、3DES而建立的高級加密標(biāo)準(zhǔn)(Advanced Encryption Standard)。作為可逆且對稱的塊加密,AES加密算法的速度比公鑰加密等加密算法快很多,在很多場合都需要AES對稱加密,但是要求加密端和解密端雙方都使用相同的密鑰是AES算法的主要缺點(diǎn)之一。
AES加密解密
AES加密需要:明文 + 密鑰+ 偏移量(IV)+密碼模式(算法/模式/填充) AES解密需要:密文 + 密鑰+ 偏移量(IV)+密碼模式(算法/模式/填充)
AES的算法模式一般為 AES/CBC/PKCS5Padding 或 AES/CBC/PKCS7Padding
AES常見的工作模式:
- 電碼本模式(ECB)
- 密碼分組鏈接模式(CBC)
- 計(jì)算器模式(CTR)
- 密碼反饋模式(CFB)
- 輸出反饋模式(OFB)
除了ECB無須設(shè)置初始化向量IV而不安全之外,其它AES工作模式都必須設(shè)置向量IV,其中GCM工作模式較為特殊。
AES填充模式
塊密碼只能對確定長度的數(shù)據(jù)塊進(jìn)行處理,而消息的長度通常是可變的,因此需要選擇填充模式。
填充區(qū)別:在ECB、CBC工作模式下最后一塊要在加密前進(jìn)行填充,其它不用選擇填充模式;填充模式:AES支持的填充模式為PKCS7和NONE不填充。其中PKCS7標(biāo)準(zhǔn)是主流加密算法都遵循的數(shù)據(jù)填充算法。AES標(biāo)準(zhǔn)規(guī)定的區(qū)塊長度為固定值128Bit,對應(yīng)的字節(jié)長度為16位,這明顯和PKCS5標(biāo)準(zhǔn)規(guī)定使用的固定值8位不符,雖然有些框架特殊處理后可以通用PKCS5,但是從長遠(yuǎn)和兼容性考慮,推薦PKCS7。
AES密鑰KEY和初始化向量IV
初始化向量IV可以有效提升安全性,但是在實(shí)際的使用場景中,它不能像密鑰KEY那樣直接保存在配置文件或固定寫死在代碼中,一般正確的處理方式為:在加密端將IV設(shè)置為一個(gè)16位的隨機(jī)值,然后和加密文本一起返給解密端即可。
密鑰KEY:AES標(biāo)準(zhǔn)規(guī)定區(qū)塊長度只有一個(gè)值,固定為128Bit,對應(yīng)的字節(jié)為16位。AES算法規(guī)定密鑰長度只有三個(gè)值,128Bit、192Bit、256Bit,對應(yīng)的字節(jié)為16位、24位和32位,其中密鑰KEY不能公開傳輸,用于加密解密數(shù)據(jù);初始化向量IV:該字段可以公開,用于將加密隨機(jī)化。同樣的明文被多次加密也會產(chǎn)生不同的密文,避免了較慢的重新產(chǎn)生密鑰的過程,初始化向量與密鑰相比有不同的安全性需求,因此IV通常無須保密。然而在大多數(shù)情況中,不應(yīng)當(dāng)在使用同一密鑰的情況下兩次使用同一個(gè)IV,一般推薦初始化向量IV為16位的隨機(jī)值。
三、加密策略
RAS、AES加密解密的操作都是一樣,如果有效的結(jié)合到一起才能達(dá)到更好的加密效果很重要;
上面說到:
AES 是對稱加密算法,優(yōu)點(diǎn):加密速度快;缺點(diǎn):如果秘鑰丟失,就容易解密密文,安全性相對比較差
RSA 是非對稱加密算法 , 優(yōu)點(diǎn):安全 ;缺點(diǎn):加密速度慢
1、主要思路:
那么我們就結(jié)合2個(gè)加密算法的優(yōu)點(diǎn)來操作:
1、因?yàn)榻涌趥鬟f的參數(shù)有多有少,當(dāng)接口傳遞的參數(shù)過多時(shí),使用RSA加密會導(dǎo)致加密速度慢,所以我們使用AES加密加密接口參數(shù)
2、因?yàn)锳ES的密鑰key和偏移量VI都是固定的所以可以使用RSA加密
3、客戶端將AES加密后的密文和RSA加密后的密文,傳遞給服務(wù)器即可。
2、涉及工具類:
util包下:
ActivityRSAUtilAES256UtilRequestDecryptionUtil
3、加密策略

4、交互方式
前端:
1、客戶端隨機(jī)生成2個(gè)16為的AES密鑰和AES偏移量
2、使用AES加密算法加密真實(shí)傳遞參數(shù),得到參數(shù)密文“asy”
3、將AES密鑰、AES偏移量和當(dāng)前時(shí)間戳,格式如下:
- key:密鑰
- keyVI:偏移量
- time:請求時(shí)間,用戶判斷是否重復(fù)請求
{
"key":"0t7FtCDKofbEVpSZS",
"keyVI":"0t7WESMofbEVpSZS",
"time":211213232323323
}
//轉(zhuǎn)成JSON字符串4、AES信息密鑰信息,再使用RSA公鑰加密,得到AES密鑰的密文“sym”
5、將“sym”和“asy”作為body參數(shù),調(diào)用接口

后端:
1、在接口接收參數(shù)中,多增加2個(gè)字段接收加密后的“sym”和“asy” (名字可以自己定,能接收到就行)
2、使用RequestDecryptionUtil.getRequestDecryption()方法解密,返回解密后的真實(shí)傳遞參數(shù)
四、服務(wù)器自動解密
因?yàn)椴皇敲總€(gè)接口都需求加密解密,我們可以自定義一個(gè)注解,將需要解密的接口上加一個(gè)這個(gè)注解,
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、創(chuàng)建一個(gè)aop切片
1、AOP判斷controller接收到請求是否帶有@RequestRSA注解
2、如果帶有注解,通過ProceedingJoinPoint類getArgs()方法獲取請求的body參數(shù),
3、將body參數(shù),傳為JSONObject類,獲取到"asy"和"sym"屬性,再調(diào)用RequestDecryptionUtil解密獲取接口傳遞的真實(shí)參數(shù)
4、獲取接口入?yún)⒌念?/p>
5、將獲取解密后的真實(shí)參數(shù),封裝到接口入?yún)⒌念愔?/p>
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 請求驗(yàn)證RSA & AES 統(tǒng)一驗(yàn)證切面
**/
@Aspect
@Component
@Order(2)
@Slf4j
public class RequestRSAAspect {
?
/**
* 1> 獲取請求參數(shù)
* 2> 獲取被請求接口的入?yún)㈩愋?
* 3> 判斷是否為get請求 是則跳過AES解密判斷
* 4> 請求參數(shù)解密->封裝到接口的入?yún)?
*/
?
@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參數(shù)
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();
//調(diào)用RequestDecryptionUtil方法解密,獲取解密后的真實(shí)參數(shù)
JSONObject decryption = RequestDecryptionUtil.getRequestDecryption(sym, asy);
//獲取接口入?yún)⒌念?
String typeName = joinPoint.getArgs()[0].getClass().getTypeName();
System.out.println("參數(shù)值類型:"+ typeName);
Class<?> aClass = joinPoint.getArgs()[0].getClass();
//將獲取解密后的真實(shí)參數(shù),封裝到接口入?yún)⒌念愔?
Object o = JSONObject.parseObject(decryption.toJSONString(), aClass);
Object[] as = {o};
return joinPoint.proceed(as);
}
return joinPoint.proceed();
}
?
/**
* 根據(jù)方法和傳入的參數(shù)獲取請求參數(shù) 獲取的是接口的入?yún)?
*/
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注解修飾的參數(shù)作為請求參數(shù)
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密鑰,偏移量、時(shí)間等信息
{
"key":"0t7FtSMofbEVpSZS",
"keyVI":"0t7FtSMofbEVpSZS",
"time":211213232323323
}2、獲取當(dāng)前時(shí)間戳,與time比較是否超過一分鐘(6000毫秒),超過就拋出“Request timed out, please try again”異常
3、沒有超時(shí),將獲取的到AES密鑰和偏移量,再對“asy”解密獲取接口傳遞的真實(shí)參數(shù)
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 接口入?yún)㈩?
* @return Object
*/
public static <T> Object getRequestDecryption(String sym, String asy, Class<T> clazz){
//驗(yàn)證密鑰
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."); //請求超時(shí)
}
//解密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){
//驗(yàn)證密鑰
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."); //請求超時(shí)
}
//解密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"); //默認(rèn):RSA/None/PKCS1Padding
keyPairGenerator.initialize(keyLength);
return keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("生成密鑰對時(shí)遇到異常" + 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();
}
?
/**
* 公鑰字符串轉(zhuǎn)PublicKey實(shí)例
* @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);
}
?
/**
* 私鑰字符串轉(zhuǎn)PrivateKey實(shí)例
* @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 + "]時(shí)遇到異常"+ 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 + "]時(shí)遇到異常"+ 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 + "]時(shí)遇到異常" + 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 + "]時(shí)遇到異常" +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轉(zhuǎn)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轉(zhuǎn)PrivateKey出錯" + e.getMessage());
}
}
?
?
//rsa切割解碼 , ENCRYPT_MODE,加密數(shù)據(jù) ,DECRYPT_MODE,解密數(shù)據(jù)
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) {
//可以調(diào)用以下的doFinal()方法完成加密或解密數(shù)據(jù):
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 + "]的數(shù)據(jù)時(shí)發(fā)生異常: " + 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的長度規(guī)定為128位16個(gè)字節(jié), 初始向量的來源為隨機(jī)生成.
*/
/**
* 加密解密算法/加密模式/填充方式
*/
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();
?
//通過在運(yùn)行環(huán)境中設(shè)置以下屬性啟用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()));
// 獲取加密內(nèi)容的字節(jié)數(shù)組(這里要設(shè)置為utf-8)不然內(nèi)容中如果有中文和英文混合中文就會解密為亂碼
byte[] byteEncode = content.getBytes(java.nio.charset.StandardCharsets.UTF_8);
// 根據(jù)密碼器的初始化方式加密
byte[] byteAES = cipher.doFinal(byteEncode);
// 將加密后的數(shù)據(jù)轉(zhuǎn)換為字符串
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()));
// 將加密并編碼后的內(nèi)容解碼成字節(jié)數(shù)組
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%可用~~~
到此這篇關(guān)于SpringBoot實(shí)現(xiàn)RAS+AES自動接口解密的文章就介紹到這了,更多相關(guān)SpringBoot RAS+AES自動接口解密內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決DataInputStream?read不等于-1,socket文件傳輸只能傳輸一個(gè)文件無法傳輸多個(gè)問題
這篇文章主要介紹了解決DataInputStream?read不等于-1,socket文件傳輸只能傳輸一個(gè)文件無法傳輸多個(gè)問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08
Java中Comparable和Comparator兩種比較器的區(qū)別詳解
這篇文章主要介紹了Java中Comparable和Comparator兩種比較器的區(qū)別詳解,Comparable接口將比較代碼嵌入自身類中,像Integer、String等這些基本類型的JAVA封裝類都已經(jīng)實(shí)現(xiàn)了Comparable接口,這些類對象本身就支持和自己比較,需要的朋友可以參考下2023-09-09
使用Springboot自定義注解,支持SPEL表達(dá)式
這篇文章主要介紹了使用Springboot自定義注解,支持SPEL表達(dá)式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02
JAVA中SSM框架的搭建實(shí)現(xiàn)CRUD的方法
這篇文章主要介紹了JAVA中SSM框架的搭建實(shí)現(xiàn)CRUD的方法,小編把運(yùn)行代碼列了出來,供大家參考。2017-11-11
Spring?Boot?使用?Hutool-jwt?實(shí)現(xiàn)?token?驗(yàn)證功能
JWT?就是一種網(wǎng)絡(luò)身份認(rèn)證和信息交換格式,這篇文章主要介紹了Spring Boot使用Hutool-jwt實(shí)現(xiàn)token驗(yàn)證,需要的朋友可以參考下2023-07-07

