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