当前位置: 首页 > news >正文

SM2国密算法在C#里到底怎么用?一个控制台程序带你搞定加密、解密和签名验签

SM2国密算法实战:从加密解密到签名验签的C#完整实现

国密算法作为信息安全领域的重要基础设施,正在金融、政务、物联网等行业快速普及。其中SM2作为非对称加密算法的代表,相比传统RSA在安全性和效率上都有显著优势。但对于大多数C#开发者来说,如何在实际项目中正确使用SM2仍然是个挑战——从加密解密的基本操作,到数字签名与验签的核心场景,再到各种格式兼容的"坑点",都需要系统的实战指导。

本文将带你用Visual Studio构建一个完整的控制台应用,不仅实现SM2的基础加密功能,更重点解决数字签名这一高频使用场景。我们会使用BouncyCastle这一成熟加密库,同时解释每个关键参数的技术含义,最后还会专门分析C1C2C3和C1C3C2格式差异这个"经典陷阱"。

1. 环境准备与基础配置

在开始编码前,我们需要准备好开发环境。创建一个新的.NET Core控制台应用(.NET 6或更高版本),然后通过NuGet添加必要的依赖包:

dotnet add package BouncyCastle.Cryptography --version 2.2.1 dotnet add package Portable.BouncyCastle --version 1.9.0

这两个包提供了完整的SM2算法实现。接下来,我们定义一个静态类SM2Helper来封装所有操作:

