最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

vue + springboot 實(shí)現(xiàn)國密SM2加密

 更新時間:2026年01月21日 09:59:30   作者:生而為活  
本文介紹 Vue+SpringBoot 架構(gòu)下,基于國密 SM2 實(shí)現(xiàn)前后臺數(shù)據(jù)加密傳輸?shù)暮诵牧鞒?文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

一、國密SM2加密算法簡介

SM2是中國國家密碼管理局發(fā)布的橢圓曲線公鑰密碼算法標(biāo)準(zhǔn),用于替代傳統(tǒng)的RSA算法。

1.1 核心特點(diǎn)

  • 高安全性:基于橢圓曲線離散對數(shù)問題(ECDLP),目前無有效破解算法
  • 密鑰長度短:256位密鑰提供相當(dāng)于RSA 2048位的安全強(qiáng)度
  • 性能優(yōu)異:計算速度比RSA快,尤其在簽名驗(yàn)證方面
  • 國密標(biāo)準(zhǔn):符合中國國家密碼管理局的加密標(biāo)準(zhǔn),適用于國內(nèi)敏感信息處理

1.2 基本原理

SM2使用公鑰密碼體制,通過橢圓曲線上的點(diǎn)運(yùn)算實(shí)現(xiàn)加密解密:

  • 密鑰對:公鑰(公開)和私鑰(保密)組成,公鑰由私鑰通過橢圓曲線點(diǎn)乘法生成
  • 加密:使用接收方公鑰加密數(shù)據(jù),生成包含橢圓曲線點(diǎn)和加密數(shù)據(jù)的密文
  • 解密:使用接收方私鑰解密數(shù)據(jù),恢復(fù)原始明文

1.3 應(yīng)用場景

  • 敏感數(shù)據(jù)加密傳輸與存儲
  • 數(shù)字簽名與身份認(rèn)證
  • 安全密鑰交換
  • 系統(tǒng)登錄與訪問控制

二、基于BouncyCastle實(shí)現(xiàn)SM2完整代碼

2.1 項(xiàng)目架構(gòu)

本次實(shí)現(xiàn)采用前后端分離架構(gòu):

  • 后端:Spring Boot + BouncyCastle
  • 前端:Vue 3 + Pinia + sm-crypto-v2

2.2 后端核心實(shí)現(xiàn)(SmCryptoUtil.java)

package com.example.sm2demo.util;

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;

public class SmCryptoUtil {
    private static final Logger logger = LoggerFactory.getLogger(SmCryptoUtil.class);
    private static final String ALGORITHM_NAME = "SM2";
    private static final String PROVIDER_NAME = BouncyCastleProvider.PROVIDER_NAME;
    private static final String CURVE_NAME = "sm2p256v1";
    private static final Encoder base64Encoder = java.util.Base64.getEncoder();
    private static final Decoder base64Decoder = java.util.Base64.getDecoder();

