更新记录

1.0.1(2026-05-22)

安卓端包名变更

1.0.0(2026-05-22)

初次发布


平台兼容性

uni-app(5.0)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
- - -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - -

uni-app x(5.0)

Chrome Safari Android iOS 鸿蒙 微信小程序

其他

多语言 暗黑模式 宽屏模式

sunrains-util

功能说明

本插件提供国密 SM2 和 SM4 算法的跨平台实现,支持以下功能:

SM2 非对称加密算法

  • ✅ SM2 密钥对生成
  • ✅ SM2 数字签名
  • ✅ SM2 签名验证
  • ✅ SM2 数据加密
  • ✅ SM2 数据解密

SM4 对称加密算法

  • ✅ SM4 密钥生成(含 IV)
  • ✅ SM4 数据加密(CBC 模式)
  • ✅ SM4 数据解密(CBC 模式)

调用方式

本插件同时支持回调风格异步风格两种调用方式:

方式一:回调风格(传统方式)

import { sm2Sign } from "@/uni_modules/sunrains-util";

sm2Sign({
  data: "需要签名的数据",
  privateKey: "你的私钥",
  success: (res) => {
    console.log("签名结果:", res.signature);
  },
  fail: (err) => {
    console.error("签名失败:", err);
  }
});

方式二:异步风格(推荐,更简洁)

import { sm2SignAsync } from "@/uni_modules/sunrains-util";

async function doSign() {
  try {
    const result = await sm2SignAsync("需要签名的数据", "你的私钥")
    console.log("签名结果:", result.signature)
  } catch (error) {
    console.error("签名失败:", error)
  }
}

平台支持

平台 SM2 实现方式 SM4 实现方式 状态
Android BouncyCastle BouncyCastle ✅ 完全支持
iOS GMObjC GMObjC ✅ 完全支持
HarmonyOS cryptoFramework cryptoFramework ✅ 完全支持
H5 sm-crypto sm-crypto ✅ 完全支持
微信小程序 miniprogram-sm-crypto miniprogram-sm-crypto ✅ 完全支持

使用示例

SM2 相关操作

1. 生成 SM2 密钥对

同步方式:

import { sm2GenerateKeyPair } from "@/uni_modules/sunrains-util";

const result = sm2GenerateKeyPair();
console.log("公钥:", result.publicKey);  // 04开头的130位十六进制字符串
console.log("私钥:", result.privateKey); // 64位十六进制字符串

异步方式:

import { sm2GenerateKeyPairAsync } from "@/uni_modules/sunrains-util";

async function generateKeys() {
  const result = await sm2GenerateKeyPairAsync()
  console.log("公钥:", result.publicKey)
  console.log("私钥:", result.privateKey)
}

2. SM2 签名

回调风格:

import { sm2Sign } from "@/uni_modules/sunrains-util";

sm2Sign({
  data: "需要签名的数据",
  privateKey: "你的私钥",
  success: (res) => {
    console.log("签名结果:", res.signature);
  },
  fail: (err) => {
    console.error("签名失败:", err);
  }
});

异步风格(推荐):

import { sm2SignAsync } from "@/uni_modules/sunrains-util";

async function doSign() {
  try {
    const result = await sm2SignAsync("需要签名的数据", "你的私钥")
    console.log("签名结果:", result.signature)
  } catch (error) {
    console.error("签名失败:", error)
  }
}

3. SM2 验签

回调风格:

import { sm2Verify } from "@/uni_modules/sunrains-util";

sm2Verify({
  data: "原始数据",
  signature: "签名值",
  publicKey: "你的公钥",
  success: (res) => {
    console.log("验签结果:", res.valid); // true 或 false
  },
  fail: (err) => {
    console.error("验签失败:", err);
  }
});

异步风格(推荐):

import { sm2VerifyAsync } from "@/uni_modules/sunrains-util";

async function doVerify() {
  try {
    const result = await sm2VerifyAsync("原始数据", "签名值", "你的公钥")
    console.log("验签结果:", result.valid) // true 或 false
  } catch (error) {
    console.error("验签失败:", error)
  }
}