using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; using Org.BouncyCastle.Math.EC; using Org.BouncyCastle.Security; using System.Text; public static class SM2Helper { // 国密标准SM2椭圆曲线参数 private static readonly X9ECParameters sm2ECParameters = ECNamedCurveTable.GetByName("sm2p256v1"); private static readonly ECDomainParameters domainParameters = new ECDomainParameters( sm2ECParameters.Curve, sm2ECParameters.G, sm2ECParameters.N, sm2ECParameters.H); // 其他方法将在这里实现... }

注意:sm2p256v1是国密标准定义的椭圆曲线名称,包含了所有必要的参数,包括素数域、曲线方程系数和基点等。

2. 密钥对生成与管理

SM2作为非对称加密算法,密钥对生成是第一步。我们需要同时支持生成新密钥对和加载已有密钥:

public static (ECPrivateKeyParameters privateKey, ECPublicKeyParameters publicKey) GenerateKeyPair() { var generator = GeneratorUtilities.GetKeyPairGenerator("EC"); generator.Init(new ECKeyGenerationParameters(domainParameters, new SecureRandom())); AsymmetricCipherKeyPair keyPair = generator.GenerateKeyPair(); return ( (ECPrivateKeyParameters)keyPair.Private, (ECPublicKeyParameters)keyPair.Public ); } public static string PublicKeyToString(ECPublicKeyParameters publicKey) { byte[] encoded = publicKey.Q.GetEncoded(false); // false表示不压缩 return BitConverter.ToString(encoded).Replace("-", ""); } public static ECPublicKeyParameters PublicKeyFromString(string publicKeyHex) { byte[] bytes = HexToBytes(publicKeyHex); ECPoint point = domainParameters.Curve.DecodePoint(bytes); return new ECPublicKeyParameters(point, domainParameters); }

密钥生成后,我们可以这样使用:

var (privateKey, publicKey) = SM2Helper.GenerateKeyPair(); string pubKeyHex = SM2Helper.PublicKeyToString(publicKey); Console.WriteLine($"生成的公钥:{pubKeyHex}"); // 保存和加载示例 ECPublicKeyParameters loadedPubKey = SM2Helper.PublicKeyFromString(pubKeyHex);

3. 加密与解密实现

SM2的加密过程比RSA复杂,因为它涉及椭圆曲线点的运算。以下是完整的加密解密实现:

public static byte[] Encrypt(ECPublicKeyParameters publicKey, byte[] plainData) { var cipher = CipherUtilities.GetCipher("SM2"); cipher.Init(true, new ParametersWithRandom(publicKey, new SecureRandom())); return cipher.DoFinal(plainData); } public static byte[] Decrypt(ECPrivateKeyParameters privateKey, byte[] cipherData) { var cipher = CipherUtilities.GetCipher("SM2"); cipher.Init(false, privateKey); return cipher.DoFinal(cipherData); }

实际使用时,我们通常会处理字符串而非原始字节数组,所以可以添加便捷方法:

public static string EncryptString(ECPublicKeyParameters publicKey, string plainText) { byte[] data = Encoding.UTF8.GetBytes(plainText); byte[] encrypted = Encrypt(publicKey, data); return BitConverter.ToString(encrypted).Replace("-", ""); } public static string DecryptString(ECPrivateKeyParameters privateKey, string cipherText) { byte[] data = HexToBytes(cipherText); byte[] decrypted = Decrypt(privateKey, data); return Encoding.UTF8.GetString(decrypted); }

测试加密解密流程:

string original = "这是一条需要加密的敏感信息"; Console.WriteLine($"原始文本:{original}"); string encrypted = SM2Helper.EncryptString(publicKey, original); Console.WriteLine($"加密结果:{encrypted}"); string decrypted = SM2Helper.DecryptString(privateKey, encrypted); Console.WriteLine($"解密结果:{decrypted}");

4. 数字签名与验签实战

数字签名是SM2最常用的场景之一,用于验证消息的真实性和完整性。以下是完整的签名验签实现:

public static byte[] Sign(ECPrivateKeyParameters privateKey, byte[] data, byte[] userId = null) { var signer = SignerUtilities.GetSigner("SM3withSM2"); signer.Init(true, new ParametersWithID(privateKey, userId ?? Encoding.UTF8.GetBytes("1234567812345678"))); signer.BlockUpdate(data, 0, data.Length); return signer.GenerateSignature(); } public static bool Verify(ECPublicKeyParameters publicKey, byte[] data, byte[] signature, byte[] userId = null) { var signer = SignerUtilities.GetSigner("SM3withSM2"); signer.Init(false, new ParametersWithID(publicKey, userId ?? Encoding.UTF8.GetBytes("1234567812345678"))); signer.BlockUpdate(data, 0, data.Length); return signer.VerifySignature(signature); }

重要提示:SM2签名需要用户ID参数,通常使用默认值"1234567812345678",但在实际项目中应根据业务需求设置特定值。

签名验签的字符串版本:

public static string SignString(ECPrivateKeyParameters privateKey, string message, string userId = "1234567812345678") { byte[] data = Encoding.UTF8.GetBytes(message); byte[] userIdBytes = Encoding.UTF8.GetBytes(userId); byte[] signature = Sign(privateKey, data, userIdBytes); return BitConverter.ToString(signature).Replace("-", ""); } public static bool VerifyString(ECPublicKeyParameters publicKey, string message, string signatureHex, string userId = "1234567812345678") { byte[] data = Encoding.UTF8.GetBytes(message); byte[] signature = HexToBytes(signatureHex); byte[] userIdBytes = Encoding.UTF8.GetBytes(userId); return Verify(publicKey, data, signature, userIdBytes); }

实际应用示例——模拟用户登录令牌的签名与验证:

// 模拟生成登录令牌 string userId = "user123"; DateTime expireTime = DateTime.Now.AddHours(2); string tokenData = $"{userId}|{expireTime:yyyy-MM-dd HH:mm:ss}"; // 用私钥签名 string signature = SM2Helper.SignString(privateKey, tokenData, userId); Console.WriteLine($"令牌签名:{signature}"); // 验证签名(服务端操作) bool isValid = SM2Helper.VerifyString(publicKey, tokenData, signature, userId); Console.WriteLine($"签名验证结果:{isValid}"); // 尝试篡改数据后的验证 string tamperedData = tokenData.Replace("user123", "attacker"); bool isTamperedValid = SM2Helper.VerifyString(publicKey, tamperedData, signature, userId); Console.WriteLine($"篡改后验证结果:{isTamperedValid}");

5. 关键问题解析与实战技巧

5.1 C1C2C3与C1C3C2格式问题

这是SM2实现中最常见的兼容性问题。不同厂商可能采用不同的密文结构:

  • 旧标准:C1C2C3(65字节C1 + 变长C2 + 32字节C3)
  • 新标准:C1C3C2(65字节C1 + 32字节C3 + 变长C2)

处理这个问题的实用方法:

public static byte[] ConvertCipherFormat(byte[] cipherData, bool fromC1C2C3ToC1C3C2) { if (cipherData.Length < 97) throw new ArgumentException("Invalid cipher data length"); byte[] c1 = new byte[65]; // 04 + 32字节x + 32字节y byte[] c3 = new byte[32]; // SM3哈希值 byte[] c2 = new byte[cipherData.Length - 97]; // 实际密文 if (fromC1C2C3ToC1C3C2) { Buffer.BlockCopy(cipherData, 0, c1, 0, 65); Buffer.BlockCopy(cipherData, 65, c2, 0, c2.Length); Buffer.BlockCopy(cipherData, 65 + c2.Length, c3, 0, 32); } else { Buffer.BlockCopy(cipherData, 0, c1, 0, 65); Buffer.BlockCopy(cipherData, 65, c3, 0, 32); Buffer.BlockCopy(cipherData, 97, c2, 0, c2.Length); } // 转换为目标格式 byte[] result = new byte[cipherData.Length]; Buffer.BlockCopy(c1, 0, result, 0, 65); if (fromC1C2C3ToC1C3C2) { Buffer.BlockCopy(c3, 0, result, 65, 32); Buffer.BlockCopy(c2, 0, result, 97, c2.Length); } else { Buffer.BlockCopy(c2, 0, result, 65, c2.Length); Buffer.BlockCopy(c3, 0, result, 65 + c2.Length, 32); } return result; }

5.2 公钥前缀04的含义

在SM2公钥中,开头的04表示这是一个非压缩格式的公钥,后面跟着的是X和Y坐标各32字节。处理时:

public static ECPublicKeyParameters PublicKeyFromString(string publicKeyHex) { if (publicKeyHex.StartsWith("04") && publicKeyHex.Length > 2) { publicKeyHex = publicKeyHex.Substring(2); } byte[] bytes = HexToBytes(publicKeyHex); if (bytes.Length != 64) // 32字节X + 32字节Y { throw new ArgumentException("Invalid public key length"); } // 重建带04前缀的完整公钥 byte[] fullKey = new byte[65]; fullKey[0] = 0x04; Buffer.BlockCopy(bytes, 0, fullKey, 1, 64); ECPoint point = domainParameters.Curve.DecodePoint(fullKey); return new ECPublicKeyParameters(point, domainParameters); }

5.3 性能优化建议

SM2虽然比RSA快,但在高并发场景下仍需优化:

  1. 重用密钥对象:避免在每次操作时都解析密钥
  2. 使用对象池:对于频繁的加密/解密操作,重用Cipher对象
  3. 异步处理:对于大量数据的处理,使用异步方法
// 对象池示例 public class SM2CipherPool { private readonly ConcurrentBag<ISigner> _signerPool = new(); private readonly ConcurrentBag<IBufferedCipher> _cipherPool = new(); public ISigner GetSigner() { if (_signerPool.TryTake(out var signer)) { return signer; } signer = SignerUtilities.GetSigner("SM3withSM2"); return signer; } public void ReturnSigner(ISigner signer) { signer.Reset(); _signerPool.Add(signer); } // 类似实现Cipher的池化方法... }

6. 完整示例:安全通信系统

让我们把这些知识点整合到一个实际场景中——两个系统之间的安全通信:

// 系统A准备发送安全消息 var (privateKeyA, publicKeyA) = SM2Helper.GenerateKeyPair(); var (_, publicKeyB) = SM2Helper.GenerateKeyPair(); // 系统B的公钥 string originalMessage = "这是一条机密业务数据"; Console.WriteLine($"原始消息:{originalMessage}"); // 1. 用B的公钥加密消息 string encryptedMessage = SM2Helper.EncryptString(publicKeyB, originalMessage); Console.WriteLine($"加密后消息:{encryptedMessage}"); // 2. 用A的私钥签名 string signature = SM2Helper.SignString(privateKeyA, encryptedMessage); Console.WriteLine($"消息签名:{signature}"); // 系统B接收并处理消息 // 3. 验证签名 bool isSignatureValid = SM2Helper.VerifyString(publicKeyA, encryptedMessage, signature); Console.WriteLine($"签名验证结果:{isSignatureValid}"); if (isSignatureValid) { // 4. 用B的私钥解密 string decryptedMessage = SM2Helper.DecryptString(privateKeyB, encryptedMessage); Console.WriteLine($"解密后消息:{decryptedMessage}"); } else { Console.WriteLine("警告:消息签名验证失败,可能被篡改!"); }

这个示例展示了典型的端到端加密通信流程,结合了SM2的加密和签名能力,确保数据的机密性、真实性和完整性。

http://www.jsqmd.com/news/908712/

相关文章:

  • 量子启发式算法优化分子对接技术研究
  • 遥感影像处理:用Python的GDAL库把TIF批量转成PNG(附完整代码)
  • ARM9上跑FreeRTOS?手把手教你为S3C2440移植系统心跳(附完整代码)
  • 国内金属管浮子流量计优质厂家推荐 源头生产厂家盘点 - 陈工日常
  • 2026年青海律师咨询靠谱吗,青海观若律师事务所值得信赖 - myqiye
  • 告别官方例程:在VSCode中从零搭建你的第一个Franka机械臂控制项目(基于libfranka 0.7.0)
  • K-means聚类实战:如何用Python可视化评估最佳K值(手把手画图+SSE分析指南)
  • GitHub 15 万 star,Karpathy 的四条 CLAUDE.md 规则让社区炸锅,准确率从 41% 升到 89%
  • 2026年莱西市正规上门黄金白银回收品牌门店名录:K金+铂金+金条+银条回收门店联系方式推荐+指南 - 前途无量YY
  • 新手别怕!用Volatility 2.6分析WinXP内存镜像,一步步揪出svchost里的恶意dll
  • 天猫超市购物卡还能这样用?快速回收指南! - 团团收购物卡回收
  • 自动化如何避免踩坑?2026企业避坑指南与AI Agent实战解析
  • 2026年做脆口剁椒好的湖南品牌怎么选 - myqiye
  • Debian 11 Bullseye 上手初体验:给老笔记本续命的5个理由
  • 别再只会用数组了!Halcon向量和字典的5个实战用法,效率翻倍
  • 2026年莱阳市正规上门黄金白银回收品牌门店名录:K金+铂金+金条+银条回收门店联系方式推荐+指南 - 前途无量YY
  • 2026年井冈山市正规上门黄金白银回收品牌门店名录:K金+铂金+金条+银条回收门店联系方式推荐+指南 - 前途无量YY
  • 笔记本电脑显卡优化终极指南:NVIDIA Profile Inspector免费性能调校工具
  • Mermaid Live Editor:7个强力功能打造专业在线流程图编辑器终极体验
  • 购物卡回收这样做,天猫超市卡轻松变现! - 团团收购物卡回收
  • 语言结构如何塑造专业权威:从语法到技术沟通的实践指南
  • 别急着重装系统!解决Linux内核menuconfig编译错误的完整排查指南
  • 3分钟掌握猫抓资源嗅探:网页视频音频一键下载终极指南
  • Arch Linux虚拟机里,用Xfce桌面+Fcitx5搞定中文输入(附VNC远程桌面配置)
  • 别再只用普通卷积了!深入浅出图解ODConv的四维注意力机制
  • 电子民主技术架构:从数据开放到安全投票的实践路径
  • 告别libfranka官方例程:手把手教你用VSCode+CMake搭建自己的Franka机械臂控制项目
  • 2026年莱州市正规上门黄金白银回收品牌门店名录:K金+铂金+金条+银条回收门店联系方式推荐+指南 - 前途无量YY
  • 如何快速获取百度网盘提取码:3步解锁海量资源的实用指南
  • 不止VMware!Windows 11安卓子系统、Docker都需要的Intel VT-x,如何在Win10/Win11下快速检查与开启?