国密算法GBSM2 C#实现 以及与多端联调结果

SM最近做项目用到国密算法进行加密处理, 其中SM2 对称加密算法用在了账号密码登录上面,三方(安卓.苹果,web端) 使用的加密方法实现一开始传到后端全都解不了, 因为有些规则不一样, 而这些在国密的文档里是没提到, 为了方便大家不踩坑,就记一下.

1.在后台C#这边加密后的密文是04开头的.而前端使用OPENSSL库去做的话, 加密后得到密文是有经过ASN1重新编码(没有04), 但下面的后台C#写法是没这道编码过程, 所以需要前端做一次ASN1解码再传过来

可参考链接 

(IOS-GMOjectC )

上面提到

  1. OpenSSL 所用公钥是 04 开头的,表示非压缩公钥格式,后台返回公钥可能是不带 04 的,需要手动拼接。
  2. 后台返回的解密结果可能是没有标准编码的原始密文 C1C3C2 格式,而 OpenSSL 的加解密都是需要 ASN1 编码格式,所以与后台交互过程中,可能需要 ASN1 编码解码。

(java-sm2)

然后加密后的密文拼接是 C1C3C2 (早期版本的规则是C1C2C3)

贴下后台代码: SM2要用到SM3的摘要生成方法, 原来BouncyCastle.NetCore的包里已经有GM的SM2实现的了,参考这篇博文(C# SM2), 看引用的名空间应该也有经过ASN1的. 只是一开始被web端js库(sm-crypto)带偏了,里面是没有asn1编码加工过的

SM2.cs

using System;
using System.Collections.Generic;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using System.Text;

namespace GBSMService
{
    public class GBSM2
    {
        public static GBSM2 Instance
        {
            get
            {
                return new GBSM2();
            }

        }

        public static readonly string[] sm2_param = {
            "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF",// p,0
            "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC",// a,1
            "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93",// b,2
            "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123",// n,3
            "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7",// gx,4
            "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0" // gy,5
        };

        public string[] ecc_param = sm2_param;

        public readonly BigInteger ecc_p;
        public readonly BigInteger ecc_a;
        public readonly BigInteger ecc_b;
        public readonly BigInteger ecc_n;
        public readonly BigInteger ecc_gx;
        public readonly BigInteger ecc_gy;

        public readonly ECCurve ecc_curve;
        public readonly ECPoint ecc_point_g;

        public readonly ECDomainParameters ecc_bc_spec;

        public readonly ECKeyPairGenerator ecc_key_pair_generator;

        private GBSM2()
        {
            ecc_param = sm2_param;

            ECFieldElement ecc_gx_fieldelement;
            ECFieldElement ecc_gy_fieldelement;

            ecc_p = new BigInteger(ecc_param[0], 16);
            ecc_a = new BigInteger(ecc_param[1], 16);
            ecc_b = new BigInteger(ecc_param[2], 16);
            ecc_n = new BigInteger(ecc_param[3], 16);
            ecc_gx = new BigInteger(ecc_param[4], 16);
            ecc_gy = new BigInteger(ecc_param[5], 16);


            ecc_gx_fieldelement = new FpFieldElement(ecc_p, ecc_gx);
            ecc_gy_fieldelement = new FpFieldElement(ecc_p, ecc_gy);

            ecc_curve = new FpCurve(ecc_p, ecc_a, ecc_b);
            ecc_point_g = new FpPoint(ecc_curve, ecc_gx_fieldelement, ecc_gy_fieldelement);

            ecc_bc_spec = new ECDomainParameters(ecc_curve, ecc_point_g, ecc_n);

            ECKeyGenerationParameters ecc_ecgenparam;
            ecc_ecgenparam = new ECKeyGenerationParameters(ecc_bc_spec, new SecureRandom());

            ecc_key_pair_generator = new ECKeyPairGenerator();
            ecc_key_pair_generator.Init(ecc_ecgenparam);
        }

        public virtual byte[] Sm2GetZ(byte[] userId, ECPoint userKey)
        {
            GBSM3Digest sm3 = new GBSM3Digest();
            byte[] p;
            // userId length
            int len = userId.Length * 8;
            sm3.Update((byte)(len >> 8 & 0x00ff));
            sm3.Update((byte)(len & 0x00ff));

            // userId
            sm3.BlockUpdate(userId, 0, userId.Length);

            // a,b
            p = ecc_a.ToByteArray();
            sm3.BlockUpdate(p, 0, p.Length);
            p = ecc_b.ToByteArray();
            sm3.BlockUpdate(p, 0, p.Length);
            // gx,gy
            p = ecc_gx.ToByteArray();
            sm3.BlockUpdate(p, 0, p.Length);
            p = ecc_gy.ToByteArray();
            sm3.BlockUpdate(p, 0, p.Length);

            // x,y
            p = userKey.XCoord.ToBigInteger().ToByteArray();
            sm3.BlockUpdate(p, 0, p.Length);
            p = userKey.YCoord.ToBigInteger().ToByteArray();
            sm3.BlockUpdate(p, 0, p.Length);

            // Z
            byte[] md = new byte[sm3.GetDigestSize()];
            sm3.DoFinal(md, 0);

            return md;
        }

    }

    public class GBSMCipher
    {
        private int ct = 1;


        private ECPoint p2;
        private GBSM3Digest sm3keybase;
        private GBSM3Digest sm3c3;


        private byte[] key = new byte[32];
        private byte keyOff = 0;


        public GBSMCipher()
        {
            this.ct = 1;
            this.key = new byte[32];
            this.keyOff = 0;
        }

        public static byte[] byteConvert32Bytes(BigInteger n)
        {
            byte[] tmpd = null;
            if (n == null)
            {
                return null;
            }

            if (n.ToByteArray().Length == 33)
            {
                tmpd = new byte[32];
                Array.Copy(n.ToByteArray(), 1, tmpd, 0, 32);
            }
            else if (n.ToByteArray().Length == 32)
            {
                tmpd = n.ToByteArray();
            }
            else
            {
                tmpd = new byte[32];
                for (int i = 0; i < 32 - n.ToByteArray().Length; i++)
                {
                    tmpd[i] = 0;
                }
                Array.Copy(n.ToByteArray(), 0, tmpd, 32 - n.ToByteArray().Length, n.ToByteArray().Length);
            }
            return tmpd;
        }

        private void Reset()
        {
            this.sm3keybase = new GBSM3Digest();
            this.sm3c3 = new GBSM3Digest();

            byte[] p = byteConvert32Bytes(p2.Normalize().XCoord.ToBigInteger());
            this.sm3keybase.BlockUpdate(p, 0, p.Length);
            this.sm3c3.BlockUpdate(p, 0, p.Length);

            p = byteConvert32Bytes(p2.Normalize().YCoord.ToBigInteger());
            this.sm3keybase.BlockUpdate(p, 0, p.Length);
            this.ct = 1;
            NextKey();
        }

        private void NextKey()
        {
            GBSM3Digest sm3keycur = new GBSM3Digest(this.sm3keybase);
            sm3keycur.Update((byte)(ct >> 24 & 0xff));
            sm3keycur.Update((byte)(ct >> 16 & 0xff));
            sm3keycur.Update((byte)(ct >> 8 & 0xff));
            sm3keycur.Update((byte)(ct & 0xff));
            sm3keycur.DoFinal(key, 0);
            this.keyOff = 0;
            this.ct++;
        }

        public ECPoint Init_enc(GBSM2 sm2, ECPoint userKey)
        {
            AsymmetricCipherKeyPair key = sm2.ecc_key_pair_generator.GenerateKeyPair();
            ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters)key.Private;
            ECPublicKeyParameters ecpub = (ECPublicKeyParameters)key.Public;
            BigInteger k = ecpriv.D;
            ECPoint c1 = ecpub.Q;
            this.p2 = userKey.Multiply(k);
            Reset();
            return c1;
        }

        public void Encrypt(byte[] data)
        {
            this.sm3c3.BlockUpdate(data, 0, data.Length);
            for (int i = 0; i < data.Length; i++)
            {
                if (keyOff == key.Length)
                {
                    NextKey();
                }
                data[i] ^= key[keyOff++];
            }
        }

        public void Init_dec(BigInteger userD, ECPoint c1)
        {
            this.p2 = c1.Multiply(userD);
            Reset();
        }

        public void Decrypt(byte[] data)
        {
            for (int i = 0; i < data.Length; i++)
            {
                if (keyOff == key.Length)
                {
                    NextKey();
                }
                data[i] ^= key[keyOff++];
            }

            this.sm3c3.BlockUpdate(data, 0, data.Length);
        }

        public void Dofinal(byte[] c3)
        {
            byte[] p = byteConvert32Bytes(p2.Normalize().YCoord.ToBigInteger());
            this.sm3c3.BlockUpdate(p, 0, p.Length);
            this.sm3c3.DoFinal(c3, 0);
            Reset();
        }
    }
}