    // 靜態(tài)初始化BouncyCastleProvider
    static {
        if (Security.getProvider(PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    /**
     * 生成SM2密鑰對
     * @return KeyPair對象,包含公鑰和私鑰
     */
    public static KeyPair generateKeyPair() {
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM_NAME, PROVIDER_NAME);
            ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec(CURVE_NAME);
            keyPairGenerator.initialize(ecGenParameterSpec);
            return keyPairGenerator.generateKeyPair();
        } catch (Exception e) {
            logger.error("生成SM2密鑰對失敗: {}", e.getMessage(), e);
            throw new RuntimeException("生成SM2密鑰對失敗", e);
        }
    }

    /**
     * 獲取十六進(jìn)制格式的公鑰
     * @param publicKey 公鑰對象
     * @return 十六進(jìn)制格式的公鑰字符串
     */
    public static String getPublicKeyHex(PublicKey publicKey) {
        BCECPublicKey bcecPublicKey = (BCECPublicKey) publicKey;
        ECPoint ecPoint = bcecPublicKey.getQ();
        byte[] encoded = ecPoint.getEncoded(false); // false表示不壓縮格式,包含0x04前綴
        return Hex.toHexString(encoded);
    }

    /**
     * 獲取Base64格式的私鑰
     * @param privateKey 私鑰對象
     * @return Base64格式的私鑰字符串
     */
    public static String getPrivateKeyBase64(PrivateKey privateKey) {
        return base64Encoder.encodeToString(privateKey.getEncoded());
    }

    /**
     * SM2解密方法
     * @param encryptedText 密文字符串
     * @param privateKeyBase64 Base64格式的私鑰
     * @return 解密后的明文字符串
     */
    public static String sm2Decrypt(String encryptedText, String privateKeyBase64) {
        if (encryptedText == null || encryptedText.isEmpty()) {
            throw new IllegalArgumentException("密文數(shù)據(jù)不能為空");
        }
        if (privateKeyBase64 == null || privateKeyBase64.isEmpty()) {
            throw new IllegalArgumentException("私鑰不能為空");
        }

        try {
            logger.info("=== 開始SM2解密流程 ===");
            logger.info("密文原始字符串長度: {} 字符", encryptedText.length());
            logger.info("密文前50字符: {}", encryptedText.substring(0, Math.min(50, encryptedText.length())));
            logger.info("密文后50字符: {}", encryptedText.substring(Math.max(0, encryptedText.length() - 50)));
            logger.info("私鑰長度: {} 字符", privateKeyBase64.length());

            // 使用Bouncy Castle實(shí)現(xiàn)SM2解密
            byte[] encryptedData = Hex.decode(encryptedText);
            byte[] privateKeyData = base64Decoder.decode(privateKeyBase64);

            X9ECParameters ecParameters = GMNamedCurves.getByName(CURVE_NAME);
            ECDomainParameters domainParameters = new ECDomainParameters(
                ecParameters.getCurve(),
                ecParameters.getG(),
                ecParameters.getN(),
                ecParameters.getH(),
                ecParameters.getSeed());

            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyData);
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_NAME, PROVIDER_NAME);
            BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            BigInteger privateKeyD = bcecPrivateKey.getD();

            ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);
            SM2Engine sm2Engine = new SM2Engine(new SM3Digest());
            sm2Engine.init(false, privateKeyParameters);

            // 檢查密文格式并修復(fù)
            if (encryptedData.length > 0 && encryptedData[0] != 0x04) {
                logger.warn("檢測到密文沒有0x04前綴,這可能是sm-crypto庫的加密輸出格式問題");
                if (encryptedData.length >= 97) {
                    // 分離C1、C3、C2組件(假設(shè)是C1C3C2格式)
                    byte[] c1WithoutPrefix = Arrays.copyOfRange(encryptedData, 0, 64);
                    byte[] c3 = Arrays.copyOfRange(encryptedData, 64, 96);
                    byte[] c2 = Arrays.copyOfRange(encryptedData, 96, encryptedData.length);

                    // 為C1添加0x04前綴
                    byte[] c1WithPrefix = new byte[65];
                    c1WithPrefix[0] = 0x04;
                    System.arraycopy(c1WithoutPrefix, 0, c1WithPrefix, 1, 64);

                    // 重新組裝密文
                    byte[] fixedEncryptedData = new byte[65 + 32 + c2.length];
                    System.arraycopy(c1WithPrefix, 0, fixedEncryptedData, 0, 65);
                    System.arraycopy(c3, 0, fixedEncryptedData, 65, 32);
                    System.arraycopy(c2, 0, fixedEncryptedData, 97, c2.length);
                    
                    encryptedData = fixedEncryptedData;
                    logger.info("成功修復(fù)密文格式:");
                    logger.info("  修復(fù)前長度: {} 字節(jié)", encryptedData.length - 1);
                    logger.info("  修復(fù)后長度: {} 字節(jié)", encryptedData.length);
                    logger.info("  修復(fù)后第一個字節(jié): 0x{}", Integer.toHexString(encryptedData[0]));
                }
            }