完整实战示例

以下是一个完整的国密算法测试页面示例,展示了所有功能的实际使用:

Vue 3 Composition API 示例

<template>
  <view class="container">
    <!-- SM4 对称加密测试 -->
    <view class="section">
      <text class="title">SM4 对称加密</text>

      <input v-model="sm4Content" placeholder="请输入要加密的内容" />
      <button @click="testSm4Encrypt">SM4 加密</button>

      <view v-if="sm4Key.key">
        <text>密钥: {{ sm4Key.key }}</text>
        <text>IV: {{ sm4Key.iv }}</text>
      </view>

      <view v-if="sm4Encrypted">
        <text>加密结果: {{ sm4Encrypted }}</text>
      </view>

      <button @click="testSm4Decrypt" :disabled="!sm4Encrypted">SM4 解密</button>

      <view v-if="sm4Decrypted">
        <text>解密结果: {{ sm4Decrypted }}</text>
      </view>
    </view>

    <!-- SM2 签名验签测试 -->
    <view class="section">
      <text class="title">SM2 数字签名</text>

      <input v-model="sm2SignContent" placeholder="请输入要签名的内容" />
      <button @click="testSm2Sign">生成密钥对并签名</button>

      <view v-if="sm2KeyPair.publicKey">
        <text>公钥: {{ sm2KeyPair.publicKey }}</text>
        <text>私钥: {{ sm2KeyPair.privateKey }}</text>
      </view>

      <view v-if="sm2Signature">
        <text>签名: {{ sm2Signature }}</text>
      </view>

      <button @click="testSm2Verify" :disabled="!sm2Signature">验签</button>

      <view v-if="sm2VerifyResult !== null">
        <text>{{ sm2VerifyResult ? '✅ 验签成功' : '❌ 验签失败' }}</text>
      </view>
    </view>

    <!-- SM2 加密解密测试 -->
    <view class="section">
      <text class="title">SM2 非对称加密</text>

      <input v-model="sm2EncryptContent" placeholder="请输入要加密的内容" />
      <button @click="testSm2Encrypt">SM2 加密</button>

      <view v-if="sm2EncryptKeyPair.publicKey">
        <text>公钥: {{ sm2EncryptKeyPair.publicKey }}</text>
        <text>私钥: {{ sm2EncryptKeyPair.privateKey }}</text>
      </view>

      <view v-if="sm2Encrypted">
        <text>加密结果: {{ sm2Encrypted }}</text>
      </view>

      <button @click="testSm2Decrypt" :disabled="!sm2Encrypted">SM2 解密</button>

      <view v-if="sm2Decrypted">
        <text>解密结果: {{ sm2Decrypted }}</text>
      </view>
    </view>
  </view>
</template>

<script setup lang="uts">
import * as SM from "@/uni_modules/sunrains-util"

// ==================== SM4 相关数据 ====================
const sm4Content = ref("Hello World")
const sm4Key = ref<SM.Sm4Key>({ key: '', iv: '' })
const sm4Encrypted = ref("")
const sm4Decrypted = ref("")

// ==================== SM2 签名验签相关数据 ====================
const sm2SignContent = ref("需要签名的数据")
const sm2KeyPair = ref<SM.Sm2KeyPair>({ publicKey: '', privateKey: '' })
const sm2Signature = ref("")
const sm2VerifyResult = ref<boolean | null>(null)

// ==================== SM2 加密解密相关数据 ====================
const sm2EncryptContent = ref("Hello SM2")
const sm2EncryptKeyPair = ref<SM.Sm2KeyPair>({ publicKey: '', privateKey: '' })
const sm2Encrypted = ref("")
const sm2Decrypted = ref("")