SM3.cs

using System;
using System.Collections.Generic;
using System.Text;
using Org.BouncyCastle.Utilities.Encoders;
using Org.BouncyCastle.Crypto;

namespace GBSMService
{
    public abstract class GeneralDigest : IDigest
    {
        private const int BYTE_LENGTH = 64;

        private byte[] xBuf;
        private int xBufOff;

        private long byteCount;

        internal GeneralDigest()
        {
            xBuf = new byte[4];
        }

        internal GeneralDigest(GeneralDigest t)
        {
            xBuf = new byte[t.xBuf.Length];
            Array.Copy(t.xBuf, 0, xBuf, 0, t.xBuf.Length);

            xBufOff = t.xBufOff;
            byteCount = t.byteCount;
        }

        public void Update(byte input)
        {
            xBuf[xBufOff++] = input;

            if (xBufOff == xBuf.Length)
            {
                ProcessWord(xBuf, 0);
                xBufOff = 0;
            }

            byteCount++;
        }

        public void BlockUpdate(
            byte[] input,
            int inOff,
            int length)
        {
            //
            // fill the current word
            //
            while ((xBufOff != 0) && (length > 0))
            {
                Update(input[inOff]);
                inOff++;
                length--;
            }

            //
            // process whole words.
            //
            while (length > xBuf.Length)
            {
                ProcessWord(input, inOff);

                inOff += xBuf.Length;
                length -= xBuf.Length;
                byteCount += xBuf.Length;
            }

            //
            // load in the remainder.
            //
            while (length > 0)
            {
                Update(input[inOff]);

                inOff++;
                length--;
            }
        }

