SpringBoot實現(xiàn)RAS+AES自動接口解密
一、講個事故
接口安全老生常談了
過年之前做了過一款飛機大戰(zhàn)的H5小游戲,里面無限模式-需要保存用戶的積分,因為使用的Body傳參,參數(shù)是可見的,為了接口安全我,我和前端約定了傳遞參數(shù)是:用戶無限模式的積分+“我們約定的一個數(shù)字”+用戶id的和,在用Base64
加密,請求到服務器我再解密,出用戶無限模式的積分;如下:
{ "integral": "MTExMTM0NzY5NQ==", }
可是過年的時候,運營突然找我說無限模式積分排行榜分數(shù)不對:
這就很詭異了,第二名才一萬多分,第一名就40多萬分?。。?!
一開始我以為是我解密有問題,反復看了好幾變,可就兩三行代碼不可能有問題的?。?!
沒辦法我去翻了好久的日志,才發(fā)現(xiàn)這個用戶把我接口參數(shù)給改了。。。。
他把Base64
接口參數(shù)改了
事已至此,我也不能怪用戶,誰讓我把人家想得太簡單,接口安全也沒到位
所以年后上班第一件是就是把接口加密的工作搞起來
目前常用的加密方式就對稱性加密和非對稱性加密,加密解密的操作的肯定是大家知道的,最重要的使用什么加密解密方式,制定什么樣的加密策略;考慮到我技術水平和接口的速度,采用的是RAS非對稱加密和AES對稱加密一起用?。。?!
二、RSA和AES基礎知識
1、非對稱加密和對稱加密
非對稱加密
非對稱加密算法是一種密鑰的保密方法。 非對稱加密算法需要兩個密鑰:公開密鑰(publickey:簡稱公鑰)和私有密鑰(privatekey:簡稱私鑰)。 公鑰與私鑰是一對,如果用公鑰對數(shù)據(jù)進行加密,只有用對應的私鑰才能解密。 因為加密和解密使用的是兩個不同的密鑰,所以這種算法叫作非對稱加密算法。
對稱加密
加密秘鑰和解密秘鑰是一樣,當你的密鑰被別人知道后,就沒有秘密可言了
AES 是對稱加密算法,優(yōu)點:加密速度快;缺點:如果秘鑰丟失,就容易解密密文,安全性相對比較差
RSA 是非對稱加密算法 , 優(yōu)點:安全 ;缺點:加密速度慢
2、RSA基礎知識
RSA
——非對稱加密,會產(chǎn)生公鑰和私鑰,公鑰在客戶端,私鑰在服務端。公鑰用于加密,私鑰用于解密。
大概的流程:
客戶端向服務器發(fā)送消息: 客戶端用公鑰加密信息,發(fā)送給服務端,服務端再用私鑰機密
服務器向客戶端發(fā)送消息:服務端用私鑰加密信息,發(fā)送給客戶端,客戶端再用公鑰機密
當然中間要保障密鑰的安全,還有很多為了保障數(shù)據(jù)安全的操作,比如數(shù)字簽名,證書簽名等等,在這我們就先不說了;
RSA加密解密算法支持三種填充模式,
分別是ENCRYPTION_OAEP
、ENCRYPTION_PKCS1
、ENCRYPTION_NONE
,RSA填充是為了和公鑰等長。
- ENCRYPTION_OAEP:最優(yōu)非對稱加密填充,是RSA加密和RSA解密最新最安全的推薦填充模式。
- ENCRYPTION_PKCS1:隨機填充數(shù)據(jù)模式,每次加密的結(jié)果都不一樣,是RSA加密和RSA解密使用最為廣泛的填充模式。
- ENCRYPTION_NONE:不填充模式,是RSA加密和RSA解密使用較少的填充模式。
RSA 常用的加密填充模式
- RSA/None/PKCS1Padding
- RSA/ECB/PKCS1Padding
知識點:
- Java 默認的 RSA 實現(xiàn)是 RSA/None/PKCS1Padding
- 在創(chuàng)建RSA秘鑰對時,長度最好選擇 2048的整數(shù)倍,長度為1024在已經(jīng)不很安全了
- 一般由服務器創(chuàng)建秘鑰對,私鑰保存在服務器,公鑰下發(fā)至客戶端
- DER是RSA密鑰的二進制格式,PEM是DER轉(zhuǎn)碼為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解密時使用相同的密鑰,或是使用兩個可以簡單地相互推算的密鑰,一般用于服務端對服務端之間對數(shù)據(jù)進行加密解密。它是一種為了替代原先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填充模式
塊密碼只能對確定長度的數(shù)據(jù)塊進行處理,而消息的長度通常是可變的,因此需要選擇填充模式。
填充區(qū)別
:在ECB、CBC工作模式下最后一塊要在加密前進行填充,其它不用選擇填充模式;填充模式
:AES支持的填充模式為PKCS7和NONE不填充。其中PKCS7標準是主流加密算法都遵循的數(shù)據(jù)填充算法。AES標準規(guī)定的區(qū)塊長度為固定值128Bit,對應的字節(jié)長度為16位,這明顯和PKCS5標準規(guī)定使用的固定值8位不符,雖然有些框架特殊處理后可以通用PKCS5,但是從長遠和兼容性考慮,推薦PKCS7。
AES密鑰KEY和初始化向量IV
初始化向量IV可以有效提升安全性,但是在實際的使用場景中,它不能像密鑰KEY那樣直接保存在配置文件或固定寫死在代碼中,一般正確的處理方式為:在加密端將IV設置為一個16位的隨機值,然后和加密文本一起返給解密端即可。
密鑰KEY
:AES標準規(guī)定區(qū)塊長度只有一個值,固定為128Bit,對應的字節(jié)為16位。AES算法規(guī)定密鑰長度只有三個值,128Bit、192Bit、256Bit,對應的字節(jié)為16位、24位和32位,其中密鑰KEY不能公開傳輸,用于加密解密數(shù)據(jù);初始化向量IV
:該字段可以公開,用于將加密隨機化。同樣的明文被多次加密也會產(chǎn)生不同的密文,避免了較慢的重新產(chǎn)生密鑰的過程,初始化向量與密鑰相比有不同的安全性需求,因此IV通常無須保密。然而在大多數(shù)情況中,不應當在使用同一密鑰的情況下兩次使用同一個IV,一般推薦初始化向量IV為16位的隨機值。
三、加密策略
RAS、AES加密解密的操作都是一樣,如果有效的結(jié)合到一起才能達到更好的加密效果很重要;
上面說到:
AES 是對稱加密算法,優(yōu)點:加密速度快;缺點:如果秘鑰丟失,就容易解密密文,安全性相對比較差
RSA 是非對稱加密算法 , 優(yōu)點:安全 ;缺點:加密速度慢
1、主要思路:
那么我們就結(jié)合2個加密算法的優(yōu)點來操作:
1、因為接口傳遞的參數(shù)有多有少,當接口傳遞的參數(shù)過多時,使用RSA加密會導致加密速度慢,所以我們使用AES加密加密接口參數(shù)
2、因為AES的密鑰key和偏移量VI都是固定的所以可以使用RSA加密
3、客戶端將AES加密后的密文和RSA加密后的密文,傳遞給服務器即可。
2、涉及工具類:
util包下:
ActivityRSAUtilAES256UtilRequestDecryptionUtil
3、加密策略
4、交互方式
前端:
1、客戶端隨機生成2個16為的AES密鑰和AES偏移量
2、使用AES加密算法加密真實傳遞參數(shù),得到參數(shù)密文“asy”
3、將AES密鑰、AES偏移量和當前時間戳,格式如下:
- key:密鑰
- keyVI:偏移量
- time:請求時間,用戶判斷是否重復請求
{ "key":"0t7FtCDKofbEVpSZS", "keyVI":"0t7WESMofbEVpSZS", "time":211213232323323 } //轉(zhuǎn)成JSON字符串
4、AES信息密鑰信息,再使用RSA公鑰加密,得到AES密鑰的密文“sym”
5、將“sym”和“asy”作為body參數(shù),調(diào)用接口
后端:
1、在接口接收參數(shù)中,多增加2個字段接收加密后的“sym”和“asy” (名字可以自己定,能接收到就行)
2、使用RequestDecryptionUtil.getRequestDecryption()方法解密,返回解密后的真實傳遞參數(shù)
四、服務器自動解密
因為不是每個接口都需求加密解密,我們可以自定義一個注解,將需要解密的接口上加一個這個注解,
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)建一個aop切片
1、AOP判斷controller接收到請求是否帶有@RequestRSA
注解
2、如果帶有注解,通過ProceedingJoinPoint類getArgs()方法獲取請求的body參數(shù),
3、將body參數(shù),傳為JSONObject類,獲取到"asy"和"sym"屬性,再調(diào)用RequestDecryptionUtil解密獲取接口傳遞的真實參數(shù)
4、獲取接口入?yún)⒌念?/p>
5、將獲取解密后的真實參數(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 請求驗證RSA & AES 統(tǒng)一驗證切面 **/ @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ù) 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ù),封裝到接口入?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密鑰,偏移量、時間等信息
{ "key":"0t7FtSMofbEVpSZS", "keyVI":"0t7FtSMofbEVpSZS", "time":211213232323323 }
2、獲取當前時間戳,與time比較是否超過一分鐘(6000毫秒),超過就拋出“Request timed out, please try again”異常
3、沒有超時,將獲取的到AES密鑰和偏移量,再對“asy”解密獲取接口傳遞的真實參數(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){ //驗證密鑰 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(); } ? /** * 公鑰字符串轉(zhuǎn)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); } ? /** * 私鑰字符串轉(zhuǎn)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轉(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ù)時發(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個字節(jié), 初始向量的來源為隨機生成. */ /** * 加密解密算法/加密模式/填充方式 */ 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(); ? //通過在運行環(huán)境中設置以下屬性啟用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ù)組(這里要設置為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%可用~~~
到此這篇關于SpringBoot實現(xiàn)RAS+AES自動接口解密的文章就介紹到這了,更多相關SpringBoot RAS+AES自動接口解密內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解決DataInputStream?read不等于-1,socket文件傳輸只能傳輸一個文件無法傳輸多個問題
這篇文章主要介紹了解決DataInputStream?read不等于-1,socket文件傳輸只能傳輸一個文件無法傳輸多個問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08Java中Comparable和Comparator兩種比較器的區(qū)別詳解
這篇文章主要介紹了Java中Comparable和Comparator兩種比較器的區(qū)別詳解,Comparable接口將比較代碼嵌入自身類中,像Integer、String等這些基本類型的JAVA封裝類都已經(jīng)實現(xiàn)了Comparable接口,這些類對象本身就支持和自己比較,需要的朋友可以參考下2023-09-09Spring?Boot?使用?Hutool-jwt?實現(xiàn)?token?驗證功能
JWT?就是一種網(wǎng)絡身份認證和信息交換格式,這篇文章主要介紹了Spring Boot使用Hutool-jwt實現(xiàn)token驗證,需要的朋友可以參考下2023-07-07