// ==================== SM4 加密 ====================
async function testSm4Encrypt() {
  if (sm4Content.value.length == 0) {
    uni.showToast({ title: '请输入内容', icon: 'none' })
    return
  }

  try {
    // 生成密钥
    const key = await SM.sm4GenerateKeyAsync()
    sm4Key.value = { key: key.key, iv: key.iv }
    console.log("SM4 密钥:", key)

    // 加密
    const encryptResult = await SM.sm4EncryptAsync(sm4Content.value, key.key, key.iv)
    sm4Encrypted.value = encryptResult.result
    console.log("SM4 加密成功:", encryptResult.result)
    uni.showToast({ title: '加密成功', icon: 'success' })

  } catch (e) {
    console.error("SM4 加密失败:", e)
    uni.showToast({ title: '加密失败', icon: 'error' })
  }
}

// ==================== SM4 解密 ====================
async function testSm4Decrypt() {
  if (sm4Encrypted.value.length == 0 || sm4Key.value.key.length == 0) {
    uni.showToast({ title: '请先加密', icon: 'none' })
    return
  }

  try {
    const decryptResult = await SM.sm4DecryptAsync(
      sm4Encrypted.value, 
      sm4Key.value.key, 
      sm4Key.value.iv
    )
    sm4Decrypted.value = decryptResult.result
    console.log("SM4 解密成功:", decryptResult.result)

    if (decryptResult.result == sm4Content.value) {
      uni.showToast({ title: '解密成功且内容一致', icon: 'success' })
    } else {
      uni.showToast({ title: '解密成功但内容不一致', icon: 'error' })
    }
  } catch (e) {
    console.error("SM4 解密失败:", e)
    uni.showToast({ title: '解密失败', icon: 'error' })
  }
}

// ==================== SM2 签名 ====================
async function testSm2Sign() {
  if (sm2SignContent.value.length == 0) {
    uni.showToast({ title: '请输入内容', icon: 'none' })
    return
  }

  try {
    // 生成密钥对
    const keyPair = await SM.sm2GenerateKeyPairAsync()
    sm2KeyPair.value = { 
      publicKey: keyPair.publicKey, 
      privateKey: keyPair.privateKey 
    }
    console.log("SM2 公钥:", keyPair.publicKey)
    console.log("SM2 私钥:", keyPair.privateKey)

    // 签名
    const signResult = await SM.sm2SignAsync(sm2SignContent.value, keyPair.privateKey)
    sm2Signature.value = signResult.signature
    console.log("SM2 签名成功:", signResult.signature)
    uni.showToast({ title: '签名成功', icon: 'success' })
  } catch (e) {
    console.error("SM2 生成密钥对失败:", e)
    uni.showToast({ title: '生成密钥对失败', icon: 'error' })
  }
}

// ==================== SM2 验签 ====================
async function testSm2Verify() {
  if (sm2Signature.value.length == 0 || sm2KeyPair.value.publicKey.length == 0) {
    uni.showToast({ title: '请先签名', icon: 'none' })
    return
  }

  try {
    // 验签
    const verifyResult = await SM.sm2VerifyAsync(
      sm2SignContent.value, 
      sm2Signature.value, 
      sm2KeyPair.value.publicKey
    )
    sm2VerifyResult.value = verifyResult.valid
    console.log("SM2 验签结果:", verifyResult.valid)

    if (verifyResult.valid) {
      uni.showToast({ title: '验签成功', icon: 'success' })
    } else {
      uni.showToast({ title: '验签失败', icon: 'error' })
    }
  } catch (e) {
    console.error("SM2 验签异常:", e)
    uni.showToast({ title: '验签异常', icon: 'error' })
  }
}

// ==================== SM2 加密 ====================
async function testSm2Encrypt() {
  if (sm2EncryptContent.value.length == 0) {
    uni.showToast({ title: '请输入内容', icon: 'none' })
    return
  }

  try {
    // 生成密钥对
    const keyPair = await SM.sm2GenerateKeyPairAsync()
    sm2EncryptKeyPair.value = { 
      publicKey: keyPair.publicKey, 
      privateKey: keyPair.privateKey 
    }
    console.log("SM2 加密公钥:", keyPair.publicKey)
    console.log("SM2 加密私钥:", keyPair.privateKey)

    // 加密
    const encryptResult = await SM.sm2EncryptAsync(sm2EncryptContent.value, keyPair.publicKey)
    sm2Encrypted.value = encryptResult.encrypted
    console.log("SM2 加密结果:", encryptResult.encrypted)
    uni.showToast({ title: '加密成功', icon: 'success' })
  } catch (e) {
    console.error("SM2 生成密钥对失败:", e)
    uni.showToast({ title: '生成密钥对失败', icon: 'error' })
  }
}

