當前位置: 妍妍網 > 碼農

SpringBoot 實作 RAS+AES 自動介面解密!

2024-05-11碼農

來源:juejin.cn/post/7203931072260915259

👉 歡迎 ,你將獲得: 專屬的計畫實戰 / Java 學習路線 / 一對一提問 / 學習打卡 / 每月贈書

新計畫: 仿小紅書 (微服務架構)正在更新中... , 全棧前後端分離部落格計畫 2.0 版本完結啦, 演示連結 http://116.62.199.48/ 。全程手摸手,後端 + 前端全棧開發,從 0 到 1 講解每個功能點開發步驟,1v1 答疑,直到計畫上線。 目前已更新了261小節,累計41w+字,講解圖:1806張,還在持續爆肝中.. 後續還會上新更多計畫,目標是將Java領域典型的計畫都整一波,如秒殺系統, 線上商城, IM即時通訊,Spring Cloud Alibaba 等等,

一、講個事故

介面安全老生常談了

過年之前做了過一款飛機大戰的H5小遊戲,裏面無限模式-需要保存使用者的積分,因為使用的Body傳參,參數是可見的。

為了介面安全我,我和前端約定了傳遞參數是:使用者無限模式的積分+「我們約定的一個數位」+使用者id的和,在用Base64加密,請求到伺服器我再解密,出使用者無限模式的積分;如下:

{
"integral""MTExMTM0NzY5NQ==",
}

可是過年的時候,營運突然找我說無限模式積分排行榜分數不對:

圖片

圖片

這就很詭異了,第二名才一萬多分,第一名就40多萬分!!!!

一開始我以為是我解密有問題,反復看了好幾變,可就兩三行程式碼不可能有問題的!!!

沒辦法我去翻了好久的日誌,才發現這個使用者把我介面參數給改了。。。。

他把Base64介面參數改了

圖片

圖片

事已至此,我也不能怪使用者,誰讓我把人家想得太簡單,介面安全也沒到位

所以年後上班第一件是就是把介面加密的工作搞起來

目前常用的加密方式就對稱性加密和非對稱性加密,加密解密的操作的肯定是大家知道的,最重要的使用什麽加密解密方式,制定什麽樣的加密策略;考慮到我技術水平胡介面的速度,采用的是RAS非對稱加密和AES對稱加密一起用!!!!

二、RSA和AES基礎知識

1、非對稱加密和對稱加密

非對稱加密

非對稱加密演算法是一種金鑰的保密方法。非對稱加密演算法需要兩個金鑰:公開金鑰(publickey:簡稱公鑰)和私有金鑰(privatekey:簡稱私鑰)。

公鑰與私鑰是一對,如果用公鑰對數據進行加密,只有用對應的私鑰才能解密。因為加密和解密使用的是兩個不同的金鑰,所以這種演算法叫作非對稱加密演算法。

對稱加密

加密秘鑰和解密秘鑰是一樣,當你的金鑰被別人知道後,就沒有秘密可言了

AES 是對稱加密演算法,優點:加密速度快;缺點:如果秘鑰遺失,就容易解密密文,安全性相對比較差

RSA 是非對稱加密演算法 , 優點:安全 ;缺點:加密速度慢

