【为什么写这篇文章】
- 关于国密SM2、SM3、SM4算法的文章,网上非常多,但是时间先后不一,知识点也非常散落,不便于理解和掌握。
- 作为普通的编程爱好者,自己的水平也就一般般,更多关心的是怎么使用;对于算法原理,知道个大概即可
【需要引用的Nuget包】
本次实现以WPF类库为基础,需要额外引用的Nuget包,就一个:BouncyCastle.Cryptography (2.4.0)
【3个国密算法的C#语言实现】
一、SM2
public class MySM2v2
{
private static readonly X9ECParameters x9 = GMNamedCurves.GetByName("SM2P256V1");
private const int RS_LEN = 32;
#region ASN.1 DER编码格式转换
public static byte[] RsAsn1ToPlainByteArray(byte[] signDer)
{
Asn1Sequence seq = Asn1Sequence.GetInstance(signDer);
byte[] r = BigIntToFixexLengthBytes(DerInteger.GetInstance(seq[0]).Value);
byte[] s = BigIntToFixexLengthBytes(DerInteger.GetInstance(seq[1]).Value);
byte[] result = new byte[RS_LEN * 2];
Buffer.BlockCopy(r, 0, result, 0, r.Length);
Buffer.BlockCopy(s, 0, result, RS_LEN, s.Length);
return result;
}
public static byte[] RsAsn1FromPlainByteArray(byte[] sign)
{
if (sign.Length != RS_LEN * 2) throw new ArgumentException("err rs. ");
BigInteger r = new(1, Arrays.CopyOfRange(sign, 0, RS_LEN));
BigInteger s = new(1, Arrays.CopyOfRange(sign, RS_LEN, RS_LEN * 2));
Asn1EncodableVector v = [new DerInteger(r), new DerInteger(s)];
try
{
return new DerSequence(v).GetEncoded("DER");
}
catch (IOException e)
{
return [];
}
}
private static byte[] BigIntToFixexLengthBytes(BigInteger rOrS)
{
byte[] rs = rOrS.ToByteArray();
if (rs.Length == RS_LEN) return rs;
else if (rs.Length == RS_LEN + 1 && rs[0] == 0) return Arrays.CopyOfRange(rs, 1, RS_LEN + 1);
else if (rs.Length < RS_LEN)
{
byte[] result = new byte[RS_LEN];
Arrays.Fill(result, (byte)0);
Buffer.BlockCopy(rs, 0, result, RS_LEN - rs.Length, rs.Length);
return result;
}
else
{
throw new ArgumentException("err rs: " + Hex.ToHexString(rs));
}
}
#endregion
#region 密钥生成
public static (byte[] PublicKey, byte[] PrivateKey) SM2KeyGen()
{
ECKeyPairGenerator eCKeyPairGenerator = new();
eCKeyPairGenerator.Init(new ECKeyGenerationParameters(new ECDomainParameters(x9), new SecureRandom()));
AsymmetricCipherKeyPair asymmetricCipherKeyPair = eCKeyPairGenerator.GenerateKeyPair();
var publicKey = ((ECPublicKeyParameters)asymmetricCipherKeyPair.Public).Q.GetEncoded(compressed: false);
var privateKey = ((ECPrivateKeyParameters)asymmetricCipherKeyPair.Private).D.ToByteArray();
return (publicKey, privateKey);
}
#endregion
#region 签名和验证
public static byte[] SM2Sign(byte[] data, byte[] privateKey, byte[]? userId = null)
{
SM2Signer sM2Signer = new(new SM3Digest());
ICipherParameters parameters = new ParametersWithRandom(new ECPrivateKeyParameters(new BigInteger(1, privateKey), new ECDomainParameters(x9)));
if (userId != null)
{
parameters = new ParametersWithID(parameters, userId);
}
sM2Signer.Init(forSigning: true, parameters);
sM2Signer.BlockUpdate(data, 0, data.Length);
var dataSignedDer = sM2Signer.GenerateSignature();
var dataSigned = RsAsn1ToPlainByteArray(dataSignedDer);
return dataSigned;
}
public static bool SM2VerifySign(byte[] data, byte[] publicKey, byte[] dataSigned, byte[]? userId = null)
{
var dataSignedDer = RsAsn1FromPlainByteArray(dataSigned);
SM2Signer sM2Signer = new(new SM3Digest());
ICipherParameters parameters = new ECPublicKeyParameters(x9.Curve.DecodePoint(publicKey), new ECDomainParameters(x9));
if (userId != null)
{
parameters = new ParametersWithID(parameters, userId);
}
sM2Signer.Init(forSigning: false, parameters);
sM2Signer.BlockUpdate(data, 0, data.Length);
return sM2Signer.VerifySignature(dataSignedDer);
}
public static byte[] SM2SignByDataHash(byte[] data, byte[] privateKey, byte[]? userId = null)
{
var dataHash = MySM3.SM3HashData(data);
SM2Signer sM2Signer = new(new SM3Digest());
ICipherParameters parameters = new ParametersWithRandom(new ECPrivateKeyParameters(new BigInteger(1, privateKey), new ECDomainParameters(x9)));
if (userId != null)
{
parameters = new ParametersWithID(parameters, userId);
}
sM2Signer.Init(forSigning: true, parameters);
sM2Signer.BlockUpdate(dataHash, 0, dataHash.Length);
var dataSignedDer = sM2Signer.GenerateSignature();
var dataSigned = RsAsn1ToPlainByteArray(dataSignedDer);
return dataSigned;
}
public static bool SM2VerifySignByDataHash(byte[] data, byte[] publicKey, byte[] dataSigned, byte[]? userId = null)
{
var dataSignedDer = RsAsn1FromPlainByteArray(dataSigned);
var dataHash = MySM3.SM3HashData(data);
SM2Signer sM2Signer = new(new SM3Digest());
ICipherParameters parameters = new ECPublicKeyParameters(x9.Curve.DecodePoint(publicKey), new ECDomainParameters(x9));
if (userId != null)
{
parameters = new ParametersWithID(parameters, userId);
}
sM2Signer.Init(forSigning: false, parameters);
sM2Signer.BlockUpdate(dataHash, 0, dataHash.Length);
return sM2Signer.VerifySignature(dataSignedDer);
}
#endregion
#region 加密和解密
public static byte[] SM2Encrypt(byte[] data, byte[] publicKey, byte[]? userId = null, SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2)
{
SM2Engine sM2Engine = new(new SM3Digest(), SM2Engine.Mode.C1C3C2);
ICipherParameters cipherParameters = new ParametersWithRandom(new ECPublicKeyParameters(x9.Curve.DecodePoint(publicKey), new ECDomainParameters(x9)));
if (userId != null)
{
cipherParameters = new ParametersWithID(cipherParameters, userId);
}
sM2Engine.Init(forEncryption: true, cipherParameters);
data = sM2Engine.ProcessBlock(data, 0, data.Length);
if (mode == SM2Engine.Mode.C1C2C3)
{
data = C132ToC123(data);
}
return data;
}
public static byte[] SM2Decrypt(byte[] dataEncrypted, byte[] privateKey, byte[]? userId = null, SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2)
{
if (mode == SM2Engine.Mode.C1C2C3)
{
dataEncrypted = C123ToC132(dataEncrypted);
}
SM2Engine sM2Engine = new(new SM3Digest(), SM2Engine.Mode.C1C3C2);
ICipherParameters cipherParameters = new ECPrivateKeyParameters(new BigInteger(1, privateKey), new ECDomainParameters(x9));
if (userId != null)
{
cipherParameters = new ParametersWithID(cipherParameters, userId);
}
sM2Engine.Init(forEncryption: false, cipherParameters);
return sM2Engine.ProcessBlock(dataEncrypted, 0, dataEncrypted.Length);
}
private static byte[] C123ToC132(byte[] c1c2c3)
{
int num = (x9.Curve.FieldSize + 7 >> 3 << 1) + 1;
byte[] array = new byte[c1c2c3.Length];
Array.Copy(c1c2c3, 0, array, 0, num);
Array.Copy(c1c2c3, c1c2c3.Length - 32, array, num, 32);
Array.Copy(c1c2c3, num, array, num + 32, c1c2c3.Length - num - 32);
return array;
}
private static byte[] C132ToC123(byte[] c1c3c2)
{
int num = (x9.Curve.FieldSize + 7 >> 3 << 1) + 1;
byte[] array = new byte[c1c3c2.Length];
Array.Copy(c1c3c2, 0, array, 0, num);
Array.Copy(c1c3c2, num + 32, array, num, c1c3c2.Length - num - 32);
Array.Copy(c1c3c2, num, array, c1c3c2.Length - 32, 32);
return array;
}
#endregion
}
二、SM3
public class MySM3
{
public static byte[] SM3KeyGen(int keySizeBit = 512)
{
int keySize = keySizeBit / 8;
var keyRandom = RandomNumberGenerator.GetBytes(keySize);
return keyRandom;
}
public static byte[] SM3HashData(byte[] data)
{
SM3Digest sm3 = new();
sm3.BlockUpdate(data, 0, data.Length);
byte[] md = new byte[sm3.GetDigestSize()];
sm3.DoFinal(md, 0);
return md;
}
public static byte[] SM3HashData(byte[] data, byte[] key)
{
SM3Digest sm3 = new();
HMac mac = new(sm3);
KeyParameter keyParameter = new(key);
mac.Init(keyParameter);
mac.BlockUpdate(data, 0, data.Length);
byte[] result = new byte[mac.GetMacSize()];
mac.DoFinal(result, 0);
return result;
}
}
三、SM4
public class MySM4
{
public static (byte[] Key, byte[] IV) SM4KeyGen(string password, int keySizeBit = 128, byte[]? salts = null)
{
byte[] salt = salts ?? [0x31, 0x33, 0x35, 0x37, 0x32, 0x34, 0x36, 0x38];
if (salt.Length % 8 == 0 && salt.Length >= 8)
{ }
else
{ throw new ArgumentException("加盐数组的长度必须为8的倍数", nameof(salts)); }
int myIterations = 1000;
Rfc2898DeriveBytes keyBytesGen = new(password, salt, myIterations, HashAlgorithmName.SHA256);
int keyLen = keySizeBit / 8;
byte[] key = new byte[keyLen];
byte[] iv = new byte[16];
key = keyBytesGen.GetBytes(key.Length);
iv = keyBytesGen.GetBytes(iv.Length);
return (Key: key, IV: iv);
}
public static byte[] SM4CBCEncrypt(byte[] data, byte[] key, byte[] iv)
{
var engine = new SM4Engine();
var blockCipher = new CbcBlockCipher(engine);
var cipher = new PaddedBufferedBlockCipher(blockCipher);
cipher.Init(true, new ParametersWithIV(new KeyParameter(key), iv));
return cipher.DoFinal(data);
}
public static byte[] SM4CBCDecrypt(byte[] dataEncrypted, byte[] key, byte[] iv)
{
var engine = new SM4Engine();
var blockCipher = new CbcBlockCipher(engine);
var cipher = new PaddedBufferedBlockCipher(blockCipher);
cipher.Init(false, new ParametersWithIV(new KeyParameter(key), iv));
return cipher.DoFinal(dataEncrypted);
}
}
【补充说明】
SM2算法安全性相当于RSA-3072
SM3算法安全性相当于SHA-256
SM4算法的安全性相当于AES-128
在日常使用中,推荐使用 SM2 + AES-256 这样的组合形式代替 RSA-2048 + AES-256 的组合形式。
【参考文献】
- 《C# SM2算法 加密,解密,签名,验签》https://www.cnblogs.com/Cxiaoao/p/15170345.html
- nuget包:EasilyNET.Security (3.24) NuGet Gallery | EasilyNET.Security 3.24.613.115
- 《C#.NET 国密SM3withSM2签名与验签 和JAVA互通》https://www.cnblogs.com/runliuv/p/15079404.html
- 《国密算法—SM2介绍及基于BC的实现》国密算法—SM2介绍及基于BC的实现_sm2不同版本的bc包1.57 和1.64-CSDN博客
- 《关于SM2算法 ASN.1编码 - 签名长度》关于SM2算法 ASN.1编码 - 签名长度_sm2签名长度-CSDN博客
- 《C#.NET 国密SM3withSM2签名与验签 和JAVA互通》https://www.cnblogs.com/runliuv/p/15079404.html
- 《关于SM2算法 ASN.1编码 - 签名长度》关于SM2算法 ASN.1编码 - 签名长度_sm2签名长度-CSDN博客
- 《C# sm3加密实现【转】Sm3Crypto ,动态库BouncyCastle.Crypto》C# sm3加密实现【转】Sm3Crypto ,动态库BouncyCastle.Crypto_c# sm3 签名-CSDN博客
- 《SM3加密算法详解(2021-12-8)》SM3加密算法详解(2021-12-8)-CSDN博客
- 《C# 国密加密》https://www.cnblogs.com/weidaorisun/p/16190122.html
- 《.NET C# 实现国密算法加解密》.NET C# 实现国密算法加解密-CSDN博客
- 《The GmSSL Project》国密SM4/SMS4分组密码