Ailaibo.CSharp.SM2
1.0.0
dotnet add package Ailaibo.CSharp.SM2 --version 1.0.0
NuGet\Install-Package Ailaibo.CSharp.SM2 -Version 1.0.0
<PackageReference Include="Ailaibo.CSharp.SM2" Version="1.0.0" />
<PackageVersion Include="Ailaibo.CSharp.SM2" Version="1.0.0" />
<PackageReference Include="Ailaibo.CSharp.SM2" />
paket add Ailaibo.CSharp.SM2 --version 1.0.0
#r "nuget: Ailaibo.CSharp.SM2, 1.0.0"
#:package Ailaibo.CSharp.SM2@1.0.0
#addin nuget:?package=Ailaibo.CSharp.SM2&version=1.0.0
#tool nuget:?package=Ailaibo.CSharp.SM2&version=1.0.0
Ailaibo.CSharp.SM2
国密 SM2 椭圆曲线公钥密码算法的 C# 实现 —— 零依赖、纯托管,支持从 .NET Framework 4.0 到 .NET 8.0 的各类项目直接引用。
- 作者:Chu Lijia
- 公司:Beijing iLab mobile Co,.Ltd
- 版本:1.0.0
- 标准:GM/T 0003-2012《SM2 椭圆曲线公钥密码算法》、GM/T 0004-2012《SM3 密码杂凑算法》
- 标签:SM2 / Ailaibo / C# / CSharp
简介
SM2 是国家密码管理局发布的椭圆曲线公钥密码算法标准,包含密钥对生成、数据加密解密、数字签名验签三大功能,广泛用于政务、金融、能源等领域的身份认证、数据保护与电子签章场景。
本包是 SM2 的 C# 实现,具有以下特点:
- 零第三方依赖:不引用 BouncyCastle 等任何密码库,椭圆曲线点乘、点加、模逆、模运算全部手写实现;
- 不调用系统内置国密 API:不依赖 OpenSSL / Windows CNG,纯托管代码自带完整算法;
- 不使用 unsafe 代码:全部安全托管 C#,可在任意平台运行;
- 不使用版本专属 API:同一份源码编译到 net40 / net452 / netstandard2.0 / netstandard2.1 / net8.0;
- 两种密文格式:同时支持
C1 ‖ C3 ‖ C2(新标准)与C1 ‖ C2 ‖ C3(旧格式),可配置切换; - 两种公钥格式:同时支持 65 字节未压缩公钥与 33 字节压缩公钥;
- 内置官方测试向量:可用
Sm2TestVectors.Verify()在部署现场做一次冒烟自检。
安装
.NET CLI:
dotnet add package Ailaibo.CSharp.SM2 --version 1.0.0
Package Manager:
Install-Package Ailaibo.CSharp.SM2 -Version 1.0.0
或在 .csproj 中:
<PackageReference Include="Ailaibo.CSharp.SM2" Version="1.0.0" />
提示: 若项目需要独立的 SM3 哈希能力(流式计算、分段多次
Update、与HashAlgorithm集成等), 请另行引用配套包Ailaibo.CSharp.SM3。本包不对外提供 SM3 的算法能力,仅供 SM2 内部按标准计算摘要。
支持的目标框架
| 目标框架 | 说明 |
|---|---|
| net40 | .NET Framework 4.0(旧系统兼容) |
| net452 | .NET Framework 4.5.2 / 4.6.x / 4.7.x / 4.8 |
| netstandard2.0 | 覆盖 .NET Core 2.0+ / .NET 5+ 等大多数现代项目 |
| netstandard2.1 | .NET Core 3.0+ / .NET 5+ |
| net8.0 | .NET 8 |
快速上手
命名空间是
Ailaibo.CSharp,主类SM2的完整类型名为Ailaibo.CSharp.SM2。
using System.Numerics;
using System.Text;
using Ailaibo.CSharp;
// 1) 生成密钥对
SM2KeyPair keyPair = SM2.GenerateKeyPair();
byte[] publicKey = keyPair.PublicKey; // 65 字节,04 开头(未压缩)
byte[] publicKeyC = keyPair.PublicKeyCompressed; // 33 字节,02/03 开头(压缩)
BigInteger privateKey = keyPair.PrivateKey;
// 2) 加密与解密(默认 C1C3C2 新标准格式)
byte[] plainData = Encoding.UTF8.GetBytes("国密 SM2 加密演示");
byte[] cipherText = SM2.Encrypt(plainData, publicKey); // isNewFormat: true
byte[] decrypted = SM2.Decrypt(cipherText, privateKey);
// 旧格式 C1C2C3:把 isNewFormat 传 false
byte[] legacyCipher = SM2.Encrypt(plainData, publicKey, false);
byte[] legacyPlain = SM2.Decrypt(legacyCipher, privateKey, false);
// 3) 签名与验签(默认用户 ID "1234567812345678")
byte[] message = Encoding.UTF8.GetBytes("订单号 20260921-0001");
byte[] signature = SM2.Sign(message, privateKey); // 64 字节 r ‖ s
bool ok = SM2.Verify(message, signature, publicKey);
// 4) 自定义用户 ID(默认可省略)
byte[] sig2 = SM2.Sign(message, privateKey, "ALICE123@YAHOO.COM");
bool ok2 = SM2.Verify(message, sig2, publicKey, "ALICE123@YAHOO.COM");
// 5) 用密钥对对象调用更省事
byte[] cipher2 = SM2.Encrypt(plainData, keyPair);
byte[] plain2 = SM2.Decrypt(cipher2, keyPair);
byte[] sig3 = SM2.Sign(message, keyPair);
bool ok3 = SM2.Verify(message, sig3, keyPair.PublicKeyCompressed); // 压缩公钥也可验签
// 6) 从已有私钥恢复密钥对(十六进制 / 字节数组 / BigInteger 三种方式)
SM2KeyPair restored = SM2.GenerateKeyPair(SM2Utils.HexToBigInteger("3945208F...4DF7C5B8"));
API 一览
SM2(静态方法)
| 成员 | 说明 |
|---|---|
GenerateKeyPair() |
生成新的 SM2 密钥对 |
GenerateKeyPair(BigInteger privateKey) |
由私钥大整数恢复密钥对 |
GenerateKeyPair(byte[] privateKeyBytes) |
由私钥字节恢复密钥对 |
Encrypt(byte[] plainData, byte[] publicKey, bool isNewFormat = true) |
用公钥加密,返回密文 |
Encrypt(byte[] plainData, SM2KeyPair keyPair, bool isNewFormat = true) |
用密钥对的公钥加密 |
Decrypt(byte[] cipherData, BigInteger privateKey, bool isNewFormat) |
用私钥解密 |
Decrypt(byte[] cipherData, SM2KeyPair keyPair, bool isNewFormat) |
用密钥对的私钥解密 |
Sign(byte[] data, BigInteger privateKey, string? userId = null) |
签名,返回 64 字节 R ‖ S |
Sign(byte[] data, SM2KeyPair keyPair, string? userId = null) |
用密钥对的私钥签名 |
SignWithId(byte[] data, BigInteger privateKey, byte[] userId) |
签名,用户 ID 以原始字节给出 |
Verify(byte[] data, byte[] signature, byte[] publicKey, string? userId) |
验签,返回 bool |
VerifyWithId(byte[] data, byte[] signature, byte[] publicKey, byte[] userId) |
验签,用户 ID 以原始字节给出 |
ComputeZA(byte[] publicKey, string? userId = null) |
计算 ZA = SM3(ENTL ‖ ID ‖ a ‖ b ‖ xG ‖ yG ‖ xA ‖ yA) |
ComputeDigest(byte[] data, byte[] publicKey, string? userId) |
计算待签名摘要 e = SM3(ZA ‖ M),便于与"只签名摘要"的系统对接 |
Sm3Hash(byte[] data) |
便捷方法:计算标准 SM3 杂凑值(不含 ZA) |
SM2.DefaultUserId |
标准默认用户 ID "1234567812345678" |
SM2.SignatureSizeInBytes |
签名固定长度 64 |
SM2KeyPair
| 成员 | 说明 |
|---|---|
PrivateKey |
私钥大整数 d,取值 [1, n-2] |
PrivateKeyBytes |
私钥的固定 32 字节大端表示 |
PrivateKeyHex |
私钥的小写十六进制字符串 |
PublicKey |
未压缩公钥,65 字节 04 ‖ x ‖ y |
PublicKeyCompressed |
压缩公钥,33 字节 02/03 ‖ x |
PublicKeyX / PublicKeyY |
公钥坐标 |
PublicKeyHex |
未压缩公钥的十六进制字符串 |
PublicKeyCompressedHex |
压缩公钥的十六进制字符串 |
GetPublicKey(bool compressed) |
按指定格式取得公钥副本 |
ToString() |
便于日志阅读的摘要,私钥已脱敏 |
SM2Utils(辅助工具)
| 成员 | 说明 |
|---|---|
BigIntegerToBytes(BigInteger) |
大整数 → 最短大端无符号字节数组 |
BigIntegerToBytes(BigInteger, int fixedLength) |
大整数 → 固定长度大端字节数组(左侧补零) |
BytesToBigInteger(byte[]) |
大端字节数组 → 非负大整数 |
ToHex(byte[]) / ToHex(byte[], bool) |
字节数组 → 十六进制(可指定大小写) |
FromHex(string) |
十六进制 → 字节数组(忽略空格等分隔符) |
HexToBigInteger(string) |
十六进制 → 大整数 |
CompressPublicKey(byte[]) |
未压缩公钥 → 压缩公钥 |
DecompressPublicKey(byte[]) |
压缩公钥 → 未压缩公钥 |
IsValidPublicKey(byte[]) |
校验公钥格式并确认点确实在曲线上 |
IsCompressedPublicKey(byte[]) / IsUncompressedPublicKey(byte[]) |
格式判断 |
Sm2TestVectors(官方测试向量与自检)
| 成员 | 说明 |
|---|---|
Verify() |
自检,失败抛 InvalidOperationException |
TryVerify(out string? failedName) |
自检,通过返回值报告结果 |
CurvePHex / CurveAHex / … |
GM/T 0003.5-2012 推荐曲线参数 |
Encryption*Hex |
官方加密样例的私钥、公钥、k、C1、C2、C3 |
Signature*Hex |
官方签名样例的示例曲线参数、ID、ZA、e 等 |
Sm3Sample1HashHex / Sm3Sample2HashHex |
SM3 官方测试向量 |
密文与公钥格式说明
密文结构
| 格式 | 结构 | 何时使用 |
|---|---|---|
C1C3C2 |
C1(65) ‖ C3(32) ‖ C2(klen) |
默认,isNewFormat = true |
C1C2C3 |
C1(65) ‖ C2(klen) ‖ C3(32) |
旧格式,isNewFormat = false |
C1是椭圆曲线点[k]G,按国密标准编码为未压缩点04 ‖ x ‖ y,共 65 字节;C2是密文主体,长度与明文相同;C3是 32 字节 SM3 完整性校验码,解密时会校验,不通过则抛CryptographicException。
对接提醒: 与外部系统对接时必须先确认对方的 C1 是否带 0x04 前缀、C2/C3 的先后顺序。
本包加密时始终输出标准的 65 字节带前缀 C1;解密时会自动兼容省略 0x04 的 64 字节 C1 形式。
公钥结构
| 格式 | 长度 | 结构 | 说明 |
|---|---|---|---|
| 未压缩 | 65 | 04 ‖ x(32) ‖ y(32) |
需求中所述的"04 开头原始公钥" |
| 压缩 | 33 | 02/03 ‖ x(32) |
前缀 02 表示 y 为偶数、03 为奇数 |
两种格式可无损互转,本包的所有接口(加密、验签)都同时接受两种格式。
注意事项
- 命名空间:本包命名空间为
Ailaibo.CSharp,主类SM2的完整类型名为Ailaibo.CSharp.SM2。 该命名空间与配套的Ailaibo.CSharp.SM3包共用,两个包同时引用不会冲突(类名不重叠)。 - 明文不能为空:
Encrypt/Sign/Verify/ComputeDigest对空字节数组会抛ArgumentException;null参数抛ArgumentNullException。 - 私钥取值范围:必须是
[1, n-2]内的大整数。上限取n-2而非n-1,是因为签名需要计算(1+d)的模逆, 而d = n-1时1+d ≡ 0,逆元不存在。 - 文本一律 UTF-8:
userId字符串按 UTF-8 编码。若约定的是其它编码或二进制 ID, 请改用SignWithId/VerifyWithId直接传入原始字节。 - 签名是随机化的:同一数据两次签名结果不同,这是正常行为;验签只关心是否通过。
- 加密是概率加密:同一明文两次加密得到的密文不同,切勿通过比较密文判断明文是否相同。
- 用户 ID 必须双方一致:
ZA把用户 ID 与公钥绑定进签名,验签方使用不同的 ID 必然失败。 未指定时双方都使用标准默认值"1234567812345678"。 - 侧信道限制:本实现基于
BigInteger,运算时间与操作数取值相关,属于非恒定时间实现, 不具备抗计时 / 功耗等侧信道攻击能力。适用于业务系统加解密与签名验签; 若用于智能卡、金融 IC 卡等攻击者能精确测量运算耗时的环境,请改用经国密认证的硬件密码模块。 - 已废弃用法:国密算法存在多种"盐值/ID"约定与 ASN.1 DER 编码签名格式(
SM2Signature ::= SEQUENCE { R, S })。 本包输出的是 64 字节裸R ‖ S,不包含 ASN.1 包装;若对接方要求 DER 编码,请自行封装。
版本历史
| 版本 | 说明 |
|---|---|
| 1.0.0 | 首个发布版本:SM2 纯托管实现,含密钥对生成、C1C3C2 / C1C2C3 双格式加解密、SM3 摘要签名验签、自定义用户 ID、公钥压缩解压缩,内置 GM/T 0003.5-2012 官方测试向量。 |
许可与支持
MIT 许可。如需商业授权或技术支持,请联系作者 Chu Lijia(Beijing iLab mobile Co,.Ltd)。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net40 is compatible. net403 was computed. net45 was computed. net451 was computed. net452 is compatible. net46 was computed. net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.0
- No dependencies.
-
.NETFramework 4.5.2
- No dependencies.
-
.NETStandard 2.0
- No dependencies.
-
.NETStandard 2.1
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 134 | 9/21/2026 |