更新记录
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>
关键要点
- 统一使用异步风格:所有操作都使用
async/await,代码更清晰 - 错误处理:使用
try-catch捕获异常,提供友好的错误提示 - 状态管理:使用
ref管理响应式数据,自动更新 UI - 输入验证:在执行操作前检查必要参数
- 结果验证:解密后验证内容与原文是否一致
跨平台注意事项
// 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 构建步骤
-
确保项目根目录已安装依赖:
npm install --save miniprogram-sm-crypto -
打开微信开发者工具,勾选「详情」→「本地设置」→「使用 npm 模块」
-
点击菜单栏「工具」→「构建 npm」,生成
miniprogram_npm目录 -
重新编译运行小程序
注意事项
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);
}
}

收藏人数:
购买源码授权版(
试用
赞赏(0)
下载 16
赞赏 0
下载 12014112
赞赏 1917
赞赏
京公网安备:11010802035340号