        public void Finish()
        {
            long bitLength = (byteCount << 3);

            //
            // add the pad bytes.
            //
            Update(unchecked((byte)128));

            while (xBufOff != 0) Update(unchecked((byte)0));
            ProcessLength(bitLength);
            ProcessBlock();
        }

        public virtual void Reset()
        {
            byteCount = 0;
            xBufOff = 0;
            Array.Clear(xBuf, 0, xBuf.Length);
        }

        public int GetByteLength()
        {
            return BYTE_LENGTH;
        }

        internal abstract void ProcessWord(byte[] input, int inOff);
        internal abstract void ProcessLength(long bitLength);
        internal abstract void ProcessBlock();
        public abstract string AlgorithmName { get; }
        public abstract int GetDigestSize();
        public abstract int DoFinal(byte[] output, int outOff);
    }

    public class SupportClass
    {
        /// <summary>
        /// Performs an unsigned bitwise right shift with the specified number
        /// </summary>
        /// <param name="number">Number to operate on</param>
        /// <param name="bits">Ammount of bits to shift</param>
        /// <returns>The resulting number from the shift operation</returns>
        public static int URShift(int number, int bits)
        {
            if (number >= 0)
                return number >> bits;
            else
                return (number >> bits) + (2 << ~bits);
        }

        /// <summary>
        /// Performs an unsigned bitwise right shift with the specified number
        /// </summary>
        /// <param name="number">Number to operate on</param>
        /// <param name="bits">Ammount of bits to shift</param>
        /// <returns>The resulting number from the shift operation</returns>
        public static int URShift(int number, long bits)
        {
            return URShift(number, (int)bits);
        }

        /// <summary>
        /// Performs an unsigned bitwise right shift with the specified number
        /// </summary>
        /// <param name="number">Number to operate on</param>
        /// <param name="bits">Ammount of bits to shift</param>
        /// <returns>The resulting number from the shift operation</returns>
        public static long URShift(long number, int bits)
        {
            if (number >= 0)
                return number >> bits;
            else
                return (number >> bits) + (2L << ~bits);
        }

        /// <summary>
        /// Performs an unsigned bitwise right shift with the specified number
        /// </summary>
        /// <param name="number">Number to operate on</param>
        /// <param name="bits">Ammount of bits to shift</param>
        /// <returns>The resulting number from the shift operation</returns>
        public static long URShift(long number, long bits)
        {
            return URShift(number, (int)bits);
        }


    }

    public class GBSM3Digest : GeneralDigest
    {


        public override string AlgorithmName
        {
            get
            {
                return "SM3";
            }

        }
        public override int GetDigestSize()
        {
            return DIGEST_LENGTH;
        }

        private const int DIGEST_LENGTH = 32;

        private static readonly int[] v0 = new int[] { 0x7380166f, 0x4914b2b9, 0x172442d7, unchecked((int)0xda8a0600), unchecked((int)0xa96f30bc), 0x163138aa, unchecked((int)0xe38dee4d), unchecked((int)0xb0fb0e4e) };

        private int[] v = new int[8];
        private int[] v_ = new int[8];

