C# .NET 云南农信国密签名(SM2)简要解析

BouncyCastle库(BC库)与云南农信最大的区别是 :

BC库 SM2Signer.Init()  方法比云南农信多了最后3行代码:

digest.Reset();
            z = GetZ(userID);

            digest.BlockUpdate(z, 0, z.Length);

云南农信这3行是没有的。

多了这3行会导致云南农信,验证签名不同过。

我们需要新写一个SM2Signer名为:SM2Signer1,继承 ISigner,然后在Init 方法里去掉这3行。

我觉得原有的 GenerateSignature() 对 r,s 的包装太复杂,就新写了个 GenerateSignatureWay2()。

云南农信的JAVA DEMO 中是先用 SHA256 HASH,注释中说是再用 SM3withSM2 签名。但看起来,直接就是 SHA256withSM2 签名,不是 (SHA256 HASH  +  SM3withSM2)。

Nuget 中安装 BouncyCastle 库,1.8.9 及以上版本。

SM2Signer1 类:

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Math.EC.Multiplier;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.Utilities.Encoders;
using System;

namespace TheSM2.Utils
{
    public class SM2Signer1 : ISigner
    {

        private readonly IDsaKCalculator kCalculator = new RandomDsaKCalculator();

        private readonly IDigest digest;

        private readonly IDsaEncoding encoding;

        private ECDomainParameters ecParams;

        private ECPoint pubPoint;

        private ECKeyParameters ecKey;

        private byte[] z;

        public virtual string AlgorithmName
        {
            get { return "SM2Sign"; }
        }

        public SM2Signer1(IDsaEncoding encoding, IDigest digest)
        {
            this.encoding = encoding;
            this.digest = digest;
        }

        public SM2Signer1(IDigest digest)
            : this(StandardDsaEncoding.Instance, digest)
        {
        }

        public virtual void Init(bool forSigning, ICipherParameters parameters)
        {
            byte[] userID;

            ICipherParameters cipherParameters;
            if (parameters is ParametersWithID)
            {
                cipherParameters = ((ParametersWithID)parameters).Parameters;
                userID = ((ParametersWithID)parameters).GetID();
                if (((ParametersWithID)parameters).GetID().Length >= 8192)
                {
                    throw new ArgumentException("SM2 user ID must be less than 2^16 bits long");
                }
            }
            else
            {
                cipherParameters = parameters;
                userID = Hex.DecodeStrict("1234567812345678");
            }

            if (forSigning)
            {
                if (cipherParameters is ParametersWithRandom)
                {
                    ParametersWithRandom parametersWithRandom = (ParametersWithRandom)cipherParameters;
                    ecKey = (ECKeyParameters)parametersWithRandom.Parameters;
                    ecParams = ecKey.Parameters;
                    kCalculator.Init(ecParams.N, parametersWithRandom.Random);
                }
                else
                {
                    ecKey = (ECKeyParameters)cipherParameters;
                    ecParams = ecKey.Parameters;
                    kCalculator.Init(ecParams.N, new SecureRandom());
                }
                pubPoint = CreateBasePointMultiplier().Multiply(ecParams.G, ((ECPrivateKeyParameters)ecKey).D).Normalize();
            }

            else
            {
                ecKey = (ECKeyParameters)cipherParameters;
                ecParams = ecKey.Parameters;
                pubPoint = ((ECPublicKeyParameters)ecKey).Q;
            }


            //BC的 SM2Signer 是带下面3行的。而云南农信则不带。
            //digest.Reset();
            //z = GetZ(userID);
            //digest.BlockUpdate(z, 0, z.Length);
        }

        protected virtual ECMultiplier CreateBasePointMultiplier()
        {
            return new FixedPointCombMultiplier();
        }

        public virtual void Update(byte b)
        {
            digest.Update(b);
        }
        public virtual void BlockUpdate(byte[] buf, int off, int len)
        {
            digest.BlockUpdate(buf, off, len);
        }
        protected virtual BigInteger CalculateE(BigInteger n, byte[] message)
        {
            // TODO Should hashes larger than the order be truncated as with ECDSA?
            return new BigInteger(1, message);
        }