            byte[] decryptedData = sm2Engine.processBlock(encryptedData, 0, encryptedData.length);
            String decryptedText = new String(decryptedData, StandardCharsets.UTF_8);
            
            logger.info("Bouncy Castle解密成功,密文長度: {},原文長度: {}", 
                encryptedText.length(), decryptedText.length());
            logger.info("=== SM2解密流程結(jié)束(成功)===");
            return decryptedText;
        } catch (InvalidCipherTextException e) {
            logger.error("SM2解密失敗 - 無效密文: {}", e.getMessage(), e);
            throw new RuntimeException("SM2解密失敗 - 無效密文", e);
        } catch (Exception e) {
            logger.error("SM2解密失敗: {}", e.getMessage(), e);
            throw new RuntimeException("SM2解密失敗", e);
        }
    }
}

2.3 前端核心實(shí)現(xiàn)(sm2Store.js)

import { defineStore } from 'pinia'
import { sm2 } from 'sm-crypto-v2'

export const useSm2Store = defineStore('sm2', {
  state: () => ({
    publicKey: '',
    privateKey: '', // 實(shí)際項(xiàng)目中私鑰不應(yīng)從前端獲取
    encryptedData: '',
    decryptedData: '',
    plainText: '',
    isLoading: false,
    error: ''
  }),

  actions: {
    /**
     * 獲取SM2公鑰
     */
    async fetchPublicKey() {
      this.isLoading = true
      this.error = ''
      try {
        const response = await fetch('http://localhost:8080/public-key-hex')
        if (!response.ok) {
          throw new Error('獲取公鑰失敗')
        }
        this.publicKey = await response.text()
        console.log('獲取公鑰成功:', this.publicKey)
        console.log('公鑰長度:', this.publicKey.length)
      } catch (error) {
        this.error = `獲取公鑰失敗: ${error.message}`
        console.error(this.error)
      } finally {
        this.isLoading = false
      }
    },

    /**
     * 加密數(shù)據(jù)
     */
    encryptData() {
      if (!this.plainText) {
        this.error = '請輸入要加密的明文'
        return
      }
      if (!this.publicKey) {
        this.error = '請先獲取公鑰'
        return
      }
      
      this.isLoading = true
      this.error = ''
      this.encryptedData = ''
      
      try {
        console.log('開始加密,明文:', this.plainText)
        console.log('公鑰:', this.publicKey)
        
        // 使用sm-crypto-v2庫進(jìn)行SM2加密,返回C1C3C2格式
        const encryptedText = sm2.encrypt(this.plainText, this.publicKey, {
          mode: 1 // 1表示C1C3C2格式
        })
        this.encryptedData = encryptedText
        console.log('加密成功:', this.encryptedData)
      } catch (error) {
        this.error = `加密失敗: ${error.message}`
        console.error(this.error)
      } finally {
        this.isLoading = false
      }
    },

    /**
     * 本地解密數(shù)據(jù)(不再調(diào)用后端接口)
     */
    decryptData() {
      if (!this.encryptedData) {
        this.error = '請先加密數(shù)據(jù)'
        return
      }
      if (!this.privateKey) {
        this.error = '解密需要私鑰'
        return
      }
      
      this.isLoading = true
      this.error = ''
      this.decryptedData = ''
      
      try {
        console.log('開始本地解密,密文:', this.encryptedData)
        console.log('私鑰:', this.privateKey)
        
        // 使用sm-crypto-v2庫進(jìn)行SM2本地解密
        const decryptedText = sm2.decrypt(this.encryptedData, this.privateKey, {
          mode: 1 // 1表示C1C3C2格式
        })
        this.decryptedData = decryptedText
        console.log('本地解密成功:', this.decryptedData)
      } catch (error) {
        this.error = `解密失敗: ${error.message}`
        console.error(this.error)
      } finally {
        this.isLoading = false
      }
    }
  }
})

2.4 后端API實(shí)現(xiàn)(Sm2Controller.java)

package com.example.sm2demo.controller;

