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

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

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

一、國(guó)密SM2加密算法簡(jiǎn)介

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

1.1 核心特點(diǎn)

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

1.2 基本原理

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

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

1.3 應(yīng)用場(chǎng)景

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

二、基于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密鑰對(duì)
     * @return KeyPair對(duì)象,包含公鑰和私鑰
     */
    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密鑰對(duì)失敗: {}", e.getMessage(), e);
            throw new RuntimeException("生成SM2密鑰對(duì)失敗", e);
        }
    }

    /**
     * 獲取十六進(jìn)制格式的公鑰
     * @param publicKey 公鑰對(duì)象
     * @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 私鑰對(duì)象
     * @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("=== 開(kāi)始SM2解密流程 ===");
            logger.info("密文原始字符串長(zhǎng)度: {} 字符", 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("私鑰長(zhǎng)度: {} 字符", 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("檢測(cè)到密文沒(méi)有0x04前綴,這可能是sm-crypto庫(kù)的加密輸出格式問(wèn)題");
                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ù)前長(zhǎng)度: {} 字節(jié)", encryptedData.length - 1);
                    logger.info("  修復(fù)后長(zhǎng)度: {} 字節(jié)", encryptedData.length);
                    logger.info("  修復(fù)后第一個(gè)字節(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解密成功,密文長(zhǎng)度: {},原文長(zhǎng)度: {}", 
                encryptedText.length(), decryptedText.length());
            logger.info("=== SM2解密流程結(jié)束(成功)===");
            return decryptedText;
        } catch (InvalidCipherTextException e) {
            logger.error("SM2解密失敗 - 無(wú)效密文: {}", e.getMessage(), e);
            throw new RuntimeException("SM2解密失敗 - 無(wú)效密文", 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('公鑰長(zhǎng)度:', 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 = '請(qǐng)輸入要加密的明文'
        return
      }
      if (!this.publicKey) {
        this.error = '請(qǐng)先獲取公鑰'
        return
      }
      
      this.isLoading = true
      this.error = ''
      this.encryptedData = ''
      
      try {
        console.log('開(kāi)始加密,明文:', this.plainText)
        console.log('公鑰:', this.publicKey)
        
        // 使用sm-crypto-v2庫(kù)進(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 = '請(qǐng)先加密數(shù)據(jù)'
        return
      }
      if (!this.privateKey) {
        this.error = '解密需要私鑰'
        return
      }
      
      this.isLoading = true
      this.error = ''
      this.decryptedData = ''
      
      try {
        console.log('開(kāi)始本地解密,密文:', this.encryptedData)
        console.log('私鑰:', this.privateKey)
        
        // 使用sm-crypto-v2庫(kù)進(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 {
    // 全局密鑰對(duì)(實(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 加密庫(kù) -->
    <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"
  }
}

四、常見(jiàn)問(wèn)題分析與解決方案

4.1 "Invalid point encoding"錯(cuò)誤

問(wèn)題現(xiàn)象: 解密時(shí)出現(xiàn)"Invalid point encoding"錯(cuò)誤。

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

解決方案:

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

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

問(wèn)題現(xiàn)象: 前端加密時(shí)出現(xiàn)此錯(cuò)誤。

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

解決方案:

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

4.3 加密模式不匹配問(wèn)題

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

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

解決方案:

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

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

相關(guān)文章

最新評(píng)論

宣汉县| 苏尼特右旗| 稻城县| 禹城市| 东丰县| 日土县| 双辽市| 栾城县| 喀喇| 旅游| 固阳县| 宣武区| 黄平县| 莱西市| 满洲里市| 中阳县| 墨竹工卡县| 抚顺市| 孟连| 广汉市| 城固县| 扶风县| 兴山县| 徐闻县| 哈尔滨市| 秦安县| 平和县| 三江| 南皮县| 彰化市| 纳雍县| 清徐县| 广东省| 庆城县| 南江县| 乌鲁木齐市| 津市市| 久治县| 安岳县| 伽师县| 遂平县|