Spring Boot中使用Bouncy Castle實現SM2國密算法(與前端JS加密交互)

Spring Boot中使用Bouncy Castle實現SM2國密算法(與前端JS加密交互)

    • 一、環境準備
    • 二、核心實現
    • 三、前后端交互流程
    • 四、關鍵問題解決方案
    • 五、常見問題排查
    • 六、最佳實踐建議

在這里插入圖片描述

在現代Web應用中,數據安全傳輸至關重要。SM2作為我國自主設計的非對稱加密算法,在安全性、效率和合規性方面具有顯著優勢。本文將詳細介紹如何在Spring Boot中集成SM2算法,實現與前端JS的無縫加密交互。

一、環境準備

技術棧:

  • Java 1.8
  • Spring Boot 2.1.18
  • Bouncy Castle 1.68+
  • 前端:sm-crypto或類似庫

Maven核心依賴:

<dependencies><dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.68</version></dependency>
</dependencies>

二、核心實現

  1. 密鑰生成服務
@RestController
@RequestMapping("/sm2")
public class SM2Controller {@GetMapping("/keypair")public Map<String, String> generateKeyPair() throws Exception {KeyPair keyPair = SM2CryptoUtil.generateKeyPair();ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();String publicKeyHex = Hex.toHexString(publicKey.getQ().getEncoded(false));String privateKeyHex = privateKey.getD().toString(16);// 標準化私鑰格式(64字符)privateKeyHex = String.format("%64s", privateKeyHex).replace(' ', '0');return Map.of("publicKey", publicKeyHex,  // 130字符帶04前綴"privateKey", privateKeyHex  // 64字符);}
}
  1. SM2解密服務
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.BigIntegers;
import org.bouncycastle.util.encoders.Hex;import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;/*** @author cmamg* @title: Base64Util* @projectName * @description: TODO* @date 2025/7/29*/
public class SM2CryptoUtil {// 加密模式常量public static final int C1C2C3 = 0;public static final int C1C3C2 = 1;// 橢圓曲線參數private static final X9ECParameters EC_PARAMS;private static final ECDomainParameters DOMAIN_PARAMS;private static final BigInteger CURVE_ORDER;static {Security.addProvider(new BouncyCastleProvider());EC_PARAMS = GMNamedCurves.getByName("sm2p256v1");DOMAIN_PARAMS = new ECDomainParameters(EC_PARAMS.getCurve(),EC_PARAMS.getG(),EC_PARAMS.getN(),EC_PARAMS.getH());CURVE_ORDER = EC_PARAMS.getN();}/*** 生成SM2密鑰對*/public static KeyPair generateKeyPair() throws Exception {ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("sm2p256v1");KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");kpg.initialize(spec, new SecureRandom());return kpg.generateKeyPair();}/*** 獲取壓縮公鑰十六進制字符串*/public static String getCompressedPublicKey(ECPoint publicKey) {byte[] compressed = publicKey.getEncoded(true);return Hex.toHexString(compressed);}/*** 獲取未壓縮公鑰十六進制字符串(不帶04前綴)*/public static String getUncompressedPublicKey(ECPoint publicKey) {byte[] uncompressed = publicKey.getEncoded(false);// 去掉開頭的04標識return Hex.toHexString(uncompressed);}/*** 從十六進制字符串解析公鑰*/public static ECPoint parsePublicKey(String publicKeyHex) {// 添加04前綴表示未壓縮格式byte[] pubKeyBytes = Hex.decode( publicKeyHex);return DOMAIN_PARAMS.getCurve().decodePoint(pubKeyBytes);}private static BigInteger parsePrivateKey(String privateKeyHex) {if (privateKeyHex == null || privateKeyHex.length() != 64) {throw new IllegalArgumentException("私鑰必須是64字符十六進制字符串");}try {BigInteger privateKey = new BigInteger(privateKeyHex, 16);// 驗證私鑰范圍 [1, n-1]if (privateKey.signum() <= 0 || privateKey.compareTo(CURVE_ORDER) >= 0) {throw new IllegalArgumentException("私鑰超出有效范圍");}return privateKey;} catch (NumberFormatException e) {throw new IllegalArgumentException("無效的私鑰格式", e);}}public static String decryptStr(String ciphertextHex, String privateKeyHex) throws Exception {return new String(decrypt(ciphertextHex,privateKeyHex, 1), "UTF-8");}/*** SM2解密*/public static byte[] decrypt(String ciphertextHex, String privateKeyHex, int cipherMode) throws Exception {// 1. 驗證并解析私鑰BigInteger privateKey = parsePrivateKey(privateKeyHex);// 2. 解析密文byte[] ciphertext = Hex.decode(ciphertextHex);// 驗證最小長度 = C1(64) + C3(32) = 96字節if (ciphertext.length < 96) {throw new IllegalArgumentException("密文太短");}// 3. 拆分密文byte[] c1 = Arrays.copyOfRange(ciphertext, 0, 64); // 64字節byte[] c3;byte[] c2;if (cipherMode == C1C2C3) {// C1C2C3模式: C1(64) + C2 + C3(32)c3 = Arrays.copyOfRange(ciphertext, ciphertext.length - 32, ciphertext.length);c2 = Arrays.copyOfRange(ciphertext, 64, ciphertext.length - 32);} else {// C1C3C2模式: C1(64) + C3(32) + C2c3 = Arrays.copyOfRange(ciphertext, 64, 96);c2 = Arrays.copyOfRange(ciphertext, 96, ciphertext.length);}// 4. 重建C1點byte[] c1Full = new byte[65]; // 04 + 64字節c1Full[0] = 0x04; // 添加未壓縮標識System.arraycopy(c1, 0, c1Full, 1, 64);ECPoint c1Point;try {c1Point = DOMAIN_PARAMS.getCurve().decodePoint(c1Full);} catch (Exception e) {throw new IllegalArgumentException("無效的C1點", e);}// 5. 計算共享點 (x2, y2) = privateKey * C1ECPoint s = c1Point.multiply(privateKey).normalize();// 驗證點是否在曲線上if (!s.isValid()) {throw new SecurityException("計算出的點不在曲線上");}byte[] x2 = BigIntegers.asUnsignedByteArray(32, s.getXCoord().toBigInteger());byte[] y2 = BigIntegers.asUnsignedByteArray(32, s.getYCoord().toBigInteger());// 6. KDF生成密鑰流byte[] z = new byte[x2.length + y2.length];System.arraycopy(x2, 0, z, 0, x2.length);System.arraycopy(y2, 0, z, x2.length, y2.length);byte[] t = kdf(z, c2.length);// 7. 異或解密byte[] msg = new byte[c2.length];for (int i = 0; i < c2.length; i++) {msg[i] = (byte) (c2[i] ^ t[i]);}// 8. 驗證C3byte[] u = new byte[x2.length + msg.length + y2.length];System.arraycopy(x2, 0, u, 0, x2.length);System.arraycopy(msg, 0, u, x2.length, msg.length);System.arraycopy(y2, 0, u, x2.length + msg.length, y2.length);byte[] calculatedC3 = sm3(u);if (!Arrays.equals(c3, calculatedC3)) {throw new SecurityException("C3驗證失敗: 數據可能被篡改或密鑰錯誤");}return msg;}/*** KDF密鑰派生函數*/private static byte[] kdf(byte[] z, int keylen) {int ct = 1;int offset = 0;byte[] result = new byte[keylen];SM3Digest digest = new SM3Digest();while (offset < keylen) {// 準備計數器字節byte[] ctBytes = new byte[]{(byte) (ct >>> 24),(byte) (ct >>> 16),(byte) (ct >>> 8),(byte) ct};// 計算SM3哈希digest.update(z, 0, z.length);digest.update(ctBytes, 0, 4);byte[] hash = new byte[digest.getDigestSize()];digest.doFinal(hash, 0);// 填充結果int copyLen = Math.min(keylen - offset, hash.length);System.arraycopy(hash, 0, result, offset, copyLen);offset += copyLen;ct++;digest.reset();}return result;}/*** SM3哈希計算*/private static byte[] sm3(byte[] input) {SM3Digest digest = new SM3Digest();digest.update(input, 0, input.length);byte[] hash = new byte[digest.getDigestSize()];digest.doFinal(hash, 0);return hash;}
}
  1. 前端加密示例
import { sm2 } from 'sm-crypto';// 使用后端生成的公鑰(130字符帶04前綴)
const publicKey = '04d4de...'; function encryptMessage(message) {// 使用C1C3C2模式加密const ciphertext = sm2.doEncrypt(message, publicKey, 1 // cipherMode=1 表示C1C3C2);return ciphertext; // 十六進制字符串
}// 調用示例
const encrypted = encryptMessage('敏感數據123');

三、前后端交互流程

密鑰獲取:

GET /sm2/keypair
Response: { "publicKey": "04...", "privateKey": "a1b2..." }

前端加密:

const ciphertext = sm2.doEncrypt(data, publicKey, 1);

后端解密:

POST /sm2/decrypt
{"ciphertext": "a1b2c3...","privateKey": "a1b2...","mode": 1
}

四、關鍵問題解決方案