import com.example.sm2demo.util.SmCryptoUtil;
import org.springframework.web.bind.annotation.*;

import java.security.KeyPair;
import java.security.PublicKey;

@RestController
@RequestMapping("/")
public class Sm2Controller {
    // 全局密鑰對(實(shí)際項(xiàng)目中應(yīng)使用更安全的密鑰管理方式)
    private static final KeyPair keyPair;
    private static final PublicKey publicKey;
    private static final String publicKeyHex;

    static {
        keyPair = SmCryptoUtil.generateKeyPair();
        publicKey = keyPair.getPublic();
        publicKeyHex = SmCryptoUtil.getPublicKeyHex(publicKey);
    }

    /**
     * 獲取十六進(jìn)制格式的公鑰
     * @return 十六進(jìn)制格式的公鑰字符串
     */
    @GetMapping("/public-key-hex")
    public String getPublicKeyHex() {
        return publicKeyHex;
    }
}

三、項(xiàng)目依賴配置

3.1 后端依賴(pom.xml)

<dependencies>
    <!-- Spring Boot 核心依賴 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Bouncy Castle 加密庫 -->
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcprov-jdk18on</artifactId>
        <version>1.83</version>
    </dependency>
    
    <!-- 日志依賴 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
    </dependency>
</dependencies>

3.2 前端依賴(package.json)

{
  "name": "sm2-frontend",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "vue": "^3.3.4",
    "pinia": "^2.1.6",
    "sm-crypto-v2": "^1.15.1"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^4.3.4",
    "vite": "^4.4.9"
  }
}

四、常見問題分析與解決方案

4.1 "Invalid point encoding"錯誤

問題現(xiàn)象: 解密時出現(xiàn)"Invalid point encoding"錯誤。

原因: SM2公鑰或密文缺少0x04前綴(橢圓曲線非壓縮點(diǎn)標(biāo)識)。

解決方案:

  • 確保公鑰使用非壓縮格式,包含0x04前綴
  • 驗(yàn)證密文格式正確(這是重中之重),必要時自動修復(fù)(如代碼中實(shí)現(xiàn)的密文修復(fù)邏輯)

4.2 "Cannot read properties of null (reading ‘multiply’)"錯誤

問題現(xiàn)象: 前端加密時出現(xiàn)此錯誤。

原因: 公鑰格式不正確或?yàn)榭铡?/p>

解決方案:

  • 確保公鑰是有效的十六進(jìn)制格式,長度為130字符(包含0x04前綴)
  • 添加公鑰格式驗(yàn)證邏輯

4.3 加密模式不匹配問題

問題現(xiàn)象: 密文解密失敗。

原因: 加密和解密使用了不同的密文格式(C1C3C2 vs C1C2C3)。

解決方案:

  • 前后端統(tǒng)一使用C1C3C2格式(新國密標(biāo)準(zhǔn))
  • 在sm-crypto-v2中通過mode參數(shù)指定格式

