当前位置: 首页 > 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.rkmt.cn/news/1420018.html

相关文章:

  • 遥感影像处理:用Python的GDAL库把TIF批量转成PNG(附完整代码)
  • ARM9上跑FreeRTOS?手把手教你为S3C2440移植系统心跳(附完整代码)
  • 告别官方例程:在VSCode中从零搭建你的第一个Franka机械臂控制项目(基于libfranka 0.7.0)
  • K-means聚类实战:如何用Python可视化评估最佳K值(手把手画图+SSE分析指南)
  • 新手别怕!用Volatility 2.6分析WinXP内存镜像,一步步揪出svchost里的恶意dll
  • 天猫超市购物卡还能这样用?快速回收指南! - 团团收购物卡回收
  • 自动化如何避免踩坑?2026企业避坑指南与AI Agent实战解析
  • 3分钟掌握猫抓资源嗅探:网页视频音频一键下载终极指南
  • Arch Linux虚拟机里,用Xfce桌面+Fcitx5搞定中文输入(附VNC远程桌面配置)
  • 如何快速获取百度网盘提取码:3步解锁海量资源的实用指南
  • 不止VMware!Windows 11安卓子系统、Docker都需要的Intel VT-x,如何在Win10/Win11下快速检查与开启?
  • 从收音机到锁相环:聊聊模拟乘法器AD834在通信系统里的那些‘隐藏’技能
  • 金属管浮子流量计是什么 产品定义与核心测量优势介绍 - 陈工日常
  • Win10系统更新后Word打不开?报错0xc0000142的完整排查与修复指南(含避坑提醒)
  • 2026年最新辽阳市黄金回收白银回收铂金回收靠谱店铺权威排行榜:纯金+金条+银条+钯金 门店地址及联系方式推荐 - 亦辰小黄鸭
  • 用LTC6268-10这颗4GHz运放,搞定你的高阻抗传感器信号放大难题
  • 别再死记硬背了!用生活中的例子帮你彻底搞懂CSMA/CD和CSMA/CA
  • 生成式视频时代的提示词护城河,Sora 2专属Prompt-LLM协同框架首度解密(仅限首批内测开发者)
  • 半导体可靠性工程师必看:IEC62380与SN29500标准详解,如何影响你的FIT报告和客户交付?
  • 工业网关吞吐量上不去?可能是你的IxChariot脚本和Pair设置没做对
  • 时间序列预测实战:用ACF和PACF为股票周线数据挑选ARIMA模型的最佳参数(p,d,q)
  • K-means实战避坑指南:你的‘最近邻中心’计算真的高效吗?对比NumPy循环与向量化实现
  • 项目介绍 MATLAB实现基于随机森林(RF)进行回归预测(含模型描述及部分示例代码)专栏近期有大量优惠 还请多多点一下关注 加油 谢谢 你的鼓励是我前行的动力 谢谢支持 加油 谢谢
  • 2026年最新林州市黄金回收白银回收铂金回收靠谱店铺权威排行榜:纯金+金条+银条+钯金 门店地址及联系方式推荐 - 亦辰小黄鸭
  • 告别路径烦恼:手把手教你用Supra 2022.6.21为AG1280Q48创建全英文工程(附常见错误排查)
  • 不用担心,京东福粒卡快速变现竟然这么简单! - 团团收购物卡回收
  • C#写的Modbus RTU串口通信工程包,带主站测试工具和完整VS项目
  • 2026年乐平市正规上门黄金白银回收品牌门店名录:K金+铂金+金条+银条回收门店联系方式推荐+指南 - 前途无量YY
  • Windows桌面仓库管理系统源码:MFC+C++开发,含SQL Server数据库与权限登录
  • 5000张实拍森林火灾烟雾图,带VOC/COCO/YOLO三格式标注、自动划分脚本与YOLOv5/v8训练全流程指南