2、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的字元格式,由於DER是二進制格式,不便於閱讀和理解。一般而言,金鑰都是透過PEM的格式進行儲存的

    /**
     * 生成金鑰對
     * @param keyLength 金鑰長度
     * @return KeyPair
     */
    public static KeyPair getKeyPair(int keyLength) {
    try {
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); //預設:RSA/None/PKCS1Padding
    keyPairGenerator.initialize(keyLength);
    return keyPairGenerator.generateKeyPair();
    } catch (NoSuchAlgorithmException e) {
    throw new RuntimeException("生成金鑰對時遇到異常" + e.getMessage());
    }
    }
    /**
     * 獲取公鑰
     */
    public static byte[] getPublicKey(KeyPair keyPair) {
    RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
    return rsaPublicKey.getEncoded();
    }
    /**
     * 獲取私鑰
     */
    public static byte[] getPrivateKey(KeyPair keyPair) {
    RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
    return rsaPrivateKey.getEncoded();
    }

    3、AES基礎知識

    AES 簡介

    AES加密解密演算法是一種可逆的對稱加密演算法,這類演算法在加密和AES解密時使用相同的金鑰,或是使用兩個可以簡單地相互推算的金鑰,一般用於伺服端對伺服端之間對數據進行加密解密。它是一種為了替代原先DES、3DES而建立的高級加密標準(Advanced Encryption Standard)。

    作為可逆且對稱的塊加密,AES加密演算法的速度比公鑰加密等加密演算法快很多,在很多場合都需要AES對稱加密,但是要求加密端和解密端雙方都使用相同的金鑰是AES演算法的主要缺點之一。

    AES加密解密

    AES加密需要:明文 + 金鑰+ 偏移量(IV)+密碼模式(演算法/模式/填充) AES解密需要:密文 + 金鑰+ 偏移量(IV)+密碼模式(演算法/模式/填充)

    AES的演算法模式一般為 AES/CBC/PKCS5Padding AES/CBC/PKCS7Padding

    AES常見的工作模式:

  • 電碼本模式(ECB)

  • 密分碼組連結模式(CBC)

  • 小算盤模式(CTR)

  • 密碼反饋模式(CFB)

  • 輸出反饋模式(OFB)

  • 除了ECB無須設定初始化向量IV而不安全之外,其它AES工作模式都必須設定向量IV,其中GCM工作模式較為特殊。

    AES填充模式

    塊密碼只能對確定長度的數據塊進行處理,而訊息的長度通常是可變的,因此需要選擇填充模式。

  • 填充區別 :在ECB、CBC工作模式下最後一塊要在加密前進行填充,其它不用選擇填充模式;

  • 填充模式 :AES支持的填充模式為PKCS7和NONE不填充。其中PKCS7標準是主流加密演算法都遵循的數據填充演算法。AES標準規定的區塊長度為固定值128Bit,對應的字節長度為16位元,這明顯和PKCS5標準規定使用的固定值8位元不符,雖然有些框架特殊處理後可以通用PKCS5,但是從長遠和相容性考慮,推薦PKCS7。

  • AES金鑰KEY和初始化向量IV

    初始化向量IV可以有效提升安全性,但是在實際的使用場景中,它不能像金鑰KEY那樣直接保存在配置檔或固定寫死在程式碼中,一般正確的處理方式為:在加密端將IV設定為一個16位元的隨機值,然後和加密文本一起返給解密端即可。

  • 金鑰KEY :AES標準規定區塊長度只有一個值,固定為128Bit,對應的字節為16位元。AES演算法規定金鑰長度只有三個值,128Bit、192Bit、256Bit,對應的字節為16位元、24位元和32位元,其中金鑰KEY不能公開傳輸,用於加密解密數據;

  • 初始化向量IV :該欄位可以公開,用於將加密隨機化。同樣的明文被多次加密也會產生不同的密文,避免了較慢的重新產生金鑰的過程,初始化向量與金鑰相比有不同的安全性需求,因此IV通常無須保密。然而在大多數情況中,不應當在使用同一金鑰的情況下兩次使用同一個IV,一般推薦初始化向量IV為16位元的隨機值。

  • 三、加密策略

    RAS、AES加密解密的操作都是一樣,如果有效的結合到一起才能達到更好的加密效果很重要;

    上面說到:

    AES 是對稱加密演算法 ,優點:加密速度快;缺點:如果秘鑰遺失,就容易解密密文,安全性相對比較差

    RSA 是非對稱加密演算法 , 優點:安全 ;缺點:加密速度慢

    1、主要思路:

    那麽我們就結合2個加密演算法的優點來操作:

  • 因為介面傳遞的參數有多有少,當介面傳遞的參數過多時,使用RSA加密會導致加密速度慢,所以我們使用AES加密加密介面參數

  • 因為AES的金鑰key和偏移量VI都是固定的所以可以使用RSA加密

  • 客戶端將AES加密後的密文和RSA加密後的密文,傳遞給伺服器即可。

  • 2、涉及工具類:

    util包下:

  • ActivityRSAUtil

  • AES256Util

  • RequestDecryptionUtil

  • 3、加密策略

    圖片
    4、互動方式

    前端:

    1、客戶端隨機生成2個16為的AES金鑰和AES偏移量

    2、使用AES加密演算法加密真實傳遞參數,得到參數密文「asy」

    3、將AES金鑰、AES偏移量和當前時間戳,格式如下:

  • key:金鑰

  • keyVI:偏移量

  • time:請求時間,使用者判斷是否重復請求

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

    4、AES資訊金鑰資訊,再使用RSA公鑰加密,得到AES金鑰的密文「sym」

    5、將「sym」和「asy」作為body參數,呼叫介面

    圖片

    後端:

    1、在介面接收參數中,多增加2個欄位接收加密後的「sym」和「asy」 (名字可以自己定,能接收到就行)

    2、使用 RequestDecryptionUtil.getRequestDecryption() 方法解密,返回解密後的真實傳遞參數

    四、伺服器自動解密

    因為不是每個介面都需求加密解密,我們可以自訂一個註解,將需要解密的介面上加一個這個註解,

    1、自訂解密註解:@RequestRSA

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RequestRSA {
    }

    2、建立一個aop切片
    1. AOP判斷controller接收到請求是否帶有 @RequestRSA 註解

    2. 如果帶有註解,透過 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].get class().getTypeName();
    System.out.println("參數值型別:"+ typeName);
    class<?> a class = joinPoint.getArgs()[0].get class();
    //將獲取解密後的真實參數,封裝到介面入參的類中
    Object o = JSONObject.parseObject(decryption.toJSONString(), a class);
    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;
    elseif (argList.size() == 1) {
    return argList.get(0);
    else {
    return argList;
    }
    }
    }




    3、RequestDecryptionUtil 解密類

    1、使用privateKey私鑰對」sym「解密獲取到客戶端加密的AES金鑰,偏移量、時間等資訊

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

    2、獲取當前時間戳,與time比較是否超過一分鐘(6000毫秒),超過就丟擲「 Request timed out, please try again 」異常

    3、沒有超時,將獲取的到AES金鑰和偏移量,再對「asy」解密獲取介面傳遞的真實參數

    import com.alibaba.fastjson.JSONObject;
    import app.activity.common.rsa.RSADecodeData;
    import app.common.exception.ServiceException;
    import java.security.interfaces.RSAPrivateKey;
    import java.util.Objects;
    /**
     * @module
     * @author: qingxu.liu
     * @date: 2023-02-09 17:43
     * @copyright
     **/
    public class RequestDecryptionUtil {
    private final static String publicKey = "RSA生成的公鑰";
    private final static String privateKey = "RSA生成的私鑰";
    private final static Integer timeout = 60000;
    /**
    *
    * @param sym RSA 密文
    * @param asy AES 密文
    * @param clazz 介面入參類
    * @return Object
    */
    public static <T> Object getRequestDecryption(String sym, String asy, class<T> clazz){
    //驗證金鑰
    try {
    //解密RSA
    RSAPrivateKey rsaPrivateKey = ActivityRSAUtil.getRSAPrivateKeyByString(privateKey);
    String RSAJson = ActivityRSAUtil.privateDecrypt(sym, rsaPrivateKey);
    RSADecodeData rsaDecodeData = JSONObject.parseObject(RSAJson, RSADecodeData. class);
    boolean isTimeout = Objects.nonNull(rsaDecodeData) && Objects.nonNull(rsaDecodeData.getTime()) && System.currentTimeMillis() - rsaDecodeData.getTime() < timeout;
    if (!isTimeout){
    throw new ServiceException("Request timed out, please try again."); //請求超時
    }
    //解密AES
    String AESJson = AES256Util.decode(rsaDecodeData.getKey(),asy,rsaDecodeData.getKeyVI());
    System.out.println("AESJson: "+AESJson);
    return JSONObject.parseObject(AESJson,clazz);
    } catch (Exception e) {
    throw new RuntimeException("RSA decryption Exception: " +e.getMessage());
    }
    }
    public static JSONObject getRequestDecryption(String sym, String asy){
    //驗證金鑰
    try {
    //解密RSA
    RSAPrivateKey rsaPrivateKey = ActivityRSAUtil.getRSAPrivateKeyByString(privateKey);
    String RSAJson = ActivityRSAUtil.privateDecrypt(sym, rsaPrivateKey);
    RSADecodeData rsaDecodeData = JSONObject.parseObject(RSAJson, RSADecodeData. class);
    boolean isTimeout = Objects.nonNull(rsaDecodeData) && Objects.nonNull(rsaDecodeData.getTime()) && System.currentTimeMillis() - rsaDecodeData.getTime() < timeout;
    if (!isTimeout){
    throw new ServiceException("Request timed out, please try again."); //請求超時
    }
    //解密AES
    String AESJson = AES256Util.decode(rsaDecodeData.getKey(),asy,rsaDecodeData.getKeyVI());
    System.out.println("AESJson: "+AESJson);
    return JSONObject.parseObject(AESJson);
    } catch (Exception e) {
    throw new RuntimeException("RSA decryption Exception: " +e.getMessage());
    }
    }
    }



    4、ActivityRSAUtil 工具類

    import org.apache.commons.io.IOUtils;
    import javax.crypto.Cipher;
    import java.io.ByteArrayOutputStream;
    import java.security.*;
    import java.security.interfaces.RSAPrivateKey;
    import java.security.interfaces.RSAPublicKey;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.security.spec.X509EncodedKeySpec;
    import java.util.Base64;
    /**
     * @module
     * @author: qingxu.liu
     * @date: 2023-02-07 16:54
     * @copyright
     **/
    public class ActivityRSAUtil {
    /**
    * 字元集
    */
    public static String CHARSET = "UTF-8";
    /**
    * 生成金鑰對
    * @param keyLength 金鑰長度
    * @return KeyPair
    */
    public static KeyPair getKeyPair(int keyLength) {
    try {
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); //預設:RSA/None/PKCS1Padding
    keyPairGenerator.initialize(keyLength);
    return keyPairGenerator.generateKeyPair();
    } catch (NoSuchAlgorithmException e) {
    throw new RuntimeException("生成金鑰對時遇到異常" + e.getMessage());
    }
    }
    /**
    * 獲取公鑰
    */
    public static byte[] getPublicKey(KeyPair keyPair) {
    RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
    return rsaPublicKey.getEncoded();
    }
    /**
    * 獲取私鑰
    */
    public static byte[] getPrivateKey(KeyPair keyPair) {
    RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
    return rsaPrivateKey.getEncoded();
    }
    /**
    * 公鑰字串轉PublicKey例項
    * @param publicKey 公鑰字串
    * @return PublicKey
    * @throws Exception e
    */
    public static PublicKey getPublicKey(String publicKey) throws Exception {
    byte[] publicKeyBytes = Base64.getDecoder().decode(publicKey.getBytes());
    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    return keyFactory.generatePublic(keySpec);
    }
    /**
    * 私鑰字串轉PrivateKey例項
    * @param privateKey 私鑰字串
    * @return PrivateKey
    * @throws Exception e
    */
    public static PrivateKey getPrivateKey(String privateKey) throws Exception {
    byte[] privateKeyBytes = Base64.getDecoder().decode(privateKey.getBytes());
    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    return keyFactory.generatePrivate(keySpec);
    }
    /**
    * 獲取公鑰字串
    * @param keyPair KeyPair
    * @return 公鑰字串
    */
    public static String getPublicKeyString(KeyPair keyPair){
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到公鑰
    return new String(org.apache.commons.codec.binary.Base64.encodeBase64(publicKey.getEncoded()));
    }
    /**
    * 獲取私鑰字串
    * @param keyPair KeyPair
    * @return 私鑰字串
    */
    public static String getPrivateKeyString(KeyPair keyPair){
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到私鑰
    return new String(org.apache.commons.codec.binary.Base64.encodeBase64((privateKey.getEncoded())));
    }

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

    /**
    * 私鑰加密
    * @param content 明文
    * @param privateKey 私鑰
    * @return 密文
    */
    public static String encryptByPrivateKey(String content, RSAPrivateKey privateKey){
    try {
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.ENCRYPT_MODE, privateKey);
    byte[] bytes = rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE,content.getBytes(CHARSET), privateKey.getModulus().bitLength());
    return new String(org.apache.commons.codec.binary.Base64.encodeBase64(bytes));
    } catch (Exception e) {
    throw new RuntimeException("privateKey加密字串[" + content + "]時遇到異常" + e.getMessage());
    }
    }
    /**
    * 公鑰解密
    * @param content 密文
    * @param publicKey 私鑰
    * @return 明文
    */
    public static String decryByPublicKey(String content, RSAPublicKey publicKey){
    try {
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.DECRYPT_MODE, publicKey);
    return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.getDecoder().decode(content), publicKey.getModulus().bitLength()), CHARSET);
    } catch (Exception e) {
    throw new RuntimeException("publicKey解密字串[" + content + "]時遇到異常" +e.getMessage());
    }
    }
    public static RSAPublicKey getRSAPublicKeyByString(String publicKey){
    try {
    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    return (RSAPublicKey)keyFactory.generatePublic(keySpec);
    } catch (Exception e) {
    throw new RuntimeException("String轉PublicKey出錯" + e.getMessage());
    }
    }

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

    //rsa切割解碼 , ENCRYPT_MODE,加密數據 ,DECRYPT_MODE,解密數據
    private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {
    int maxBlock = 0; //最大塊
    if (opmode == Cipher.DECRYPT_MODE) {
    maxBlock = keySize / 8;
    else {
    maxBlock = keySize / 8 - 11;
    }
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    int offSet = 0;
    byte[] buff;
    int i = 0;
    try {
    while (datas.length > offSet) {
    if (datas.length - offSet > maxBlock) {
    //可以呼叫以下的doFinal()方法完成加密或解密數據:
    buff = cipher.doFinal(datas, offSet, maxBlock);
    else {
    buff = cipher.doFinal(datas, offSet, datas.length - offSet);
    }
    out.write(buff, 0, buff.length);
    i++;
    offSet = i * maxBlock;
    }
    } catch (Exception e) {
    throw new RuntimeException("加解密閥值為[" + maxBlock + "]的數據時發生異常: " + e.getMessage());
    }
    byte[] resultDatas = out.toByteArray();
    IOUtils.closeQuietly(out);
    return resultDatas;
    }
    }















    5、AES256Util 工具類

    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    import javax.crypto.Cipher;
    import javax.crypto.spec.SecretKeySpec;
    import java.nio.charset.StandardCharsets;
    import java.security.Security;
    import java.util.Base64;
    /**
     * @module
     * @author: qingxu.liu
     * @date: 2023-02-07 16:14
     * @copyright
     **/
    public class AES256Util {
    private static final String AES = "AES";
    /**
    * 初始向量IV, 初始向量IV的長度規定為128位元16個字節, 初始向量的來源為隨機生成.
    */
    /**
    * 加密解密演算法/加密模式/填充方式
    */
    private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding";
    private static final Base64.Encoder base64Encoder = java.util.Base64.getEncoder();
    private static final Base64.Decoder base64Decoder = java.util.Base64.getDecoder();
    //透過在執行環境中設定以下內容啟用AES-256支持
    static {
    Security.setProperty("crypto.policy""unlimited");
    }
    /*
    * 解決java不支持AES/CBC/PKCS7Padding模式解密
    */
    static {
    Security.addProvider(new BouncyCastleProvider());
    }
    /**
    * AES加密
    */
    public static String encode(String key, String content,String keyVI) {
    try {
    javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(key.getBytes(), AES);
    javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM);
    cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(keyVI.getBytes()));
    // 獲取加密內容的字節陣列(這裏要設定為utf-8)不然內容中如果有中文和英文混合中文就會解密為亂碼
    byte[] byteEncode = content.getBytes(java.nio.charset.StandardCharsets.UTF_8);
    // 根據密碼器的初始化方式加密
    byte[] byteAES = cipher.doFinal(byteEncode);
    // 將加密後的數據轉換為字串
    return base64Encoder.encodeToString(byteAES);
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    /**
    * AES解密
    */
    public static String decode(String key, String content,String keyVI) {
    try {
    javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(key.getBytes(), AES);
    javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM);
    cipher.init(javax.crypto.Cipher.DECRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(keyVI.getBytes()));
    // 將加密並編碼後的內容解碼成字節陣列
    byte[] byteContent = base64Decoder.decode(content);
    // 解密
    byte[] byteDecode = cipher.doFinal(byteContent);
    return new String(byteDecode, java.nio.charset.StandardCharsets.UTF_8);
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    /**
    * AES加密ECB模式PKCS7Padding填充方式
    * @param str 字串
    * @param key 金鑰
    * @return 加密字串
    * @throws Exception 異常資訊
    */
    public static String aes256ECBPkcs7PaddingEncrypt(String str, String key) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
    byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
    cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, AES));
    byte[] doFinal = cipher.doFinal(str.getBytes(StandardCharsets.UTF_8));
    return new String(Base64.getEncoder().encode(doFinal));
    }
    /**
    * AES解密ECB模式PKCS7Padding填充方式
    * @param str 字串
    * @param key 金鑰
    * @return 解密字串
    * @throws Exception 異常資訊
    */
    public static String aes256ECBPkcs7PaddingDecrypt(String str, String key) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
    byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
    cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, AES));
    byte[] doFinal = cipher.doFinal(Base64.getDecoder().decode(str));
    return new String(doFinal);
    }
    }






    親測100%可用~~~

    👉 歡迎 ,你將獲得: 專屬的計畫實戰 / Java 學習路線 / 一對一提問 / 學習打卡 / 每月贈書

    新計畫: 仿小紅書 (微服務架構)正在更新中... , 全棧前後端分離部落格計畫 2.0 版本完結啦, 演示連結 http://116.62.199.48/ 。全程手摸手,後端 + 前端全棧開發,從 0 到 1 講解每個功能點開發步驟,1v1 答疑,直到計畫上線。 目前已更新了261小節,累計41w+字,講解圖:1806張,還在持續爆肝中.. 後續還會上新更多計畫,目標是將Java領域典型的計畫都整一波,如秒殺系統, 線上商城, IM即時通訊,Spring Cloud Alibaba 等等,


    1. 

    2. 

    3. 

    4. 

    最近面試BAT,整理一份面試資料Java面試BATJ通關手冊,覆蓋了Java核心技術、JVM、Java並行、SSM、微服務、資料庫、數據結構等等。

    獲取方式:點「在看」,關註公眾號並回復 Java 領取,更多內容陸續奉上。

    PS:因公眾號平台更改了推播規則,如果不想錯過內容,記得讀完點一下在看,加個星標,這樣每次新文章推播才會第一時間出現在你的訂閱列表裏。

    「在看」支持小哈呀,謝謝啦