        /// <summary>
        /// 返回了N,r,s 并用StandardDsaEncoding 做了包装。需要反向解析出 r,s 。
        /// </summary>
        /// <returns></returns>
        /// <exception cref="CryptoException"></exception>
        public virtual byte[] GenerateSignature()
        {
            byte[] eHash = DigestUtilities.DoFinal(digest);

            BigInteger n = ecParams.N;
            BigInteger e = CalculateE(n, eHash);
            BigInteger d = ((ECPrivateKeyParameters)ecKey).D;

            BigInteger r, s;

            ECMultiplier basePointMultiplier = CreateBasePointMultiplier();

            // 5.2.1 Draft RFC:  SM2 Public Key Algorithms
            do // generate s
            {
                BigInteger k;
                do // generate r
                {
                    // A3
                    k = kCalculator.NextK();

                    // A4
                    ECPoint p = basePointMultiplier.Multiply(ecParams.G, k).Normalize();

                    // A5
                    r = e.Add(p.AffineXCoord.ToBigInteger()).Mod(n);
                }
                while (r.SignValue == 0 || r.Add(k).Equals(n));

                // A6
                BigInteger dPlus1ModN = BigIntegers.ModOddInverse(n, d.Add(BigIntegers.One));

                s = k.Subtract(r.Multiply(d)).Mod(n);
                s = dPlus1ModN.Multiply(s).Mod(n);
            }
            while (s.SignValue == 0);

            // A7
            try
            {
                return encoding.Encode(ecParams.N, r, s);
            }
            catch (Exception ex)
            {
                throw new CryptoException("unable to encode signature: " + ex.Message, ex);
            }
        }

        private bool VerifySignature(BigInteger r, BigInteger s)
        {
            BigInteger n = ecParams.N;

            // 5.3.1 Draft RFC:  SM2 Public Key Algorithms
            // B1
            if (r.CompareTo(BigInteger.One) < 0 || r.CompareTo(n) >= 0)
                return false;

            // B2
            if (s.CompareTo(BigInteger.One) < 0 || s.CompareTo(n) >= 0)
                return false;

            // B3
            byte[] eHash = DigestUtilities.DoFinal(digest);

            // B4
            BigInteger e = CalculateE(n, eHash);

            // B5
            BigInteger t = r.Add(s).Mod(n);
            if (t.SignValue == 0)
                return false;

            // B6
            ECPoint q = ((ECPublicKeyParameters)ecKey).Q;
            ECPoint x1y1 = ECAlgorithms.SumOfTwoMultiplies(ecParams.G, s, q, t).Normalize();
            if (x1y1.IsInfinity)
                return false;

            // B7
            return r.Equals(e.Add(x1y1.AffineXCoord.ToBigInteger()).Mod(n));
        }

        public virtual bool VerifySignature(byte[] signature)
        {
            try
            {
                BigInteger[] rs = encoding.Decode(ecParams.N, signature);

                return VerifySignature(rs[0], rs[1]);
            }
            catch (Exception)
            {
            }

            return false;
        }

        public virtual void Reset()
        {
            if (z != null)
            {
                digest.Reset();
                digest.BlockUpdate(z, 0, z.Length);
            }
        }

        private byte[] GetZ(byte[] userID)
        {
            AddUserID(digest, userID);

            AddFieldElement(digest, ecParams.Curve.A);
            AddFieldElement(digest, ecParams.Curve.B);
            AddFieldElement(digest, ecParams.G.AffineXCoord);
            AddFieldElement(digest, ecParams.G.AffineYCoord);
            AddFieldElement(digest, pubPoint.AffineXCoord);
            AddFieldElement(digest, pubPoint.AffineYCoord);

            return DigestUtilities.DoFinal(digest);
        }
        private void AddUserID(IDigest digest, byte[] userID)
        {
            int len = userID.Length * 8;
            digest.Update((byte)(len >> 8));
            digest.Update((byte)len);
            digest.BlockUpdate(userID, 0, userID.Length);
        }
        private void AddFieldElement(IDigest digest, ECFieldElement v)
        {
            byte[] p = v.GetEncoded();
            digest.BlockUpdate(p, 0, p.Length);
        }