  1. 公鑰格式一致性
    前端要求公鑰帶04前綴(未壓縮格式),后端需確保:
public String getPublicKeyHex(ECPoint publicKey) {return Hex.toHexString(publicKey.getEncoded(false)); // 帶04前綴
}
  1. 私鑰范圍驗證
    防止Scalar not in interval錯誤:
private static final BigInteger CURVE_ORDER = EC_PARAMS.getN();if (privateKey.signum() <= 0 || privateKey.compareTo(CURVE_ORDER) >= 0) {throw new IllegalArgumentException("無效私鑰范圍");
}
  1. C1點重建
    前端密文中的C1點不帶04前綴,后端需重建:
byte[] c1Full = new byte[65];
c1Full[0] = 0x04; // 添加前綴
System.arraycopy(c1, 0, c1Full, 1, 64);

五、常見問題排查

錯誤現象 可能原因 解決方案
Scalar not in interval 私鑰格式錯誤或越界 驗證私鑰長度64字符,值在[1, n-1]范圍內
C3驗證失敗 密鑰錯誤或數據篡改 檢查公私鑰配對,重試加密流程
無效的C1點 密文格式錯誤 確認使用C1C3C2模式,檢查密文長度
解密亂碼 編碼不一致 統一使用UTF-8編碼

六、最佳實踐建議

密鑰管理:

前端不存儲私鑰

后端使用HSM或KMS管理私鑰

定期輪換密鑰

性能優化:

// 重用SM3Digest實例
private static final ThreadLocal<SM3Digest> sm3Cache = ThreadLocal.withInitial(SM3Digest::new);

安全增強:

// 防止時序攻擊
if (!MessageDigest.isEqual(c3, calculatedC3)) {throw new SecurityException("C3驗證失敗");
}

七、總結
本文實現了Spring Boot中完整的SM2算法集成方案,重點解決了:

密鑰生成與格式標準化

與前端JS的加密交互

解密過程中的異常處理

通過此方案,開發者可以快速構建符合國密標準的安全應用,確保數據傳輸的機密性和完整性。在實際業務中,建議結合HTTPS等傳輸層安全措施,構建縱深防御體系。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/917114.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/917114.shtml
英文地址,請注明出處:http://en.pswp.cn/news/917114.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

機器學習sklearn:隨機森林的決策樹

bg&#xff1a;對比決策樹來說&#xff0c;搞多幾棵樹就是隨機森林了rlf_1 [] rlf_2 [] for i in range(10):rfc RandomForestClassifier(n_estimators25)rfc_s cross_val_score(rfc, wine.data, wine.target, cv10).mean()rlf_1.append(rfc_s)clf DecisionTreeClassifier…

上海月賽kk

1.十六進制#include<bits/stdc.h> using namespace std;int n;int main(){cin>>n;stack<int>re;if(n<16)cout<<0;while(n){re.push(n%16);n/16;}while(!re.empty()){int xre.top();re.pop();if(x<10)cout<<x;else cout<<char(Ax-10)…

暑期算法訓練.12

目錄 52. 力扣1 兩數之和 52.1 題目解析&#xff1a; 52.2 算法思路&#xff1a; 52.3 代碼演示&#xff1a; ?編輯 52.4 總結反思&#xff1a; 53 面試題&#xff1a;判定是否互為字符重排 53.1 題目解析&#xff1a; 53.2 算法思路&#xff1a; 53.3 代碼演示&…

MySQL時間處理完全指南:從存儲到查詢優化

時間是數據庫中最活躍的數據維度之一&#xff0c;正確處理時間數據關系到系統穩定性、數據分析準確性和業務邏輯正確性。本文將深入剖析MySQL時間處理的完整知識體系。一、MySQL時間數據類型詳解1. 核心時間類型對比類型存儲空間范圍特性時區影響DATE3字節1000-01-01~9999-12-3…

Text2SQL 智能問答系統開發-預定義模板(二)

背景 在構建一個支持多輪對話的 Text2SQL 系統過程中&#xff0c;我完成了以下關鍵功能&#xff1a; 已完成 基礎 Text2SQL 功能實現 實現用戶輸入自然語言問題后&#xff0c;系統能夠自動生成 SQL 并執行返回結果。用戶交互優化 支持用戶通過補充信息對查詢進行調整&#xff0…

JavaScript 異步編程:Promise 與 async/await 詳解

一、Promise 1. 什么是 Promise&#xff1f; Promise 是 JavaScript 中用于處理異步操作的對象&#xff0c;它代表一個異步操作的最終完成&#xff08;或失敗&#xff09;及其結果值。 2. Promise 的三種狀態 ??Pending&#xff08;待定&#xff09;??&#xff1a;初始狀態…

OS架構整理

OS架構整理引導啟動部分bios bootloader區別啟動流程&#xff08;x86 BIOS 啟動&#xff09;&#xff1a;biosboot_loader3.切換進保護模式實模式的限制如何切換進保護模式加載kernel到內存地址1M加載內核映像文件elf一些基礎知識鏈接腳本與代碼數據段創建GDT表段頁式內存管理顯…

【WRF-Chem第二期】WRF-Chem有關 namelist 詳解

目錄namelist 選項&#xff1a;chem_opt 的選擇其他化學相關的 namelist 選項氣溶膠光學屬性與輸出邊界與初始條件配置&#xff08;氣體&#xff09;參考本博客詳細介紹 WRF-Chem有關 namelist 選項。 namelist 選項&#xff1a;chem_opt 的選擇 chem_opt 是什么&#xff1f;…

STM32-USART串口實現接收數據三種方法(1.根據\r\n標志符、2.空閑幀中斷、3.根據定時器輔助接收)

本章概述思維導圖&#xff1a;USART串口初始化配置串口初始化配置在&#xff08;STM32-USART串口初始化章節有詳細教程配置&#xff09;&#xff0c;本章不做講解直接代碼示例&#xff0c;本章重點在于串口實現接收數據三種方法&#xff1b;配置USART1串口接收初始化函數步驟&a…

【NLP輿情分析】基于python微博輿情分析可視化系統(flask+pandas+echarts) 視頻教程 - 微博評論數據可視化分析-點贊區間折線圖實現

大家好&#xff0c;我是java1234_小鋒老師&#xff0c;最近寫了一套【NLP輿情分析】基于python微博輿情分析可視化系統(flaskpandasecharts)視頻教程&#xff0c;持續更新中&#xff0c;計劃月底更新完&#xff0c;感謝支持。今天講解微博評論數據可視化分析-點贊區間折線圖實現…

Unity_SRP Batcher

SRP Batcher 全面解析&#xff1a;原理、啟用、優化與調試一、什么是 SRP Batcher&#xff1f;SRP Batcher 是 Unity Scriptable Render Pipeline&#xff08;URP、HDRP 或自定義 SRP&#xff09; 專屬的 CPU 渲染性能優化技術&#xff0c;核心目標是 減少材質切換時的 CPU 開銷…

詳解Vite 配置中的代理功能

在前端開發過程中&#xff0c;你可能經常會遇到一個頭疼的問題&#xff1a;當你在本地啟動的前端項目中調用后端接口時&#xff0c;瀏覽器控制臺會報出類似 “Access to fetch at ‘http://xxx’ from origin ‘http://localhost:3000’ has been blocked by CORS policy” 的錯…

理解梯度在神經網絡中的應用

梯度&#xff08;Gradient&#xff09;是微積分中的一個重要概念&#xff0c;廣泛應用于機器學習和深度學習中&#xff0c;尤其是在神經網絡的訓練過程中。下面將從梯度的基本概念、其在神經網絡中的應用兩個方面進行詳細介紹。一、梯度的基本概念 1.1 什么是梯度&#xff1f; …

WPF,按鈕透明背景實現MouseEnter

在幫手程序&#xff08;assister.exe&#xff09;中&#xff0c;可以點擊錄制按鈕&#xff0c;實現錄制用戶操作直接生成操作列表。而在彈出錄制按鈕的懸浮窗中&#xff0c;需要能夠拖動錄制按鈕放置在任意的位置&#xff0c;以免阻擋正常的窗口。具體功能是&#xff0c;當鼠標…

【抄襲】思科交換機DAI(動態ARP監控)配置測試

一.概述 1.DAI作用 ①.使用DAI&#xff0c;管理員可以指定交換機的端口為信任和非信任端口&#xff1a; 信任端口可以轉發任何ARP信息 非信任端口的ARP消息要進行ARP檢測驗證 ②.交換機執行如下的ARP驗證&#xff1a; 靜態ARP監控&#xff1a;為一個靜態的IP地址配置一個靜態AR…

在嵌入式系統或 STM32 平臺中常見的外設芯片和接口

在嵌入式系統或 STM32 平臺中常見的 外設芯片 或 模塊名稱&#xff0c;包括&#xff1a; &#x1f4fa; 顯示驅動&#xff08;如 ST7735、OTM8009A、NT35510&#xff09;&#x1f4f7; 攝像頭模組&#xff08;如 OV5640、OV9655、S5K5CAG&#xff09;&#x1f4be; Flash 存儲器…

AI 類型的 IDE

指集成了 AI 輔助編程能力的集成開發環境 一、代碼輔助生成 ? 自動補全&#xff08;更智能&#xff09; 比傳統 IDE 更智能&#xff0c;理解上下文&#xff0c;生成整個函數/模塊 示例&#xff1a;根據函數名 calculateTax 自動生成稅務計算邏輯 ? 函數 / 類自動生成 給…

JP3-3-MyClub后臺后端(一)

Java道經 - 項目 - MyClub - 后臺后端&#xff08;一&#xff09; 傳送門&#xff1a;JP3-1-MyClub項目簡介 傳送門&#xff1a;JP3-2-MyClub公共服務 傳送門&#xff1a;JP3-3-MyClub后臺后端&#xff08;一&#xff09; 傳送門&#xff1a;JP3-3-MyClub后臺后端&#xff08;…

架構實戰——互聯網架構模板(“存儲層”技術)

目錄 一、SQL 二、NoSQL 三、小文件存儲 四、大文件存儲 本文來源:極客時間vip課程筆記 一、SQL SQL 即我們通常所說的關系數據。前幾年 NoSQL 火了一陣子,很多人都理解為 NoSQL 是完全拋棄關系數據,全部采用非關系型數據。但經過幾年的試驗后,大家發現關系數據不可能完全被…

CentOS7.9在線部署Dify

一、CentOS7.9安裝dify 二、檢查是否安裝dcoker docker --version2.1下載后將安裝包上傳至服務器對應文件夾下,我選在放在了 /root文件夾下 cd /root2.2 上傳至服務器 cd /root #對應目錄下tar -xvf docker-26.1.4.tgz # 解壓安裝包:chmod 755 -R docker # 賦予可執…