// ==================== SM2 解密 ====================
async function testSm2Decrypt() {
  if (sm2Encrypted.value.length == 0 || sm2EncryptKeyPair.value.privateKey.length == 0) {
    uni.showToast({ title: '请先加密', icon: 'none' })
    return
  }

  try {
    const decryptResult = await SM.sm2DecryptAsync(
      sm2Encrypted.value, 
      sm2EncryptKeyPair.value.privateKey
    )
    sm2Decrypted.value = decryptResult.decrypted
    console.log("SM2 解密结果:", decryptResult.decrypted)

    if (decryptResult.decrypted == sm2EncryptContent.value) {
      uni.showToast({ title: '解密成功且内容一致', icon: 'success' })
    } else {
      uni.showToast({ title: '解密成功但内容不一致', icon: 'error' })
    }
  } catch (e) {
    console.error("SM2 解密异常:", e)
    uni.showToast({ title: '解密异常', icon: 'error' })
  }
}
</script>

<style scoped>
.container {
  padding: 20px;
}
.section {
  margin-bottom: 30px;
  padding: 15px;
  background-color: #f5f5f5;
  border-radius: 8px;
}
.title {
  font-size: 18px;
  font-weight: bold;
  margin-bottom: 15px;
}
</style>

关键要点

  1. 统一使用异步风格:所有操作都使用 async/await,代码更清晰
  2. 错误处理:使用 try-catch 捕获异常,提供友好的错误提示
  3. 状态管理:使用 ref 管理响应式数据,自动更新 UI
  4. 输入验证:在执行操作前检查必要参数
  5. 结果验证:解密后验证内容与原文是否一致

跨平台注意事项

// H5 平台需要特殊导入
//#ifdef H5
import * as SM from "@/uni_modules/sunrains-util/utssdk/web/index.uts"
//#else
import * as SM from "@/uni_modules/sunrains-util"
//#endif

// 其他平台(Android、iOS、HarmonyOS、微信小程序)直接使用默认导入
import * as SM from "@/uni_modules/sunrains-util"

各平台实现说明

Android 平台

SM2 实现:

  • 依赖库:BouncyCastle (org.bouncycastle:bcprov-jdk15on:1.70)
  • 特点
    • 使用 ECKeyPairGenerator 生成密钥对
    • 使用 SM2Signer 进行签名和验签(DER 格式)
    • 使用 SM2Engine 进行加密和解密(C1C3C2 格式)
    • 不依赖 JCA Provider,避免兼容性问题

SM4 实现:

  • 依赖库:BouncyCastle
  • 特点
    • 使用 SM4Engine 进行加密和解密
    • CBC 模式 + PKCS7 填充
    • 支持自定义密钥和 IV

iOS 平台

实现方式:GMObjC 原生库

SM2 特点:

  • 使用 GMSm2Utils 进行所有操作
  • 签名自动转换为 DER 格式以兼容其他平台
  • 加密输出 C1C3C2 格式(04 开头)
  • 支持跨平台互通(Android/HarmonyOS)

SM4 特点:

  • 使用 GMSm4Utils 进行加密解密
  • CBC 模式 + PKCS7 填充
  • 密钥和 IV 为 32 位十六进制字符串

HarmonyOS 平台

实现方式:@ohos.security.cryptoFramework

SM2 特点:

  • 使用 HarmonyOS 原生 cryptoFramework API
  • 签名使用 DER 格式
  • 加密使用 C1C3C2 格式
  • 完全符合国密标准

SM4 特点:

  • 使用 createSymKeyGenerator 生成密钥
  • 使用 createCipher 进行加密解密
  • CBC 模式 + PKCS7 填充
  • 支持 SM4_128 算法

H5 平台