        /// <summary>
        /// 直接返回 R,S BigInteger数组
        /// </summary>
        /// <returns></returns>
        /// <exception cref="CryptoException"></exception>
        public virtual BigInteger[] GenerateSignatureWay2()
        {
            byte[] eHash = DigestUtilities.DoFinal(digest);

            BigInteger n = ecParams.N;
            BigInteger e = CalculateE(n, eHash);
            BigInteger d = ((ECPrivateKeyParameters)ecKey).D;

            BigInteger r, s;

            ECMultiplier basePointMultiplier = CreateBasePointMultiplier();

            // 5.2.1 Draft RFC:  SM2 Public Key Algorithms
            do // generate s
            {
                BigInteger k;
                do // generate r
                {
                    // A3
                    k = kCalculator.NextK();

                    // A4
                    ECPoint p = basePointMultiplier.Multiply(ecParams.G, k).Normalize();

                    // A5
                    r = e.Add(p.AffineXCoord.ToBigInteger()).Mod(n);
                }
                while (r.SignValue == 0 || r.Add(k).Equals(n));

                // A6
                BigInteger dPlus1ModN = BigIntegers.ModOddInverse(n, d.Add(BigIntegers.One));

                s = k.Subtract(r.Multiply(d)).Mod(n);
                s = dPlus1ModN.Multiply(s).Mod(n);
            }
            while (s.SignValue == 0);

            // A7
            try
            {
                //return encoding.Encode(ecParams.N, r, s);
                BigInteger[] biRS = new BigInteger[] { r,s};
                return biRS;
            }
            catch (Exception ex)
            {
                throw new CryptoException("unable to encode signature: " + ex.Message, ex);
            }
        }

    }
}

SM2SignUtil 类:

using Org.BouncyCastle.Asn1.GM;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.Encoders;
using System;
using System.Text;

namespace TheSM2.Utils
{
    public class SM2SignUtil
    {
        public static string SM2Sign(string data,string userId,string priKey)
        {            

            byte[] msgBytes = Encoding.UTF8.GetBytes(data);             

            byte[] byUserId = Encoding.UTF8.GetBytes(userId);

            ICipherParameters param = getCipherParam(priKey, userId);

            Sha256Digest sha256Digest = new Sha256Digest();

            //SM2Signer signer = new SM2Signer(sha256Digest); // BC 自带
            SM2Signer1 signer = new SM2Signer1(sha256Digest); // 参照云南农信

            //BouncyCastle SM2Signer 自带的Init 方法比 云南农信JAVA 代码多了3行,需要新写一个类 继承ISigner,去掉那3行代码,否则云南农信验证签名不通过。
            signer.Init(true, param);
            signer.BlockUpdate(msgBytes, 0, msgBytes.Length);

            //GenerateSignature 返回值用StandardDsaEncoding做了包装,包含 N、r、s 3个值。需要配合decodeSignResNRS2RS 反向解析出r、s,再拼接。
            var generateSignature = signer.GenerateSignature();

            var sign = decodeSignResNRS2RS(generateSignature);
             return sign;
        }

        /// <summary>
        /// 只是对signer.GenerateSignature()方法返回值做了改动,直接返回BigInteger 数组。
        /// </summary>
        /// <param name="data"></param>
        /// <param name="userId"></param>
        /// <param name="priKey"></param>
        /// <returns></returns>
        public static string SM2SignWay2(string data, string userId, string priKey)
        {

            byte[] msgBytes = Encoding.UTF8.GetBytes(data);

            byte[] byUserId = Encoding.UTF8.GetBytes(userId);

            ICipherParameters param = getCipherParam(priKey, userId);

            Sha256Digest sha256Digest = new Sha256Digest();

            //SM2Signer signer = new SM2Signer(sha256Digest); // BC 自带
            SM2Signer1 signer = new SM2Signer1(sha256Digest); // 参照云南农信

            // BouncyCastle SM2Signer 自带的Init 方法比 云南农信JAVA 代码多了3行,需要新写一个类 继承ISigner,去掉那3行代码,否则云南农信验证签名不通过。
            signer.Init(true, param);
            signer.BlockUpdate(msgBytes, 0, msgBytes.Length);

            var bigIntegers = signer.GenerateSignatureWay2();

            byte[] rBytes = modifyRSFixedBytes(bigIntegers[0].ToByteArray());
            byte[] sBytes = modifyRSFixedBytes(bigIntegers[1].ToByteArray());

            byte[] signBytes = concatenate(rBytes, sBytes);
            String sign = Hex.ToHexString(signBytes).ToUpper();
            return sign;
        }