        private static readonly int[] X0 = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

        private int[] X = new int[68];
        private int xOff;

        private int T_00_15 = 0x79cc4519;
        private int T_16_63 = 0x7a879d8a;

        public GBSM3Digest()
        {
            Reset();
        }

        public GBSM3Digest(GBSM3Digest t) : base(t)
        {

            Array.Copy(t.X, 0, X, 0, t.X.Length);
            xOff = t.xOff;

            Array.Copy(t.v, 0, v, 0, t.v.Length);
        }

        public override void Reset()
        {
            base.Reset();

            Array.Copy(v0, 0, v, 0, v0.Length);

            xOff = 0;
            Array.Copy(X0, 0, X, 0, X0.Length);
        }

        internal override void ProcessBlock()
        {
            int i;

            int[] ww = X;
            int[] ww_ = new int[64];

            for (i = 16; i < 68; i++)
            {
                ww[i] = P1(ww[i - 16] ^ ww[i - 9] ^ (ROTATE(ww[i - 3], 15))) ^ (ROTATE(ww[i - 13], 7)) ^ ww[i - 6];
            }

            for (i = 0; i < 64; i++)
            {
                ww_[i] = ww[i] ^ ww[i + 4];
            }

            int[] vv = v;
            int[] vv_ = v_;

            Array.Copy(vv, 0, vv_, 0, v0.Length);

            int SS1, SS2, TT1, TT2, aaa;
            for (i = 0; i < 16; i++)
            {
                aaa = ROTATE(vv_[0], 12);
                SS1 = aaa + vv_[4] + ROTATE(T_00_15, i);
                SS1 = ROTATE(SS1, 7);
                SS2 = SS1 ^ aaa;

                TT1 = FF_00_15(vv_[0], vv_[1], vv_[2]) + vv_[3] + SS2 + ww_[i];
                TT2 = GG_00_15(vv_[4], vv_[5], vv_[6]) + vv_[7] + SS1 + ww[i];
                vv_[3] = vv_[2];
                vv_[2] = ROTATE(vv_[1], 9);
                vv_[1] = vv_[0];
                vv_[0] = TT1;
                vv_[7] = vv_[6];
                vv_[6] = ROTATE(vv_[5], 19);
                vv_[5] = vv_[4];
                vv_[4] = P0(TT2);
            }
            for (i = 16; i < 64; i++)
            {
                aaa = ROTATE(vv_[0], 12);
                SS1 = aaa + vv_[4] + ROTATE(T_16_63, i);
                SS1 = ROTATE(SS1, 7);
                SS2 = SS1 ^ aaa;

                TT1 = FF_16_63(vv_[0], vv_[1], vv_[2]) + vv_[3] + SS2 + ww_[i];
                TT2 = GG_16_63(vv_[4], vv_[5], vv_[6]) + vv_[7] + SS1 + ww[i];
                vv_[3] = vv_[2];
                vv_[2] = ROTATE(vv_[1], 9);
                vv_[1] = vv_[0];
                vv_[0] = TT1;
                vv_[7] = vv_[6];
                vv_[6] = ROTATE(vv_[5], 19);
                vv_[5] = vv_[4];
                vv_[4] = P0(TT2);
            }
            for (i = 0; i < 8; i++)
            {
                vv[i] ^= vv_[i];
            }

            // Reset
            xOff = 0;
            Array.Copy(X0, 0, X, 0, X0.Length);
        }

        internal override void ProcessWord(byte[] in_Renamed, int inOff)
        {
            int n = in_Renamed[inOff] << 24;
            n |= (in_Renamed[++inOff] & 0xff) << 16;
            n |= (in_Renamed[++inOff] & 0xff) << 8;
            n |= (in_Renamed[++inOff] & 0xff);
            X[xOff] = n;

            if (++xOff == 16)
            {
                ProcessBlock();
            }
        }

        internal override void ProcessLength(long bitLength)
        {
            if (xOff > 14)
            {
                ProcessBlock();
            }

            X[14] = (int)(SupportClass.URShift(bitLength, 32));
            X[15] = (int)(bitLength & unchecked((int)0xffffffff));
        }

        public static void IntToBigEndian(int n, byte[] bs, int off)
        {
            bs[off] = (byte)(SupportClass.URShift(n, 24));
            bs[++off] = (byte)(SupportClass.URShift(n, 16));
            bs[++off] = (byte)(SupportClass.URShift(n, 8));
            bs[++off] = (byte)(n);
        }

