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. HarmonyOS NEXT应用开发案例——列表编辑实现

    介绍 本示例介绍用过使用ListItem组件属性swipeAction实现列表左滑编辑效果的功能. 该场景多用于待办事项管理.文件管理.备忘录的记录管理等. 效果图预览 使用说明: 点击添加按钮,选择 ...

  2. [FE] Quasar 性能优化: 减小 vendor.js 尺寸

    默认情况下,出于性能和缓存的原因,Quasar 所有来自 node_modules 的东西都会被注入到 vendor 中. 但是,如果希望从这个 vendor.js 中添加或删除某些内容,可以如下这样 ...

  3. [FAQ] docker-compose MySQL8 ERROR: Different lower_case_table_names settings for server

    MySQL8 启动时 lower_case_table_names 的设置和初始值不一致时,会报 ERROR. 在 docker-compose 中,只需要在命令中加入命令选项即可,并配置一个新的 v ...

  4. [PHP] composer, PHP Fatal error: Allowed memory size of xx bytes exhausted

    终端执行 composer 命令时经常会遇到内存不够的情况. 视情况升级一下 composer,使用 composer self-update. 默认 php 的内存限制是 128M,临时取消 php ...

  5. [FE] Quasar BEX 预览版指南

    BEX(Browser Extension)是 Quasar 基于同一套代码允许编译成浏览器扩展来运行,支持 Firefox & Chrome. 截止目前(2019/12/25), bex 模 ...

  6. Mobius 一个运行在 .NET Core 上的 .NET 运行时

    一个 .NET 应用仅仅只是一块在 .NET 运行时上面运行的二进制代码.而 .NET 运行时只是一个能执行这项任务的程序.当前的 .NET Framework 和 .NET Core 运行时采用 C ...

  7. go语言package使用

    近期接触go感觉package包之间引用很麻烦,很绕圈子.下面一起理一理这个package咋用 关于package: 1.不限于一个文件,可以多个文件组成一个package 2.不要求package的 ...

  8. C语言程序设计-笔记2-分支结构

    C语言程序设计-笔记2-分支结构 例3-1  简单的猜数游戏.输入你所猜的整数(假定1-100),与计算机产生的被猜数比较,若相等,显示猜中:若不等,显示与被猜数的大小关系. /*简单的猜数游戏*/ ...

  9. 登录信息localStorage存储

    localStorage拓展了cookie的4K限制,与sessionStorage的唯一一点区别就是localStorage属于永久性存储,而sessionStorage属于当会话结束的时候,ses ...

  10. Python多线程编程深度探索:从入门到实战

    title: Python多线程编程深度探索:从入门到实战 date: 2024/4/28 18:57:17 updated: 2024/4/28 18:57:17 categories: 后端开发 ...