        public static ICipherParameters getCipherParam(String privateKey, String userId)
        {
            // 反序列化私钥
            ParametersWithRandom parameters = getParameterRandom(privateKey);
            if (userId != null)
            {
                ICipherParameters param = new ParametersWithID(parameters, Encoding.UTF8.GetBytes(userId));
                return param;
            }
            return parameters;
        }

        private static ParametersWithRandom getParameterRandom(String privateKey)
        {
            BigInteger privateKeyD = new BigInteger(privateKey, 16);
            X9ECParameters sm2ECParameters = GMNamedCurves.GetByName("sm2p256v1");
            // //构造domain参数
            ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.Curve, sm2ECParameters.G,
                    sm2ECParameters.N);
            ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);
            ParametersWithRandom parameters = new ParametersWithRandom(privateKeyParameters,
                    SecureRandom.GetInstance("SHA1PRNG"));
            ICipherParameters param = parameters;
            return parameters;
        }

        public static String decodeSignResNRS2RS(byte[] generateSignature)


        {
            X9ECParameters sm2ECParameters = GMNamedCurves.GetByName("sm2p256v1");
            StandardDsaEncoding dsaEncoding = StandardDsaEncoding.Instance;
            BigInteger[] bigIntegers = dsaEncoding.Decode(sm2ECParameters.N, generateSignature);
            // BigInteger[] bigIntegers = sm2Signer.generateSignature(message);

            byte[] rBytes = modifyRSFixedBytes(bigIntegers[0].ToByteArray());
            byte[] sBytes = modifyRSFixedBytes(bigIntegers[1].ToByteArray());

            byte[] signBytes = concatenate(rBytes, sBytes);
            String sign = Hex.ToHexString(signBytes).ToUpper();
            return sign;
        }

        private static byte[] modifyRSFixedBytes(byte[] rs)
        {
            int length = rs.Length;
            int fixedLength = 32;
            byte[] result = new byte[fixedLength];
            if (length < 32)
            {
                Array.Copy(rs, 0, result, fixedLength - length, length);
            }
            else
            {
                Array.Copy(rs, length - fixedLength, result, 0, fixedLength);
            }
            return result;
        }

        public static byte[] concatenate(byte[] var0, byte[] var1)
        {
            byte[] var2 = new byte[var0.Length + var1.Length];
            Array.Copy(var0, 0, var2, 0, var0.Length);
            Array.Copy(var1, 0, var2, var0.Length, var1.Length);
            return var2;
        }

    }
}

使用:

String data = "123";
                string userId = "1234567812345678";
                String priKey = "BDD9D484017EB3CB03D48579680EEF038BD7F4419980EE08AEACEA2A8A039062";

                textBox1.Text = SM2SignUtil.SM2SignWay2(data, userId, priKey);

THE END

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为你提供国密算法SM2签名和验证签名示例。以下是示例代码: 签名示例(使用Java语言): ```java import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.CryptoException; import org.bouncycastle.crypto.digests.SM3Digest; import org.bouncycastle.crypto.engines.SM2Engine; import org.bouncycastle.crypto.generators.ECKeyPairGenerator; import org.bouncycastle.crypto.params.*; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.math.BigInteger; import java.security.*; import java.security.spec.ECGenParameterSpec; public class SM2SignatureExample { public static void main(String[] args) { Security.addProvider(new BouncyCastleProvider()); try { // 生成密钥对 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC"); ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec("sm2p256v1"); keyPairGenerator.initialize(ecGenParameterSpec, new SecureRandom()); KeyPair keyPair = keyPairGenerator.generateKeyPair(); // 获取私钥和公钥 PrivateKey privateKey = keyPair.getPrivate(); PublicKey publicKey = keyPair.getPublic(); // 待签名的数据 byte[] data = "Hello, SM2!".getBytes(); // 执行签名操作 SM2Signer signer = new SM2Signer(); signer.init(true, new ParametersWithRandom(privateKey, new SecureRandom())); signer.update(data, 0, data.length); byte[] signature = signer.generateSignature(); System.out.println("签名值: " + new BigInteger(1,

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值