到此這篇關(guān)于vue + springboot 實(shí)現(xiàn)國密SM2加密的文章就介紹到這了,更多相關(guān)vue springboot國密SM2加密內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用vue-router切換頁面時實(shí)現(xiàn)設(shè)置過渡動畫

    使用vue-router切換頁面時實(shí)現(xiàn)設(shè)置過渡動畫

    今天小編就為大家分享一篇使用vue-router切換頁面時實(shí)現(xiàn)設(shè)置過渡動畫。具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-10-10
  • Vue純前端如何實(shí)現(xiàn)導(dǎo)出簡單Excel表格的功能

    Vue純前端如何實(shí)現(xiàn)導(dǎo)出簡單Excel表格的功能

    這篇文章主要介紹了如何在Vue項(xiàng)目中使用vue-json-excel插件實(shí)現(xiàn)Excel表格的導(dǎo)出功能,包括安裝依賴、引入插件、使用組件、設(shè)置表頭和數(shù)據(jù)、處理空數(shù)據(jù)情況、源代碼修改以解決常見問題,需要的朋友可以參考下
    2025-01-01
  • Vue3組件不發(fā)生變化如何監(jiān)聽pinia中數(shù)據(jù)變化

    Vue3組件不發(fā)生變化如何監(jiān)聽pinia中數(shù)據(jù)變化

    這篇文章主要給大家介紹了關(guān)于Vue3組件不發(fā)生變化如何監(jiān)聽pinia中數(shù)據(jù)變化的相關(guān)資料,pinia是Vue的存儲庫,它允許您跨組件/頁面共享狀態(tài),文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-11-11
  • Vue自定義指令結(jié)合阿里云OSS優(yōu)化圖片的實(shí)現(xiàn)方法

    Vue自定義指令結(jié)合阿里云OSS優(yōu)化圖片的實(shí)現(xiàn)方法

    這篇文章主要介紹了Vue自定義指令結(jié)合阿里云OSS優(yōu)化圖片的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • vue項(xiàng)目中使用天地圖的簡單代碼示例

    vue項(xiàng)目中使用天地圖的簡單代碼示例

    這篇文章主要給大家介紹了關(guān)于vue項(xiàng)目中使用天地圖的相關(guān)資料,在Vue.js項(xiàng)目中使用天地圖,首先需要申請apikey并在index.html中引入天地圖的js文件,然后創(chuàng)建一個div元素作為地圖容器,并通過CSS設(shè)置其樣式,需要的朋友可以參考下
    2024-11-11
  • 解決Vue數(shù)據(jù)更新了但頁面沒有更新的問題

    解決Vue數(shù)據(jù)更新了但頁面沒有更新的問題

    在vue項(xiàng)目中,有些我們會遇到修改完數(shù)據(jù),但是視圖卻沒有更新的情況,具體的場景不一樣,解決問題的方法也不一樣,在網(wǎng)上看了很多文章,在此總結(jié)匯總一下,需要的朋友可以參考下
    2023-08-08
  • 詳解Vue3如何優(yōu)雅的監(jiān)聽localStorage變化

    詳解Vue3如何優(yōu)雅的監(jiān)聽localStorage變化

    最近在研究框架,也仔細(xì)用了Vue3一些功能,所以本文就來和大家聊聊Vue3如何實(shí)現(xiàn)優(yōu)雅的監(jiān)聽localStorage的變化,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-06-06
  • Vue+java實(shí)現(xiàn)時間段的搜索示例

    Vue+java實(shí)現(xiàn)時間段的搜索示例

    本文主要介紹了Vue+java實(shí)現(xiàn)時間段的搜索示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • vue中子組件傳遞數(shù)據(jù)給父組件的講解

    vue中子組件傳遞數(shù)據(jù)給父組件的講解

    今天小編就為大家分享一篇關(guān)于vue中子組件傳遞數(shù)據(jù)給父組件的講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • vue項(xiàng)目實(shí)現(xiàn)點(diǎn)擊目標(biāo)區(qū)域之外可關(guān)閉(隱藏)目標(biāo)區(qū)域

    vue項(xiàng)目實(shí)現(xiàn)點(diǎn)擊目標(biāo)區(qū)域之外可關(guān)閉(隱藏)目標(biāo)區(qū)域

    這篇文章主要介紹了vue項(xiàng)目實(shí)現(xiàn)點(diǎn)擊目標(biāo)區(qū)域之外可關(guān)閉(隱藏)目標(biāo)區(qū)域,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03

最新評論

枣庄市| 长乐市| 南雄市| 监利县| 柳河县| 浦城县| 砚山县| 昌吉市| 塔城市| 丰宁| 常山县| 京山县| 织金县| 富川| 郧西县| 辽中县| 桦川县| 康平县| 太湖县| 乌拉特中旗| 咸阳市| 宣威市| 盐城市| 弥渡县| 沧州市| 广安市| 乌兰察布市| 来凤县| 屏东县| 德阳市| 昌黎县| 疏勒县| 宁安市| 信阳市| 武隆县| 谢通门县| 长治市| 台安县| 西青区| 平乐县| 昭平县|