SM2 是国家密码管理局组织制定并提出的椭圆曲线密码算法标准。
本文使用第三方密码库 BouncyCastle 实现 SM2 加解密,使用 NuGet 安装即可,包名:Portable.BouncyCastle,目前最新版本为:1.9.0。

整合了 Sm2Util 的功能
由于Bouncy Castle版本问题 SM2Engine未包含Mode需要自定义Mode枚举
/// <summary>
/// 国密算法工具类(SM2/SM3/SM4)
/// 整合了Sm2Util 的功能。
/// </summary>
public static class GMBouncyCastleUtils
{
#region 常量定义
// SM4加密固定IV
private const string SM4_IV = "zhjy-essa-ABCDEF";
// SM2曲线参数(使用 GMNamedCurves 确保国密标准)
private static readonly X9ECParameters Sm2EcParams = GMNamedCurves.GetByName("SM2P256V1");
private static readonly ECDomainParameters Sm2DomainParams = new ECDomainParameters(
Sm2EcParams.Curve, Sm2EcParams.G, Sm2EcParams.N, Sm2EcParams.H);
#endregion
#region SM2 模式枚举
/// <summary>
/// SM2 密文格式
/// </summary>
public enum Sm2Mode
{
C1C2C3,
C1C3C2
}
#endregion
#region SM2 密钥对生成
/// <summary>
/// 生成 SM2 密钥对(国密标准曲线 sm2p256v1)
/// </summary>
/// <returns>字典:pubkey(04开头的十六进制公钥), prikey(十六进制私钥)</returns>
public static Dictionary<string, string> GenerateSm2KeyPair()
{
var gen = new ECKeyPairGenerator();
var keyGenParams = new ECKeyGenerationParameters(Sm2DomainParams, new SecureRandom());
gen.Init(keyGenParams);
var keyPair = gen.GenerateKeyPair();
var privKey = (ECPrivateKeyParameters)keyPair.Private;
var pubKey = (ECPublicKeyParameters)keyPair.Public;
// 私钥:BigInteger 转十六进制(固定长度 64 字符)
string priKeyHex = privKey.D.ToString(16).ToLower();
// 公钥:未压缩格式(04 + X + Y),长度 130 字符(04 + 64 + 64)
string pubKeyHex = Hex.ToHexString(pubKey.Q.GetEncoded(false)).ToLower(); // 带04前缀
return new Dictionary<string, string> { { "pubkey", pubKeyHex }, { "prikey", priKeyHex } };
}
#endregion
#region SM2 加密/解密(支持密文格式切换)
/// <summary>
/// SM2 加密(使用公钥)
/// </summary>
/// <param name="publicKeyHex">公钥十六进制(04 + X + Y)</param>
/// <param name="data">待加密的明文数据</param>
/// <param name="mode">密文格式(默认 C1C3C2,与国标一致)</param>
/// <returns>加密后的密文(Base64)</returns>
public static string Sm2Encrypt(string publicKeyHex, byte[] data, Sm2Mode mode = Sm2Mode.C1C3C2)
{
try
{
// 解析公钥
byte[] pubBytes = Hex.Decode(publicKeyHex);
Org.BouncyCastle.Math.EC.ECPoint pubPoint = Sm2DomainParams.Curve.DecodePoint(pubBytes);
ECPublicKeyParameters pubKeyParams = new ECPublicKeyParameters(pubPoint, Sm2DomainParams);
// SM2 加密
SM2Engine engine = new SM2Engine(new SM3Digest());
engine.Init(true, new ParametersWithRandom(pubKeyParams, new SecureRandom()));
byte[] cipher = engine.ProcessBlock(data, 0, data.Length);
// 格式转换(如果需要)
if (mode == Sm2Mode.C1C2C3)
cipher = ChangeC1C3C2ToC1C2C3(cipher);
return Convert.ToBase64String(cipher);
}
catch (Exception ex)
{
throw new Exception("SM2加密失败:" + ex.Message, ex);
}
}
/// <summary>
/// SM2 解密(使用私钥)
/// </summary>
/// <param name="privateKeyHex">私钥十六进制</param>
/// <param name="cipherBase64">密文(Base64)</param>
/// <param name="mode">密文格式(与加密时一致)</param>
/// <returns>解密后的明文</returns>
public static byte[] Sm2Decrypt(string privateKeyHex, string cipherBase64, Sm2Mode mode = Sm2Mode.C1C3C2)
{
try
{
byte[] cipher = Convert.FromBase64String(cipherBase64);
// 格式转换(解密引擎期望 C1C3C2 格式)
if (mode == Sm2Mode.C1C2C3)
cipher = ChangeC1C2C3ToC1C3C2(cipher);
BigInteger privD = new BigInteger(privateKeyHex, 16);
ECPrivateKeyParameters privKeyParams = new ECPrivateKeyParameters(privD, Sm2DomainParams);
SM2Engine engine = new SM2Engine(new SM3Digest());
engine.Init(false, privKeyParams);
return engine.ProcessBlock(cipher, 0, cipher.Length);
}
catch (Exception ex)
{
throw new Exception("SM2解密失败:" + ex.Message, ex);
}
}
/// <summary>
/// SM2 加密 SM4 密钥
/// </summary>
public static string Sm2EncryptSm4Key(string sm2PublicKeyHex, string sm4KeyHex, Sm2Mode mode = Sm2Mode.C1C3C2)
{
byte[] sm4KeyBytes = Hex.Decode(sm4KeyHex); // 修正:应该加密密钥的字节表示,而非 UTF8 字符串
byte[] encrypted = Convert.FromBase64String(Sm2Encrypt(sm2PublicKeyHex, sm4KeyBytes, mode));
return Convert.ToBase64String(encrypted);
}
/// <summary>
/// SM2 解密 SM4 密钥
/// </summary>
public static string Sm2DecryptSm4Key(string sm2PrivateKeyHex, string encryptedDataBase64, Sm2Mode mode = Sm2Mode.C1C3C2)
{
byte[] decryptedBytes = Sm2Decrypt(sm2PrivateKeyHex, encryptedDataBase64, mode);
return Hex.ToHexString(decryptedBytes).ToLower();
}
#endregion
#region SM2 签名 / 验签
/// <summary>
/// SM2 签名(对原始数据,内部会先做 SM3 摘要)
/// </summary>
public static string Sm2Sign(string privateKeyHex, string data)
{
byte[] dataBytes = Encoding.UTF8.GetBytes(data);
return Sm2Sign(privateKeyHex, dataBytes);
}
public static string Sm2Sign(string privateKeyHex, byte[] data)
{
try
{
BigInteger privD = new BigInteger(privateKeyHex, 16);
ECPrivateKeyParameters privKeyParams = new ECPrivateKeyParameters(privD, Sm2DomainParams);
ISigner signer = SignerUtilities.GetSigner("SM3withSM2");
signer.Init(true, privKeyParams);
signer.BlockUpdate(data, 0, data.Length);
byte[] sig = signer.GenerateSignature();
return Convert.ToBase64String(sig);
}
catch (Exception ex)
{
throw new Exception("SM2签名失败:" + ex.Message, ex);
}
}
/// <summary>
/// SM2 签名(对已有的 SM3 摘要)
/// </summary>
public static string Sm2SignWithDigest(string privateKeyHex, string dataDigestHex)
{
byte[] digest = Hex.Decode(dataDigestHex);
return Sm2Sign(privateKeyHex, digest);
}
/// <summary>
/// SM2 验签
/// </summary>
/// <param name="publicKeyHex">公钥(十六进制)</param>
/// <param name="data">原始数据</param>
/// <param name="signatureBase64">签名(Base64)</param>
/// <returns>验签是否通过</returns>
public static bool Sm2Verify(string publicKeyHex, string data, string signatureBase64)
{
byte[] dataBytes = Encoding.UTF8.GetBytes(data);
return Sm2Verify(publicKeyHex, dataBytes, signatureBase64);
}
public static bool Sm2Verify(string publicKeyHex, byte[] data, string signatureBase64)
{
try
{
byte[] pubBytes = Hex.Decode(publicKeyHex);
Org.BouncyCastle.Math.EC.ECPoint pubPoint = Sm2DomainParams.Curve.DecodePoint(pubBytes);
ECPublicKeyParameters pubKeyParams = new ECPublicKeyParameters(pubPoint, Sm2DomainParams);
ISigner signer = SignerUtilities.GetSigner("SM3withSM2");
signer.Init(false, pubKeyParams);
signer.BlockUpdate(data, 0, data.Length);
byte[] sig = Convert.FromBase64String(signatureBase64);
return signer.VerifySignature(sig);
}
catch
{
return false;
}
}
#endregion
#region SM3 摘要
public static string Sm3ComputeDigest(string data)
{
byte[] dataBytes = Encoding.UTF8.GetBytes(data);
return Sm3ComputeDigest(dataBytes);
}
public static string Sm3ComputeDigest(byte[] data)
{
SM3Digest sm3 = new SM3Digest();
sm3.BlockUpdate(data, 0, data.Length);
byte[] digest = new byte[sm3.GetDigestSize()];
sm3.DoFinal(digest, 0);
return Hex.ToHexString(digest).ToLower();
}
#endregion
#region SM4 对称加密
/// <summary>
/// 生成 128 位 SM4 密钥
/// </summary>
/// <returns>SM4 密钥(十六进制字符串,小写)</returns>
public static string Sm4GenerateKey()
{
byte[] key = new byte[16];
using (var rng = RandomNumberGenerator.Create())
rng.GetBytes(key);
return Hex.ToHexString(key).ToLower();
}
/// <summary>
/// SM4 CBC 模式加密(PKCS5/PKCS7 填充)
/// </summary>
/// <param name="sm4KeyHex">SM4 密钥(十六进制字符串)</param>
/// <param name="plainText">明文</param>
/// <returns>密文(Base64 编码)</returns>
public static string Sm4Encrypt(string sm4KeyHex, string plainText)
{
byte[] key = Hex.Decode(sm4KeyHex);
byte[] iv = Encoding.UTF8.GetBytes(SM4_IV);
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
SM4Engine engine = new SM4Engine();
CbcBlockCipher cbc = new CbcBlockCipher(engine);
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(cbc, new Pkcs7Padding());
cipher.Init(true, new ParametersWithIV(new KeyParameter(key), iv));
byte[] cipherBytes = cipher.DoFinal(plainBytes);
return Convert.ToBase64String(cipherBytes);
}
/// <summary>
/// SM4 CBC 模式解密
/// </summary>
/// <param name="sm4KeyHex">SM4 密钥(十六进制字符串)</param>
/// <param name="cipherTextBase64">密文(Base64 编码)</param>
/// <returns>明文(UTF-8 字符串)</returns>
public static string Sm4Decrypt(string sm4KeyHex, string cipherBase64)
{
byte[] key = Hex.Decode(sm4KeyHex);
byte[] iv = Encoding.UTF8.GetBytes(SM4_IV);
byte[] cipherBytes = Convert.FromBase64String(cipherBase64);
SM4Engine engine = new SM4Engine();
CbcBlockCipher cbc = new CbcBlockCipher(engine);
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(cbc, new Pkcs7Padding());
cipher.Init(false, new ParametersWithIV(new KeyParameter(key), iv));
byte[] plainBytes = cipher.DoFinal(cipherBytes);
return Encoding.UTF8.GetString(plainBytes);
}
#endregion
#region 数据主体构建(业务参数排序 + URL编码)
public static string BuildDataBody(Dictionary<string, string> businessParams, string sm2PublicKeyHex)
{
var sorted = businessParams
.Where(kv => !string.IsNullOrWhiteSpace(kv.Value))
.OrderBy(kv => kv.Key, StringComparer.Ordinal)
.ToList();
var sb = new StringBuilder();
foreach (var kv in sorted)
sb.Append($"{kv.Key}={Uri.EscapeDataString(kv.Value)}&");
sb.Append($"public_key={Uri.EscapeDataString(sm2PublicKeyHex)}");
return sb.ToString();
}
#endregion
#region 密文格式转换(私有辅助方法)
/// <summary>
/// C1C3C2 -> C1C2C3
/// </summary>
private static byte[] ChangeC1C3C2ToC1C2C3(byte[] c1c3c2)
{
int c1Len = (Sm2DomainParams.Curve.FieldSize + 7) / 8 * 2 + 1; // 65
int c3Len = 32;
byte[] c1c2c3 = new byte[c1c3c2.Length];
Buffer.BlockCopy(c1c3c2, 0, c1c2c3, 0, c1Len); // C1
Buffer.BlockCopy(c1c3c2, c1Len + c3Len, c1c2c3, c1Len, c1c3c2.Length - c1Len - c3Len); // C2
Buffer.BlockCopy(c1c3c2, c1Len, c1c2c3, c1c3c2.Length - c3Len, c3Len); // C3
return c1c2c3;
}
/// <summary>
/// C1C2C3 -> C1C3C2
/// </summary>
private static byte[] ChangeC1C2C3ToC1C3C2(byte[] c1c2c3)
{
int c1Len = (Sm2DomainParams.Curve.FieldSize + 7) / 8 * 2 + 1; // 65
int c3Len = 32;
int c2Len = c1c2c3.Length - c1Len - c3Len;
byte[] c1c3c2 = new byte[c1c2c3.Length];
Buffer.BlockCopy(c1c2c3, 0, c1c3c2, 0, c1Len); // C1
Buffer.BlockCopy(c1c2c3, c1Len + c2Len, c1c3c2, c1Len, c3Len); // C3
Buffer.BlockCopy(c1c2c3, c1Len, c1c3c2, c1Len + c3Len, c2Len); // C2
return c1c3c2;
}
#endregion
}
国密SM2算法加解密:
using Org.BouncyCastle.Asn1.GM;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.Encoders;
using System;
using System.Collections.Generic;
using System.Text;
namespace AI_SXPA.Utility
{
/// <summary>
/// 可用
/// </summary>
public class Sm2Util
{
/// <summary>
/// 加密模式
/// </summary>
public enum Mode
{
C1C2C3,
C1C3C2
}
private readonly Mode _mode;
private readonly string _privkey;
private ICipherParameters _privateKeyParameters;
private string _pubkey;
private ICipherParameters _publicKeyParameters;
public Sm2Util(string pubkey, string privkey, Mode mode = Mode.C1C3C2, bool isPkcs8 = false)
{
if (pubkey != null)
_pubkey = pubkey;
if (privkey != null)
_privkey = privkey;
_mode = mode;
}
public Sm2Util(string pubkey, Mode mode = Mode.C1C3C2, bool isPkcs8 = false)
{
if (pubkey != null)
_pubkey = pubkey;
_mode = mode;
}
private ICipherParameters PrivateKeyParameters
{
get
{
try
{
var r = _privateKeyParameters;
if (r == null)
r = _privateKeyParameters =
new ECPrivateKeyParameters(new BigInteger(_privkey, 16),
new ECDomainParameters(GMNamedCurves.GetByName("SM2P256V1")));
return r;
}
catch (Exception ex)
{
return null;
}
}
}
private ICipherParameters PublicKeyParameters
{
get
{
try
{
var r = _publicKeyParameters;
if (r == null)
{
//截取64字节有效的SM2公钥(如果公钥首个字节为0x04)
if (_pubkey.Length > 128) _pubkey = _pubkey.Substring(_pubkey.Length - 128);
//将公钥拆分为x,y分量(各32字节)
var stringX = _pubkey.Substring(0, 64);
var stringY = _pubkey.Substring(stringX.Length);
//将公钥x、y分量转换为BigInteger类型
var x = new BigInteger(stringX, 16);
var y = new BigInteger(stringY, 16);
//通过公钥x、y分量创建椭圆曲线公钥规范
var x9Ec = GMNamedCurves.GetByName("SM2P256V1");
r = _publicKeyParameters = new ECPublicKeyParameters(x9Ec.Curve.CreatePoint(x, y),
new ECDomainParameters(x9Ec));
}
return r;
}
catch (Exception ex)
{
return null;
}
}
}
/// <summary>
/// 生成秘钥对
/// </summary>
/// <returns></returns>
public static Dictionary<string, string> GenerateKeyPair()
{
string[] param =
{
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", // p,0
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", // a,1
"28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", // b,2
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", // n,3
"32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", // gx,4
"BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0" // gy,5
};
var eccParam = param;
var eccP = new BigInteger(eccParam[0], 16);
var eccA = new BigInteger(eccParam[1], 16);
var eccB = new BigInteger(eccParam[2], 16);
var eccN = new BigInteger(eccParam[3], 16);
var eccGx = new BigInteger(eccParam[4], 16);
var eccGy = new BigInteger(eccParam[5], 16);
ECFieldElement element = new FpFieldElement(eccP, eccGx);
ECFieldElement ecFieldElement = new FpFieldElement(eccP, eccGy);
ECCurve eccCurve = new FpCurve(eccP, eccA, eccB);
ECPoint eccPointG = new FpPoint(eccCurve, element, ecFieldElement);
var bcSpec = new ECDomainParameters(eccCurve, eccPointG, eccN);
var ecgenparam = new ECKeyGenerationParameters(bcSpec, new SecureRandom());
var generator = new ECKeyPairGenerator();
generator.Init(ecgenparam);
var key = generator.GenerateKeyPair();
var ecpriv = (ECPrivateKeyParameters)key.Private;
var ecpub = (ECPublicKeyParameters)key.Public;
var privateKey = ecpriv.D;
var publicKey = ecpub.Q;
var dic = new Dictionary<string, string>();
dic.Add("pubkey", Encoding.Default.GetString(Hex.Encode(publicKey.GetEncoded())));
dic.Add("prikey", Encoding.Default.GetString(Hex.Encode(privateKey.ToByteArray())));
//dic.Add("pubkey", Encoding.Default.GetString(Hex.Encode(publicKey.GetEncoded())).ToUpper());
//dic.Add("prikey", Encoding.Default.GetString(Hex.Encode(privateKey.ToByteArray())).ToUpper());
return dic;
}
/// <summary>
/// 解密
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public byte[] Decrypt(byte[] data)
{
try
{
if (_mode == Mode.C1C3C2)
data = C132ToC123(data);
var sm2 = new SM2Engine(new SM3Digest());
sm2.Init(false, PrivateKeyParameters);
return sm2.ProcessBlock(data, 0, data.Length);
}
catch (Exception ex)
{
return null;
}
}
/// <summary>
/// 加密
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public byte[] Encrypt(byte[] data)
{
try
{
var sm2 = new SM2Engine(new SM3Digest());
sm2.Init(true, new ParametersWithRandom(PublicKeyParameters));
data = sm2.ProcessBlock(data, 0, data.Length);
if (_mode == Mode.C1C3C2)
data = C123ToC132(data);
return data;
}
catch (Exception ex)
{
return null;
}
}
private static byte[] C123ToC132(byte[] c1c2c3)
{
var gn = GMNamedCurves.GetByName("SM2P256V1");
var c1Len = (gn.Curve.FieldSize + 7) / 8 * 2 + 1;
var c3Len = 32;
var result = new byte[c1c2c3.Length];
Array.Copy(c1c2c3, 0, result, 0, c1Len); //c1
Array.Copy(c1c2c3, c1c2c3.Length - c3Len, result, c1Len, c3Len); //c3
Array.Copy(c1c2c3, c1Len, result, c1Len + c3Len, c1c2c3.Length - c1Len - c3Len); //c2
return result;
}
private static byte[] C132ToC123(byte[] c1c3c2)
{
var gn = GMNamedCurves.GetByName("SM2P256V1");
var c1Len = (gn.Curve.FieldSize + 7) / 8 * 2 + 1;
var c3Len = 32;
var result = new byte[c1c3c2.Length];
Array.Copy(c1c3c2, 0, result, 0, c1Len); //c1: 0->65
Array.Copy(c1c3c2, c1Len + c3Len, result, c1Len, c1c3c2.Length - c1Len - c3Len); //c2
Array.Copy(c1c3c2, c1Len, result, c1c3c2.Length - c3Len, c3Len); //c3
return result;
}
/// <summary>
/// 字节数组转16进制原码字符串
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static string BytesToHexStr(byte[] bytes)
{
var str = "";
if (bytes != null)
for (var i = 0; i < bytes.Length; i++)
str += bytes[i].ToString("X2");
return str;
}
/// <summary>
/// 16进制原码字符串转字节数组
/// </summary>
/// <param name="hexStr">"AABBCC"或"AA BB CC"格式的字符串</param>
/// <returns></returns>
public static byte[] HexStrToBytes(string hexStr)
{
hexStr = hexStr.Replace(" ", "");
if (hexStr.Length % 2 != 0) throw new ArgumentException("参数长度不正确,必须是偶数位。");
var bytes = new byte[hexStr.Length / 2];
for (var i = 0; i < bytes.Length; i++)
{
var b = Convert.ToByte(hexStr.Substring(i * 2, 2), 16);
bytes[i] = b;
}
return bytes;
}
}
}
调用如下:
var sm2Pkey = "";//获取到公钥
var util = new Sm2Util(sm2PKey);
//加密后的密码
encryptedPassword = Sm2Util.BytesToHexStr(util.Encrypt(Encoding.Default.GetBytes("你的明文密码"))).ToLower();
SM2 加解密联调时踩坑
1、密文数据,有些加密硬件出来密文结构为 C1|C2|C3 ,有些为 C1|C3|C2 , 需要对应密文结构做解密操作
2、有些加密硬件,公钥前加04 ,私钥前加00,密文前加04 ,在处理时候,可以根据长度处理,尤其 04 的处理。


被折叠的 条评论
为什么被折叠?



