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

C# .NET 云南农信国密签名(SM2)简要解析的更多相关文章

  1. 国密算法SM2证书制作

    国密算法sm2非对称算法椭圆曲线 原文:http://www.jonllen.cn/jonllen/work/162.aspx 前段时间将系统的RSA算法全部升级为SM2国密算法,密码机和UKey硬件 ...

  2. C#.NET 国密SM3withSM2签名与验签 和JAVA互通

    C# 基于.NET FRAMEWORK 4.5 JAVA 基于 JDK1.8 一.要点 1.签名算法:SM3withSM2. 2.签名值byte[] 转字符串时,双方要统一,这里是BASE64. 二. ...

  3. 关于国密HTTPS 的那些事(一)

    关于国密HTTPS 的那些事(一) 随着<密码法>密码法的颁布与实施,国密的应用及推广终于有法可依.而对于应用国密其中的一个重要组成部分----国密HTTPS通信也应运而生.为了大家更好的 ...

  4. 部署国密SSL证书,如何兼容国际主流浏览器?

    国密算法在主流操作系统.浏览器等客户端中,还没有实现广泛兼容.因此,在面向开放互联网的产品应用中,国密算法无法得到广泛应用.比如,在SSL证书应用领域,由于国际主流浏览器不信任国密算法,如果服务器部署 ...

  5. SM系列国密算法(转)

    原文地址:科普一下SM系列国密算法(从零开始学区块链 189) 众所周知,为了保障商用密码的安全性,国家商用密码管理办公室制定了一系列密码标准,包括SM1(SCB2).SM2.SM3.SM4.SM7. ...

  6. 2017-2018-2 20179204《网络攻防实践》第十三周学习总结 python实现国密算法

    国密商用算法是指国密SM系列算法,包括基于椭圆曲线的非对称公钥密码SM2算法.密码杂凑SM3算法.分组密码SM4算法,还有只以IP核形式提供的非公开算法流程的对称密码SM1算法等. 第1节 SM2非对 ...

  7. 腾讯云短信 nodejs 接入, 通过验证码修改手机示例

    腾讯云短信 nodejs 接入, 通过验证码修改手机示例 参考:腾讯云短信文档国内短信快速入门qcloudsms Node.js SDK文档中心>短信>错误码 nodejs sdk 使用示 ...

  8. java实现阿里云短信服务发送验证码

    由于做项目的时候遇到了接第三方短信服务,所以记录一下. 一.新建一个maven项目并导入相关依赖 <!--手机发送短信验证码--> <dependency> <group ...

  9. Linux实现树莓派3B的国密SM9算法交叉编译——(三)国密SM9算法实现

    先参考这篇文章 Linux实现树莓派3B的国密SM9算法交叉编译——(二)miracl库的测试与静态库的生成 进行miracl库的交叉编译测试,并生成miracl静态链接库. 这篇文章主要介绍基于mi ...

  10. SM2国密证书合法性验证

    通常我们遇到过的X509证书都是基于RSA-SHA1算法的,目前国家在大力推行国密算法,未来银行发行的IC卡也都是基于PBOC3.0支持国密算法的,因此我们来学习一下如何验证SM2国密证书的合法性.至 ...

随机推荐

  1. 怀里橘猫柴犬,掌上代码江湖——对话阿里云 MVP郭旭东

    简介: 跟郭旭东聊过之后,我对程序员的敬佩又多一分.这个92年的开发者,难能可贵地兼备朝气蓬勃的技术能量与长远深刻的行业洞见.独自承担DevOps平台从0到1的所有工作,我打趣说超级开发者不过如此,他 ...

  2. dotnet 警惕 Assembly.Location 返回空

    在大部分情况下,获取当前所运行的应用程序的所在路径时,常用的就是 Assembly.Location 属性,按照之前的经验,使用 Assembly.Location 是最为稳定的做法,然而在 dotn ...

  3. dotnet 将任意时区的 DateTimeOffset 转换为中国时区时间文本

    本文告诉大家在拿到任意时区的 DateTimeOffset 对象,将 DateTimeOffset 转换为使用中国的 +8 时区表示的时间 在开始之前,需要说明的是,采用 DateTimeOffset ...

  4. Mybatis学习五($和#区别以及其他tips)

    1.$和#区别 1 #是将传入的值当做字符串的形式,eg:select id,name,age from student where id =#{id},当前端把id值1,传入到后台的时候,就相当于 ...

  5. 以对象的方式访问html中的标签,比正则表达式更好用的方式获取html中的内容,linq方式直接获取所有的链接,更加先进的c#版本爬虫开源库

    这是我本人自己写的一个开源库,现已经发布到nuget,可以直接在vs的nuget包管理中搜索到,或者可以到nuget官网下载:https://www.nuget.org/packages/ZmjCon ...

  6. 01.windows 环境设置

    windows下可以安装Git工具, 使用git bash操作 Windows 10 环境下,通过-/.bash_profile 设置 git bash 别名: 打开 git bash,需切换到当前用 ...

  7. 03 elasticsearch学习笔记-IK分词器?

    目录 1. 什么是IK分词器 2. 下载IK分词器 3. 使用kibana测试! 4. 创建自定义词典 5. Analysis 1. 什么是IK分词器 2. 下载IK分词器 下载地址,版本要和ES的版 ...

  8. ansible(7)--ansible的file模块

    1. file模块 功能:为被控端创建文件或目录,设定权限属性: 主要参数如下: 参数 说明 path 指定远程服务器的路径,也可以写成'dest','name' state 状态,可以将值设定为di ...

  9. Linux(二):Linux的灵魂

    上次说Linux的前世今生的时候,提了一句,就像学习java一样,我们有一个核心的准则 "万物皆对象" ,学习Linux,同样有基本准则,这也是Linux的最基本的特点,那就是&q ...

  10. java学习之旅(day.02)

    java运行机制 编译型:转换为计算机可读的语言 解释型:用什么,读什么 预编译:java文件(源程序)通过javac命令到class文件,class文件(类)放入类加载器,这个类就加载到JVM中了, ...