实现方式:sm-crypto (JavaScript 库)

特点:

  • 通过 npm 包 sm-crypto 实现
  • 所有功能均在浏览器环境中运行
  • 签名使用 DER 格式(der: true
  • 默认 userId 为 '1234567812345678'

微信小程序平台

实现方式:miniprogram-sm-crypto

特点:

  • 专为小程序优化的国密算法库
  • 完全支持 SM2 和 SM4
  • SM2:密钥生成、签名、验签、加密、解密
  • SM4:密钥生成、加密、解密(CBC 模式)
  • 需要通过微信开发者工具构建 npm

重要:微信小程序 npm 构建步骤

  1. 确保项目根目录已安装依赖:

    npm install --save miniprogram-sm-crypto
  2. 打开微信开发者工具,勾选「详情」→「本地设置」→「使用 npm 模块」

  3. 点击菜单栏「工具」→「构建 npm」,生成 miniprogram_npm 目录

  4. 重新编译运行小程序

注意事项

1. 密钥格式

SM2 密钥:

  • 公钥:以 04 开头的 130 位十六进制字符串(非压缩格式)
  • 私钥:64 位十六进制字符串

SM4 密钥:

  • 密钥 (key):32 位十六进制字符串(128 位)
  • 初始向量 (IV):32 位十六进制字符串(128 位)

2. 签名格式

  • 所有平台的签名输出均为 DER 编码的十六进制字符串
  • DER 格式确保跨平台兼容性

3. 加密格式

SM2 加密:

  • 输出格式:C1C3C2(以 04 开头)
  • 兼容 Android BouncyCastle、iOS GMObjC、HarmonyOS cryptoFramework

SM4 加密:

  • 模式:CBC
  • 填充:PKCS7
  • 输出:十六进制字符串

4. 跨平台兼容性

完全兼容

  • 各平台生成的 SM2 密钥对可以互相使用
  • Android 平台签名的数据可以在 iOS/Harmony/H5 平台验证
  • SM2 加密的数据可以在不同平台间解密
  • SM4 加密的数据可以在 Android/iOS/Harmony 平台间加解密

⚠️ 注意事项

  • 确保使用相同的哈希选项(默认启用)
  • SM4 加密和解密必须使用相同的密钥和 IV
  • 微信小程序需要先构建 npm 才能使用

5. 错误码说明

错误码 说明
9010000 SM4 密钥生成失败
9010001 SM4 加密失败
9010002 SM4 解密失败
9020000 SM2 密钥对生成失败
9020001 SM2 签名失败
9020002 SM2 验签失败
9020003 SM2 加密失败
9020004 SM2 解密失败

开发文档

java服务端工具类

package top.sunrains;

import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.util.encoders.Hex;

import java.math.BigInteger;
import java.security.SecureRandom;

/**
 *         <dependency>
 *             <groupId>org.bouncycastle</groupId>
 *             <artifactId>bcprov-jdk15to18</artifactId>
 *             <version>1.69</version>
 *         </dependency>
 */
public class SM2Util {

    private static final ECParameterSpec CURVE = ECNamedCurveTable.getParameterSpec("sm2p256v1");
    private static final ECDomainParameters DOMAIN = new ECDomainParameters(
            CURVE.getCurve(),
            CURVE.getG(),
            CURVE.getN(),
            CURVE.getH()
    );

    /**
     * 生成SM2密钥对
     *
     * @return Map包含publicKey和privateKey,均为小写十六进制字符串
     */
    public static java.util.Map<String, String> generateKeyPair() {
        try {
            BigInteger d = new BigInteger(256, new SecureRandom()).mod(DOMAIN.getN());
            org.bouncycastle.math.ec.ECPoint q = DOMAIN.getG().multiply(d).normalize();

            String x = String.format("%064x", q.getAffineXCoord().toBigInteger());
            String y = String.format("%064x", q.getAffineYCoord().toBigInteger());
            String publicKey = "04" + x + y;
            String privateKey = String.format("%064x", d);
            java.util.Map<String, String> keyMap = new java.util.HashMap<>();
            keyMap.put("publicKey", publicKey.toLowerCase());
            keyMap.put("privateKey", privateKey.toLowerCase());
            return keyMap;
        } catch (Exception e) {
            System.err.println("密钥生成失败: " + e.getMessage());
            e.printStackTrace();
            java.util.Map<String, String> keyMap = new java.util.HashMap<>();
            keyMap.put("publicKey", "");
            keyMap.put("privateKey", "");
            return keyMap;
        }
    }

    /**
     * SM2签名
     *
     * @param data 待签名的原始数据
     * @param privateKeyHex 私钥(64字符十六进制)
     * @return 签名结果(DER格式,小写十六进制)
     */
    public static String sign(String data, String privateKeyHex) {
        try {
            byte[] msgBytes = data.getBytes(java.nio.charset.StandardCharsets.UTF_8);

            BigInteger d = new BigInteger(privateKeyHex, 16);
            ECPrivateKeyParameters priKey = new ECPrivateKeyParameters(d, DOMAIN);

            SM2Signer signer = new SM2Signer(new SM3Digest());
            signer.init(true, priKey);
            signer.update(msgBytes, 0, msgBytes.length);
            byte[] sig = signer.generateSignature();

            return Hex.toHexString(sig).toLowerCase();
        } catch (Exception e) {
            System.err.println("签名失败: " + e.getMessage());
            e.printStackTrace();
            return "";
        }
    }

    /**
     * SM2验签
     *
     * @param data 原始数据
     * @param signatureHex 签名(DER格式,十六进制)
     * @param publicKeyHex 公钥(04开头或不含04前缀的十六进制)
     * @return 验签是否成功
     */
    public static boolean verify(String data, String signatureHex, String publicKeyHex) {
        try {
            byte[] msgBytes = data.getBytes(java.nio.charset.StandardCharsets.UTF_8);

            String xy = publicKeyHex.startsWith("04") ? publicKeyHex.substring(2) : publicKeyHex;
            BigInteger x = new BigInteger(xy.substring(0, 64), 16);
            BigInteger y = new BigInteger(xy.substring(64, 128), 16);

            org.bouncycastle.math.ec.ECPoint pubPoint = DOMAIN.getCurve().createPoint(x, y).normalize();
            ECPublicKeyParameters pubKey = new ECPublicKeyParameters(pubPoint, DOMAIN);

            SM2Signer signer = new SM2Signer(new SM3Digest());
            signer.init(false, pubKey);
            signer.update(msgBytes, 0, msgBytes.length);

            byte[] sigBytes = Hex.decode(signatureHex);
            return signer.verifySignature(sigBytes);
        } catch (Exception e) {
            System.err.println("验签失败: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    /**
     * SM2加密
     *
     * @param data 待加密的原始数据(UTF-8字符串)
     * @param publicKeyHex 公钥(04开头或不含04前缀的十六进制)
     * @return 密文(HEX字符串,小写)
     */
    public static String encrypt(String data, String publicKeyHex) {
        try {
            byte[] dataBytes = data.getBytes(java.nio.charset.StandardCharsets.UTF_8);

            String xy = publicKeyHex.startsWith("04") ? publicKeyHex.substring(2) : publicKeyHex;
            BigInteger x = new BigInteger(xy.substring(0, 64), 16);
            BigInteger y = new BigInteger(xy.substring(64, 128), 16);

            org.bouncycastle.math.ec.ECPoint pubPoint = DOMAIN.getCurve().createPoint(x, y).normalize();
            ECPublicKeyParameters pubKey = new ECPublicKeyParameters(pubPoint, DOMAIN);

            SM2Engine engine = new SM2Engine(new SM3Digest(), SM2Engine.Mode.C1C3C2);
            engine.init(true, new ParametersWithRandom(pubKey, new SecureRandom()));

            byte[] encrypted = engine.processBlock(dataBytes, 0, dataBytes.length);
            return Hex.toHexString(encrypted).toLowerCase();
        } catch (Exception e) {
            System.err.println("加密失败: " + e.getMessage());
            e.printStackTrace();
            return "";
        }
    }

    /**
     * SM2解密
     *
     * @param encryptedHex 密文(HEX字符串)
     * @param privateKeyHex 私钥(64字符十六进制)
     * @return 解密后的原始数据(UTF-8字符串)
     */
    public static String decrypt(String encryptedHex, String privateKeyHex) {
        try {
            byte[] encryptedBytes = Hex.decode(encryptedHex);

            BigInteger d = new BigInteger(privateKeyHex, 16);
            ECPrivateKeyParameters priKey = new ECPrivateKeyParameters(d, DOMAIN);

            SM2Engine engine = new SM2Engine(new SM3Digest(), SM2Engine.Mode.C1C3C2);
            engine.init(false, priKey);

            byte[] decrypted = engine.processBlock(encryptedBytes, 0, encryptedBytes.length);
            return new String(decrypted, java.nio.charset.StandardCharsets.UTF_8);
        } catch (Exception e) {
            System.err.println("解密失败: " + e.getMessage());
            e.printStackTrace();
            return "";
        }
    }

    /**
     * 测试方法
     */
    public static void main(String[] args) {
        System.out.println("=== SM2 工具类测试 ===\n");

        java.util.Map<String, String> keyPair = generateKeyPair();
        String publicKey = keyPair.get("publicKey");
        String privateKey = keyPair.get("privateKey");

        System.out.println("公钥: " + publicKey);
        System.out.println("私钥: " + privateKey);
        System.out.println();

        String testData = "123";
        System.out.println("原始数据: " + testData);

        String signature = sign(testData, privateKey);
        System.out.println("签名: " + signature);
        System.out.println();

        boolean isValid = verify(testData, signature, publicKey);
        System.out.println("验签结果: " + isValid);
        System.out.println();

        if (!isValid) {
            System.err.println("验签失败!");
        } else {
            System.out.println("验签成功!");
        }

        System.out.println("\n=== SM2 加密解密测试 ===\n");

        // 加密解密测试
        String originalText = "Hello SM2";
        System.out.println("原始数据: " + originalText);

        String encrypted = encrypt(originalText, publicKey);
        System.out.println("原始数据: " + originalText);

        String decrypted = decrypt(encrypted, privateKey);
        System.out.println("解密结果: " + decrypted);
        System.out.println();

        if (originalText.equals(decrypted)) {
            System.out.println("加密解密成功!");
        } else {
            System.err.println("加密解密失败!");
        }
    }
}

package top.sunrains;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.security.Security;

/**
 *         <dependency>
 *             <groupId>org.bouncycastle</groupId>
 *             <artifactId>bcprov-jdk15to18</artifactId>
 *             <version>1.69</version>
 *         </dependency>
 *
 * SM4国密算法工具类
 * 使用Bouncy Castle原生API
 * 配置信息:
 * - 算法:SM4
 * - 模式:CBC(密码分组链接)
 * - 填充:PKCS5Padding
 * - 密钥长度:128位(16字节)
 * - IV长度:128位(16字节)
 */
public class Sm4Util {

    static {
        try {
            Security.addProvider(new BouncyCastleProvider());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
//        String key = generateKeyHex();
//        String iv = generateIvHex();
        //String s = encryptHex("123", key, iv);
        String key = "7f9a942a1aa85c331d3697a5369a37fe";
        String iv = "cbc07750d33d6a3ef6d39fc7e7c4a876";

        String s = "09187a80e9f52e7e962a3465ed70669e";
        System.out.println(s);
        String decryptedHex = decryptHex(s, key, iv);
        System.out.println(decryptedHex);
    }

    /**
     * 生成SM4密钥(16字节,128位)
     * @return 十六进制编码的密钥字符串(32个字符)
     */
    public static String generateKeyHex() {
        byte[] keyBytes = new byte[16];
        new SecureRandom().nextBytes(keyBytes);
        return byteArrayToHex(keyBytes);
    }

    /**
     * 生成IV向量(16字节,128位)
     * @return 十六进制编码的IV字符串(32个字符)
     */
    public static String generateIvHex() {
        byte[] ivBytes = new byte[16];
        new SecureRandom().nextBytes(ivBytes);
        return byteArrayToHex(ivBytes);
    }

    /**
     * SM4-CBC加密(字符串到HEX)
     * 标准加密方式
     *
     * @param plaintext 明文字符串(UTF-8)
     * @param keyHex 十六进制密钥字符串
     * @param ivHex 十六进制IV字符串
     * @return 加密后的HEX字符串
     * @throws Exception 加密异常
     */
    public static String encryptHex(String plaintext, String keyHex, String ivHex) throws Exception {
        byte[] key = decodeKeyHex(keyHex);
        byte[] iv = decodeIvHex(ivHex);
        byte[] data = plaintext.getBytes("UTF-8");

        byte[] encrypted = encrypt(data, key, iv);
        return byteArrayToHex(encrypted);
    }

    /**
     * SM4-CBC加密
     *
     * @param data 原始数据字节数组
     * @param key 密钥字节数组(16字节)
     * @param iv IV字节数组(16字节)
     * @return 加密后的字节数组
     * @throws Exception 加密异常
     */
    public static byte[] encrypt(byte[] data, byte[] key, byte[] iv) throws Exception {
        Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS5Padding", "BC");
        cipher.init(Cipher.ENCRYPT_MODE,
                new SecretKeySpec(key, "SM4"),
                new IvParameterSpec(iv));
        return cipher.doFinal(data);
    }

    /**
     * byte[] 转 Hex
     *
     * @param bytes 字节数组
     * @return HEX字符串(小写)
     */
    public static String byteArrayToHex(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(b & 0xFF);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString().toLowerCase();
    }

    public static String decryptHex(String encryptedHex, String keyHex, String ivHex) throws Exception {
        byte[] key = decodeKeyHex(keyHex);
        byte[] iv = decodeIvHex(ivHex);
        byte[] encryptedData = hexToByteArray(encryptedHex);

        byte[] decrypted = decrypt(encryptedData, key, iv);
        return new String(decrypted, "UTF-8");
    }

    /**
     * SM4-CBC解密
     *
     * @param encryptedData 加密数据字节数组
     * @param key 密钥字节数组(16字节)
     * @param iv IV字节数组(16字节)
     * @return 解密后的原始字节数组
     * @throws Exception 解密异常
     */
    public static byte[] decrypt(byte[] encryptedData, byte[] key, byte[] iv) throws Exception {
        Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS5Padding", "BC");
        cipher.init(Cipher.DECRYPT_MODE,
                new SecretKeySpec(key, "SM4"),
                new IvParameterSpec(iv));
        return cipher.doFinal(encryptedData);
    }

    /**
     * 从十六进制字符串还原密钥字节数组
     * @param keyHex 十六进制编码的密钥字符串
     * @return 密钥字节数组(16字节)
     */
    public static byte[] decodeKeyHex(String keyHex) {
        return hexToByteArray(keyHex);
    }

    /**
     * 从十六进制字符串还原IV字节数组
     * @param ivHex 十六进制编码的IV字符串
     * @return IV字节数组(16字节)
     */
    public static byte[] decodeIvHex(String ivHex) {
        return hexToByteArray(ivHex);
    }

    /**
     * HEX 转 byte[]
     *
     * @param hex HEX字符串
     * @return 字节数组
     */
    public static byte[] hexToByteArray(String hex) {
        if (hex == null || hex.length() == 0) {
            return new byte[0];
        }

        int len = hex.length() / 2;
        byte[] bytes = new byte[len];
        for (int i = 0; i < len; i++) {
            int high = Character.digit(hex.charAt(i * 2), 16);
            int low = Character.digit(hex.charAt(i * 2 + 1), 16);
            bytes[i] = (byte) ((high << 4) | low);
        }
        return bytes;
    }

    public static String stringToHex(String str) {
        if (str == null || str.isEmpty()) {
            return "";
        }
        byte[] bytes = str.getBytes(java.nio.charset.StandardCharsets.UTF_8);
        return byteArrayToHex(bytes);
    }
}

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。