You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

267 lines
10 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using Org.BouncyCastle.Asn1;
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.Crypto.Signers;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Security;
using System;
using System.Text;
namespace Infrastructure.Helpers
{
public class SM2CryptoHelper
{
private static readonly ECDomainParameters DomainParams;
// 国标要求的默认用户ID
private static readonly byte[] DefaultUserId = Encoding.UTF8.GetBytes("1234567812345678");
static SM2CryptoHelper()
{
var curve = GMNamedCurves.GetByName("sm2p256v1");
DomainParams = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H);
}
#region 生成密钥对
// 生成SM2密钥对
public static (byte[] privateKey, byte[] publicKey) GenerateKeyPair()
{
var gen = new ECKeyPairGenerator();
gen.Init(new ECKeyGenerationParameters(DomainParams, new SecureRandom()));
var keyPair = gen.GenerateKeyPair();
var privKey = (ECPrivateKeyParameters)keyPair.Private;
var pubKey = (ECPublicKeyParameters)keyPair.Public;
return (
AdjustTo32Bytes(privKey.D.ToByteArrayUnsigned()),
pubKey.Q.GetEncoded(false) // 未压缩格式公钥(65字节)
);
}
#endregion
#region 签名 验签
// SM2签名 (R|S裸编码64字节)
public static byte[] Sign(byte[] privateKey, byte[] data)
{
if (privateKey == null || privateKey.Length != 32)
throw new ArgumentException("Private key must be 32 bytes");
var d = new BigInteger(1, privateKey);
var privateKeyParam = new ECPrivateKeyParameters(d, DomainParams);
// 使用国标要求的用户ID
var signer = new SM2Signer(new SM3Digest());
signer.Init(true, new ParametersWithID(
new ParametersWithRandom(privateKeyParam, new SecureRandom()),
DefaultUserId));
signer.BlockUpdate(data, 0, data.Length);
byte[] derSignature = signer.GenerateSignature();
// 解析DER格式为R|S裸编码
//return DerToRawSignature(derSignature);
// 使用更可靠的 DER 转裸签名方法
return ConvertDerToRawSignature(derSignature, 32);
}
// SM2验签
public static bool Verify(byte[] publicKey, byte[] data, byte[] signature)
{
if (signature.Length != 64)
throw new ArgumentException("Signature must be 64 bytes raw format (R|S)");
if (publicKey == null || (publicKey.Length != 65 && publicKey[0] == 0x04))
throw new ArgumentException("Public key must be in uncompressed format (65 bytes)");
try
{
// 将裸签名转换为DER格式
//byte[] derSignature = RawToDerSignature(signature);
byte[] derSignature = ConvertRawToDerSignature(signature, 32);
ECPoint q = DomainParams.Curve.DecodePoint(publicKey);
var pubKey = new ECPublicKeyParameters(q, DomainParams);
var signer = new SM2Signer(new SM3Digest());
signer.Init(false, new ParametersWithID(pubKey, DefaultUserId));
signer.BlockUpdate(data, 0, data.Length);
return signer.VerifySignature(derSignature);
}
catch
{
return false;
}
}
#endregion
#region 加密解密
// SM2加密 (C1C3C2模式)
public static byte[] Encrypt(byte[] publicKey, byte[] plaintext)
{
if (publicKey == null || publicKey.Length != 65)
throw new ArgumentException("Public key must be 65 bytes uncompressed format");
ECPoint q = DomainParams.Curve.DecodePoint(publicKey);
var pubKey = new ECPublicKeyParameters(q, DomainParams);
var cipher = new SM2Engine(new SM3Digest());
cipher.Init(true, new ParametersWithRandom(pubKey, new SecureRandom()));
byte[] encrypted = cipher.ProcessBlock(plaintext, 0, plaintext.Length);
// 转换为C1C3C2模式
return ConvertToC1C3C2(encrypted);
//return cipher.ProcessBlock(plaintext, 0, plaintext.Length);
}
/// <summary>
/// 将C1C2C3格式转换为C1C3C2格式
/// </summary>
private static byte[] ConvertToC1C3C2(byte[] c1c2c3)
{
// 密文结构: C1(65字节) + C2(明文长度) + C3(32字节)
int c1Length = 65; // C1部分长度
int c3Length = 32; // C3部分长度
int c2Length = c1c2c3.Length - c1Length - c3Length; // C2部分长度
if (c2Length < 0)
throw new ArgumentException("Invalid ciphertext length");
// 创建新数组: C1(65) + C3(32) + C2(变长)
byte[] c1c3c2 = new byte[c1c2c3.Length];
// 复制C1部分 (0-65字节)
Buffer.BlockCopy(c1c2c3, 0, c1c3c2, 0, c1Length);
// 复制C3部分 (最后32字节)
Buffer.BlockCopy(c1c2c3, c1Length + c2Length, c1c3c2, c1Length, c3Length);
// 复制C2部分 (65到65+c2Length)
Buffer.BlockCopy(c1c2c3, c1Length, c1c3c2, c1Length + c3Length, c2Length);
return c1c3c2;
}
// SM2解密 (C1C3C2模式)
public static byte[] Decrypt(byte[] privateKey, byte[] ciphertext)
{
if (privateKey == null || privateKey.Length != 32)
throw new ArgumentException("Private key must be 32 bytes");
// 先将密文转换为C1C2C3模式BouncyCastle默认格式
byte[] c1c2c3 = ConvertToC1C2C3(ciphertext);
var d = new BigInteger(1, privateKey);
var privKey = new ECPrivateKeyParameters(d, DomainParams);
// 明确指定C1C3C2模式
var cipher = new SM2Engine(new SM3Digest());
cipher.Init(false, privKey);
try
{
return cipher.ProcessBlock(c1c2c3, 0, c1c2c3.Length);
//return cipher.ProcessBlock(ciphertext, 0, ciphertext.Length);
}
catch (Exception ex)
{
throw new CryptoException("Decryption failed", ex);
}
}
/// <summary>
/// 将C1C3C2格式转换为C1C2C3格式
/// </summary>
private static byte[] ConvertToC1C2C3(byte[] c1c3c2)
{
// 密文结构: C1(65字节) + C3(32字节) + C2(明文长度)
int c1Length = 65; // C1部分长度
int c3Length = 32; // C3部分长度
int c2Length = c1c3c2.Length - c1Length - c3Length; // C2部分长度
if (c2Length < 0)
throw new ArgumentException("Invalid ciphertext length");
// 创建新数组: C1(65) + C2(变长) + C3(32)
byte[] c1c2c3 = new byte[c1c3c2.Length];
// 复制C1部分 (0-65字节)
Buffer.BlockCopy(c1c3c2, 0, c1c2c3, 0, c1Length);
// 复制C2部分 (65+32到结束)
Buffer.BlockCopy(c1c3c2, c1Length + c3Length, c1c2c3, c1Length, c2Length);
// 复制C3部分 (65到65+32)
Buffer.BlockCopy(c1c3c2, c1Length, c1c2c3, c1Length + c2Length, c3Length);
return c1c2c3;
}
#endregion
#region 内部工具方法
private static byte[] AdjustTo32Bytes(byte[] input)
{
if (input.Length == 32) return input;
if (input.Length > 32)
throw new ArgumentException("Input too long for 32-byte adjustment");
byte[] output = new byte[32];
Buffer.BlockCopy(input, 0, output, 32 - input.Length, input.Length);
return output;
}
// === 新增方法:可靠的 DER 转裸签名 ===
private static byte[] ConvertDerToRawSignature(byte[] derSignature, int partLength)
{
Asn1Sequence seq = (Asn1Sequence)Asn1Object.FromByteArray(derSignature);
byte[] rBytes = PadLeadingZeros(((DerInteger)seq[0]).Value.ToByteArrayUnsigned(), partLength);
byte[] sBytes = PadLeadingZeros(((DerInteger)seq[1]).Value.ToByteArrayUnsigned(), partLength);
byte[] rawSig = new byte[partLength * 2];
Buffer.BlockCopy(rBytes, 0, rawSig, 0, partLength);
Buffer.BlockCopy(sBytes, 0, rawSig, partLength, partLength);
return rawSig;
}
// === 新增方法:安全的填充处理 ===
private static byte[] PadLeadingZeros(byte[] input, int requiredLength)
{
if (input.Length == requiredLength)
return input;
if (input.Length > requiredLength)
throw new InvalidOperationException("Input longer than required length");
byte[] padded = new byte[requiredLength];
int offset = requiredLength - input.Length;
Buffer.BlockCopy(input, 0, padded, offset, input.Length);
return padded;
}
// === 新增方法:可靠的裸签名转 DER ===
private static byte[] ConvertRawToDerSignature(byte[] rawSignature, int partLength)
{
if (rawSignature.Length != partLength * 2)
throw new ArgumentException("Invalid raw signature length");
byte[] rBytes = new byte[partLength];
byte[] sBytes = new byte[partLength];
Buffer.BlockCopy(rawSignature, 0, rBytes, 0, partLength);
Buffer.BlockCopy(rawSignature, partLength, sBytes, 0, partLength);
// 移除前导零(匹配 Java 行为)
BigInteger r = new BigInteger(1, rBytes);
BigInteger s = new BigInteger(1, sBytes);
Asn1EncodableVector v = new Asn1EncodableVector();
v.Add(new DerInteger(r));
v.Add(new DerInteger(s));
return new DerSequence(v).GetEncoded();
}
#endregion
}
}