        public override int DoFinal(byte[] out_Renamed, int outOff)
        {
            Finish();

            for (int i = 0; i < 8; i++)
            {
                IntToBigEndian(v[i], out_Renamed, outOff + i * 4);
            }

            Reset();

            return DIGEST_LENGTH;
        }

        private int ROTATE(int x, int n)
        {
            return (x << n) | (SupportClass.URShift(x, (32 - n)));
        }

        private int P0(int X)
        {
            return ((X) ^ ROTATE((X), 9) ^ ROTATE((X), 17));
        }

        private int P1(int X)
        {
            return ((X) ^ ROTATE((X), 15) ^ ROTATE((X), 23));
        }

        private int FF_00_15(int X, int Y, int Z)
        {
            return (X ^ Y ^ Z);
        }

        private int FF_16_63(int X, int Y, int Z)
        {
            return ((X & Y) | (X & Z) | (Y & Z));
        }

        private int GG_00_15(int X, int Y, int Z)
        {
            return (X ^ Y ^ Z);
        }

        private int GG_16_63(int X, int Y, int Z)
        {
            return ((X & Y) | (~X & Z));
        }
    }
}

调用代码, 解密时如果密文是04 开头的, 截取 前130位当C1(不是04开头的截取前128位), 中间64位当C3 (固定32byte的摘要长度),其余的为C2进行解密

using Org.BouncyCastle.Math;
using Org.BouncyCastle.Utilities.Encoders;
namespace GBSMService
{
    public class EncryptionService
    {
        //public 公钥048f98e876afd3e26bbccfcc204637cb884fa988d5b30cb59aa16dffeb799919d281b38782cfec2560fb15317697724d1203ee2911a2ef4c1ab1f36194b2364e28
        //私钥
        private const string KSm2Key = "de5f142f987ea5989b9a249833c80b4a9626a0b87ddd4ab42daca55328632073";

        /// <summary>
        /// 国密SM2解密
        /// </summary>
        /// <param name="inputString">C1C3C2</param>
        /// <returns></returns>
        public static string GBSm2Decrypt(string inputString)
        {
            string plainText = "";
            if (inputString == null || inputString.Length == 0)
            {
                return plainText;
            }
            try
            {
                byte[] privateKey = Hex.Decode(KSm2Key);
                byte[] encryptedData = Hex.Decode(inputString);
                String data = Encoding.UTF8.GetString(Hex.Encode(encryptedData));

                byte[] c1Bytes = Hex.Decode(Encoding.UTF8.GetBytes(data.Substring(0, 130)));
                byte[] c3 = Hex.Decode(Encoding.UTF8.GetBytes(data.Substring(130, 64)));
                byte[] c2 = Hex.Decode(Encoding.UTF8.GetBytes(data.Substring(194)));

                GBSM2 sm2 = GBSM2.Instance;
                BigInteger userD = new BigInteger(1, privateKey);

                Org.BouncyCastle.Math.EC.ECPoint c1 = sm2.ecc_curve.DecodePoint(c1Bytes);
                GBSMCipher cipher = new GBSMCipher();
                cipher.Init_dec(userD, c1);
                cipher.Decrypt(c2);
                cipher.Dofinal(c3);
                plainText = Encoding.Default.GetString(c2);
            }
            catch(Exception ex)
            {
                plainText = "";
            }
            return plainText;
        }

        /// <summary>
        /// 国密SM2加密
        /// </summary>
        /// <param name="pk"></param>
        /// <param name="oriText"></param>
        /// <returns>C1C3C2</returns>
        public static string GBSm2Encrypt(string pk, string oriText)
        {
            byte[] publicKey = Hex.Decode(pk);
                byte[] data = Encoding.Default.GetBytes(oriText);
            byte[] source = new byte[data.Length];
            Array.Copy(data, 0, source, 0, data.Length);

            GBSMCipher cipher = new GBSMCipher();
            GBSM2 sm2 = GBSM2.Instance;

            Org.BouncyCastle.Math.EC.ECPoint userKey = sm2.ecc_curve.DecodePoint(publicKey);

            Org.BouncyCastle.Math.EC.ECPoint c1 = cipher.Init_enc(sm2, userKey);
            cipher.Encrypt(source);

            byte[] c3 = new byte[32];
            cipher.Dofinal(c3);

            String sc1 = Encoding.UTF8.GetString(Hex.Encode(c1.GetEncoded()));
            String sc2 = Encoding.UTF8.GetString(Hex.Encode(source));
            String sc3 = Encoding.UTF8.GetString(Hex.Encode(c3));
            //去掉04开头
            return (sc1 + sc3 + sc2).ToUpper();
        }
    }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值