基于BouncyCastle的国密SM2、SM3、SM4算法C#语言实现

【为什么写这篇文章】

  1. 关于国密SM2、SM3、SM4算法的文章,网上非常多,但是时间先后不一,知识点也非常散落,不便于理解和掌握。
  2. 作为普通的编程爱好者,自己的水平也就一般般,更多关心的是怎么使用;对于算法原理,知道个大概即可

【需要引用的Nuget包】

本次实现以WPF类库为基础,需要额外引用的Nuget包,就一个:BouncyCastle.Cryptography (2.4.0)

【3个国密算法的C#语言实现】

一、SM2

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

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

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 的组合形式。

【参考文献】

  1. 《C# SM2算法 加密,解密,签名,验签》https://www.cnblogs.com/Cxiaoao/p/15170345.html
  2.  nuget包:EasilyNET.Security (3.24) https://www.nuget.org/packages/EasilyNET.Security
  3. 《C#.NET 国密SM3withSM2签名与验签 和JAVA互通》https://www.cnblogs.com/runliuv/p/15079404.html
  4. 《国密算法—SM2介绍及基于BC的实现》https://blog.csdn.net/zcmain/article/details/114099664
  5. 《关于SM2算法 ASN.1编码 - 签名长度》https://blog.csdn.net/softt/article/details/134941449
  6. 《C#.NET 国密SM3withSM2签名与验签 和JAVA互通》https://www.cnblogs.com/runliuv/p/15079404.html
  7. 《关于SM2算法 ASN.1编码 - 签名长度》https://blog.csdn.net/softt/article/details/134941449
  8. 《C# sm3加密实现【转】Sm3Crypto ,动态库BouncyCastle.Crypto》https://blog.csdn.net/yh361/article/details/130737990
  9. 《SM3加密算法详解(2021-12-8)》https://blog.csdn.net/qq_40662424/article/details/121637732
  10. 《C# 国密加密》https://www.cnblogs.com/weidaorisun/p/16190122.html
  11. 《.NET C# 实现国密算法加解密》https://blog.csdn.net/szy13323042191/article/details/139652040
  12. 《The GmSSL Project》http://gmssl.org/docs/sm4.html

 

posted @ 2024-06-20 12:30  syzcyyx  阅读(63)  评论(0编辑  收藏  举报