feat(tenpayv3): 新增 SM4 算法工具类

This commit is contained in:
Fu Diwei
2022-11-10 01:57:13 +08:00
parent 3558bf8231
commit 2c37745113
6 changed files with 755 additions and 48 deletions

View File

@@ -18,26 +18,32 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// 基于 GCM 模式解密数据。
/// </summary>
/// <param name="keyBytes">AES 密钥字节数组。</param>
/// <param name="ivBytes">加密使用的初始化向量字节数组。</param>
/// <param name="nonceBytes">加密使用的初始化向量字节数组。</param>
/// <param name="aadBytes">加密使用的附加数据包字节数组。</param>
/// <param name="cipherBytes">待解密数据字节数组。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="AES_CIPHER_PADDING_NOPADDING"/></param>
/// <returns>解密后的数据字节数组。</returns>
public static byte[] DecryptWithGCM(byte[] keyBytes, byte[] ivBytes, byte[] aadBytes, byte[] cipherBytes)
public static byte[] DecryptWithGCM(byte[] keyBytes, byte[] nonceBytes, byte[]? aadBytes, byte[] cipherBytes, string paddingMode = AES_CIPHER_PADDING_NOPADDING)
{
if (keyBytes == null) throw new ArgumentNullException(nameof(keyBytes));
if (ivBytes == null) throw new ArgumentNullException(nameof(ivBytes));
if (aadBytes == null) throw new ArgumentNullException(nameof(aadBytes));
if (cipherBytes == null) throw new ArgumentNullException(nameof(cipherBytes));
const int KEY_LENGTH_BYTE = 32;
const int NONCE_LENGTH_BYTE = 12;
const int TAG_LENGTH_BYTE = 16;
const int TAG_LENGTH_BIT = 128;
IBufferedCipher cipher = CipherUtilities.GetCipher(string.Format("{0}/{1}", AES_CIPHER_ALGORITHM_GCM, AES_CIPHER_PADDING_NOPADDING));
ICipherParameters aeadParams = new AeadParameters(
if (keyBytes == null) throw new ArgumentNullException(nameof(keyBytes));
if (keyBytes.Length != KEY_LENGTH_BYTE) throw new ArgumentException($"Invalid key byte length (expected: {KEY_LENGTH_BYTE}, actual: {keyBytes.Length}).", nameof(keyBytes));
if (nonceBytes == null) throw new ArgumentNullException(nameof(nonceBytes));
if (nonceBytes.Length != NONCE_LENGTH_BYTE) throw new ArgumentException($"Invalid nonce byte length (expected: {NONCE_LENGTH_BYTE}, actual: {nonceBytes.Length}).", nameof(nonceBytes));
if (cipherBytes == null) throw new ArgumentNullException(nameof(cipherBytes));
if (cipherBytes.Length < TAG_LENGTH_BYTE) throw new ArgumentException($"Invalid cipher byte length (expected: more than {TAG_LENGTH_BYTE}, actual: {cipherBytes.Length}).", nameof(cipherBytes));
IBufferedCipher cipher = CipherUtilities.GetCipher(string.Format("{0}/{1}", AES_CIPHER_ALGORITHM_GCM, paddingMode));
ICipherParameters cipherParams = new AeadParameters(
new KeyParameter(keyBytes),
TAG_LENGTH_BIT,
ivBytes,
TAG_LENGTH_BYTE * 8,
nonceBytes,
aadBytes
);
cipher.Init(false, aeadParams);
cipher.Init(false, cipherParams);
byte[] plainBytes = new byte[cipher.GetOutputSize(cipherBytes.Length)];
int len = cipher.ProcessBytes(cipherBytes, 0, cipherBytes.Length, plainBytes, 0);
cipher.DoFinal(plainBytes, len);
@@ -48,21 +54,23 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// 基于 GCM 模式解密数据。
/// </summary>
/// <param name="key">AES 密钥。</param>
/// <param name="iv">加密使用的初始化向量。</param>
/// <param name="nonce">加密使用的初始化向量。</param>
/// <param name="aad">加密使用的附加数据包。</param>
/// <param name="cipherText">经 Base64 编码后的待解密数据。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="AES_CIPHER_PADDING_NOPADDING"/></param>
/// <returns>解密后的文本数据。</returns>
public static string DecryptWithGCM(string key, string iv, string? aad, string cipherText)
public static string DecryptWithGCM(string key, string nonce, string? aad, string cipherText, string paddingMode = AES_CIPHER_PADDING_NOPADDING)
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (iv == null) throw new ArgumentNullException(nameof(iv));
if (nonce == null) throw new ArgumentNullException(nameof(nonce));
if (cipherText == null) throw new ArgumentNullException(nameof(cipherText));
byte[] plainBytes = DecryptWithGCM(
keyBytes: Encoding.UTF8.GetBytes(key),
ivBytes: Encoding.UTF8.GetBytes(iv),
nonceBytes: Encoding.UTF8.GetBytes(nonce),
aadBytes: Encoding.UTF8.GetBytes(aad ?? string.Empty),
cipherBytes: Convert.FromBase64String(cipherText)
cipherBytes: Convert.FromBase64String(cipherText),
paddingMode: paddingMode
);
return Encoding.UTF8.GetString(plainBytes);
}

View File

@@ -54,19 +54,19 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
return (RsaKeyParameters)cert.GetPublicKey();
}
private static byte[] SignWithSHA256(RsaKeyParameters rsaPrivateKeyParams, byte[] plainBytes)
private static byte[] SignWithSHA256(RsaKeyParameters rsaPrivateKeyParams, byte[] msgBytes)
{
ISigner signer = SignerUtilities.GetSigner(RSA_SIGNER_ALGORITHM_SHA256);
signer.Init(true, rsaPrivateKeyParams);
signer.BlockUpdate(plainBytes, 0, plainBytes.Length);
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
return signer.GenerateSignature();
}
private static bool VerifyWithSHA256(RsaKeyParameters rsaPublicKeyParams, byte[] plainBytes, byte[] signBytes)
private static bool VerifyWithSHA256(RsaKeyParameters rsaPublicKeyParams, byte[] msgBytes, byte[] signBytes)
{
ISigner signer = SignerUtilities.GetSigner(RSA_SIGNER_ALGORITHM_SHA256);
signer.Init(false, rsaPublicKeyParams);
signer.BlockUpdate(plainBytes, 0, plainBytes.Length);
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
return signer.VerifySignature(signBytes);
}
@@ -88,31 +88,31 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// 使用私钥基于 SHA-256 算法生成签名。
/// </summary>
/// <param name="privateKeyBytes">PKCS#8 私钥字节数组。</param>
/// <param name="plainBytes">待签名的数据字节数组。</param>
/// <param name="msgBytes">待签名的数据字节数组。</param>
/// <returns>签名字节数组。</returns>
public static byte[] SignWithSHA256(byte[] privateKeyBytes, byte[] plainBytes)
public static byte[] SignWithSHA256(byte[] privateKeyBytes, byte[] msgBytes)
{
if (privateKeyBytes == null) throw new ArgumentNullException(nameof(privateKeyBytes));
if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes));
if (msgBytes == null) throw new ArgumentNullException(nameof(msgBytes));
RsaKeyParameters rsaKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes);
return SignWithSHA256(rsaKeyParams, plainBytes);
return SignWithSHA256(rsaKeyParams, msgBytes);
}
/// <summary>
/// 使用私钥基于 SHA-256 算法生成签名。
/// </summary>
/// <param name="privateKey">PKCS#8 私钥PEM 格式)。</param>
/// <param name="plainText">待签名的文本数据。</param>
/// <param name="message">待签名的文本数据。</param>
/// <returns>经 Base64 编码的签名。</returns>
public static string SignWithSHA256(string privateKey, string plainText)
public static string SignWithSHA256(string privateKey, string message)
{
if (privateKey == null) throw new ArgumentNullException(nameof(privateKey));
if (plainText == null) throw new ArgumentNullException(nameof(plainText));
if (message == null) throw new ArgumentNullException(nameof(message));
byte[] privateKeyBytes = ConvertPkcs8PrivateKeyToByteArray(privateKey);
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
byte[] signBytes = SignWithSHA256(privateKeyBytes, plainBytes);
byte[] msgBytes = Encoding.UTF8.GetBytes(message);
byte[] signBytes = SignWithSHA256(privateKeyBytes, msgBytes);
return Convert.ToBase64String(signBytes);
}
@@ -120,55 +120,55 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
/// 使用公钥基于 SHA-256 算法验证签名。
/// </summary>
/// <param name="publicKeyBytes">PKCS#8 公钥字节数据。</param>
/// <param name="plainBytes">待验证的数据字节数据。</param>
/// <param name="msgBytes">待验证的数据字节数据。</param>
/// <param name="signBytes">待验证的签名字节数据。</param>
/// <returns>验证结果。</returns>
public static bool VerifyWithSHA256(byte[] publicKeyBytes, byte[] plainBytes, byte[] signBytes)
public static bool VerifyWithSHA256(byte[] publicKeyBytes, byte[] msgBytes, byte[] signBytes)
{
if (publicKeyBytes == null) throw new ArgumentNullException(nameof(publicKeyBytes));
if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes));
if (msgBytes == null) throw new ArgumentNullException(nameof(msgBytes));
if (signBytes == null) throw new ArgumentNullException(nameof(signBytes));
RsaKeyParameters rsaKeyParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
return VerifyWithSHA256(rsaKeyParams, plainBytes, signBytes);
return VerifyWithSHA256(rsaKeyParams, msgBytes, signBytes);
}
/// <summary>
/// 使用公钥基于 SHA-256 算法验证签名。
/// </summary>
/// <param name="publicKey">PKCS#8 公钥PEM 格式)。</param>
/// <param name="plainText">待验证的文本数据。</param>
/// <param name="message">待验证的文本数据。</param>
/// <param name="signature">经 Base64 编码的待验证的签名。</param>
/// <returns>验证结果。</returns>
public static bool VerifyWithSHA256(string publicKey, string plainText, string signature)
public static bool VerifyWithSHA256(string publicKey, string message, string signature)
{
if (publicKey == null) throw new ArgumentNullException(nameof(publicKey));
if (plainText == null) throw new ArgumentNullException(nameof(plainText));
if (message == null) throw new ArgumentNullException(nameof(message));
if (signature == null) throw new ArgumentNullException(nameof(signature));
byte[] publicKeyBytes = ConvertPkcs8PublicKeyToByteArray(publicKey);
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
byte[] msgBytes = Encoding.UTF8.GetBytes(message);
byte[] signBytes = Convert.FromBase64String(signature);
return VerifyWithSHA256(publicKeyBytes, plainBytes, signBytes);
return VerifyWithSHA256(publicKeyBytes, msgBytes, signBytes);
}
/// <summary>
/// 使用证书基于 SHA-256 算法验证签名。
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <param name="plainText">待验证的文本数据。</param>
/// <param name="message">待验证的文本数据。</param>
/// <param name="signature">经 Base64 编码的待验证的签名。</param>
/// <returns>验证结果。</returns>
public static bool VerifyWithSHA256ByCertificate(string certificate, string plainText, string signature)
public static bool VerifyWithSHA256ByCertificate(string certificate, string message, string signature)
{
if (certificate == null) throw new ArgumentNullException(nameof(certificate));
if (plainText == null) throw new ArgumentNullException(nameof(plainText));
if (message == null) throw new ArgumentNullException(nameof(message));
if (signature == null) throw new ArgumentNullException(nameof(signature));
RsaKeyParameters rsaKeyParams = ConvertCertificateToPublicKeyParams(certificate);
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
byte[] msgBytes = Encoding.UTF8.GetBytes(message);
byte[] signBytes = Convert.FromBase64String(signature);
return VerifyWithSHA256(rsaKeyParams, plainBytes, signBytes);
return VerifyWithSHA256(rsaKeyParams, msgBytes, signBytes);
}
/// <summary>

View File

@@ -0,0 +1,599 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
{
/// <summary>
/// SM2 算法工具类。
/// </summary>
public static partial class SM2Utility
{
// 默认用户身份标识1234567812345678
private static readonly byte[] DEFAULT_USERID_BYTES = new byte[] { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38 };
private static byte[] ConvertPkcs8PrivateKeyToByteArray(string privateKey)
{
privateKey = privateKey
.Replace("-----BEGIN PRIVATE KEY-----", string.Empty)
.Replace("-----END PRIVATE KEY-----", string.Empty);
privateKey = Regex.Replace(privateKey, "\\s+", string.Empty);
return Convert.FromBase64String(privateKey);
}
private static byte[] ConvertPkcs8PublicKeyToByteArray(string publicKey)
{
publicKey = publicKey
.Replace("-----BEGIN PUBLIC KEY-----", string.Empty)
.Replace("-----END PUBLIC KEY-----", string.Empty);
publicKey = Regex.Replace(publicKey, "\\s+", string.Empty);
return Convert.FromBase64String(publicKey);
}
private static X509Certificate ParseX509Certificate(string certificate)
{
using (TextReader sreader = new StringReader(certificate))
{
PemReader pemReader = new PemReader(sreader);
return (X509Certificate)pemReader.ReadObject();
}
}
private static ECPublicKeyParameters ConvertCertificateToPublicKeyParams(string certificate)
{
X509Certificate cert = ParseX509Certificate(certificate);
return (ECPublicKeyParameters)cert.GetPublicKey();
}
private static byte[] SignWithSM3(ECPrivateKeyParameters ecPrivateKeyParams, byte[] userIdBytes, byte[] plainBytes)
{
ISigner signer = new SM2Signer(new SM3Digest());
ICipherParameters idParams = new ParametersWithID(new ParametersWithRandom(ecPrivateKeyParams), userIdBytes);
signer.Init(true, idParams);
signer.BlockUpdate(plainBytes, 0, plainBytes.Length);
return signer.GenerateSignature();
}
private static bool VerifyWithSM3(ECPublicKeyParameters ecPublicKeyParams, byte[] userIdBytes, byte[] plainBytes, byte[] signBytes)
{
ISigner signer = new SM2Signer(new SM3Digest());
ICipherParameters idParams = new ParametersWithID(new ParametersWithRandom(ecPublicKeyParams), userIdBytes);
signer.Init(false, idParams);
signer.BlockUpdate(plainBytes, 0, plainBytes.Length);
return signer.VerifySignature(signBytes);
}
/// <summary>
/// 使用私钥基于 SM3 算法生成签名。
/// </summary>
/// <param name="privateKeyBytes">PKCS#8 私钥字节数组。</param>
/// <param name="plainBytes">待签名的数据字节数组。</param>
/// <returns>签名字节数组。</returns>
public static byte[] SignWithSM3(byte[] privateKeyBytes, byte[] plainBytes)
{
return SignWithSM3(
privateKeyBytes: privateKeyBytes,
userIdBytes: DEFAULT_USERID_BYTES,
plainBytes: plainBytes
);
}
/// <summary>
/// 使用私钥基于 SM3 算法生成签名。
/// </summary>
/// <param name="privateKeyBytes">PKCS#8 私钥字节数组。</param>
/// <param name="userIdBytes">用户身份标识字节数组。</param>
/// <param name="plainBytes">待签名的数据字节数组。</param>
/// <returns>签名字节数组。</returns>
public static byte[] SignWithSM3(byte[] privateKeyBytes, byte[] userIdBytes, byte[] plainBytes)
{
if (privateKeyBytes == null) throw new ArgumentNullException(nameof(privateKeyBytes));
if (userIdBytes == null) throw new ArgumentNullException(nameof(userIdBytes));
if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes));
//ECPrivateKeyParameters ecKeyParams = (ECPrivateKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes);
//return SignWithSM3(ecKeyParams, userIdBytes, plainBytes);
BigInteger userD = new BigInteger(1, privateKeyBytes);
SM2Factory sm2Factory = new SM2Factory();
ECPoint userKey = sm2Factory.ecc_point_g.Multiply(userD);
SM3Digest sm3Digest = new SM3Digest();
var z = sm2Factory.Sm2GetZ(userIdBytes, userKey);
sm3Digest.BlockUpdate(z, 0, z.Length);
sm3Digest.BlockUpdate(plainBytes, 0, plainBytes.Length);
var md = new byte[32];
sm3Digest.DoFinal(md, 0);
var result = sm2Factory.Sm2Sign(md, userD, userKey);
return result.ToByteArray();
}
/// <summary>
/// 使用私钥基于 SM3 算法生成签名。
/// </summary>
/// <param name="privateKey">PKCS#8 私钥PEM 格式)。</param>
/// <param name="plainText">待签名的文本数据。</param>
/// <returns>经 Base64 编码的签名。</returns>
public static string SignWithSM3(string privateKey, string plainText)
{
if (privateKey == null) throw new ArgumentNullException(nameof(privateKey));
if (plainText == null) throw new ArgumentNullException(nameof(plainText));
byte[] privateKeyBytes = ConvertPkcs8PrivateKeyToByteArray(privateKey);
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
byte[] signBytes = SignWithSM3(privateKeyBytes, plainBytes);
return Convert.ToBase64String(signBytes);
}
/// <summary>
/// 使用公钥基于 SM3 算法验证签名。
/// </summary>
/// <param name="publicKeyBytes">PKCS#8 公钥字节数据。</param>
/// <param name="plainBytes">待验证的数据字节数据。</param>
/// <param name="signBytes">待验证的签名字节数据。</param>
/// <returns>验证结果。</returns>
public static bool VerifyWithSM3(byte[] publicKeyBytes, byte[] plainBytes, byte[] signBytes)
{
return VerifyWithSM3(
publicKeyBytes: publicKeyBytes,
userIdBytes: DEFAULT_USERID_BYTES,
plainBytes: plainBytes,
signBytes: signBytes
);
}
/// <summary>
/// 使用公钥基于 SM3 算法验证签名。
/// </summary>
/// <param name="publicKeyBytes">PKCS#8 公钥字节数据。</param>
/// <param name="userIdBytes">用户身份标识字节数组。</param>
/// <param name="plainBytes">待验证的数据字节数据。</param>
/// <param name="signBytes">待验证的签名字节数据。</param>
/// <returns>验证结果。</returns>
public static bool VerifyWithSM3(byte[] publicKeyBytes, byte[] userIdBytes, byte[] plainBytes, byte[] signBytes)
{
if (publicKeyBytes == null) throw new ArgumentNullException(nameof(publicKeyBytes));
if (userIdBytes == null) throw new ArgumentNullException(nameof(userIdBytes));
if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes));
if (signBytes == null) throw new ArgumentNullException(nameof(signBytes));
ECPublicKeyParameters ecPublicKeyParams = (ECPublicKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
return VerifyWithSM3(ecPublicKeyParams, userIdBytes, plainBytes, signBytes);
}
/// <summary>
/// 使用公钥基于 SM3 算法验证签名。
/// </summary>
/// <param name="publicKey">PKCS#8 公钥PEM 格式)。</param>
/// <param name="plainText">待验证的文本数据。</param>
/// <param name="signature">经 Base64 编码的待验证的签名。</param>
/// <returns>验证结果。</returns>
public static bool VerifyWithSM3(string publicKey, string plainText, string signature)
{
if (publicKey == null) throw new ArgumentNullException(nameof(publicKey));
if (plainText == null) throw new ArgumentNullException(nameof(plainText));
if (signature == null) throw new ArgumentNullException(nameof(signature));
byte[] publicKeyBytes = ConvertPkcs8PublicKeyToByteArray(publicKey);
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
byte[] signBytes = Convert.FromBase64String(signature);
return VerifyWithSM3(publicKeyBytes, plainBytes, signBytes);
}
/// <summary>
/// <para>从 CRT/CER 证书中导出 PKCS#8 公钥。</para>
/// <para>
/// 即从 -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----
/// 转为 -----BEGIN PUBLIC KEY----- ..... -----END PUBLIC KEY-----
/// </para>
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <returns>PKCS#8 公钥PEM 格式)。</returns>
public static string ExportPublicKey(string certificate)
{
if (certificate == null) throw new ArgumentNullException(nameof(certificate));
using (TextWriter swriter = new StringWriter())
{
ECKeyParameters ecKeyParams = ConvertCertificateToPublicKeyParams(certificate);
PemWriter pemWriter = new PemWriter(swriter);
pemWriter.WriteObject(ecKeyParams);
pemWriter.Writer.Flush();
return swriter.ToString()!;
}
}
/// <summary>
/// <para>从 CRT/CER 证书中导出证书序列号。</para>
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <returns>证书序列号。</returns>
public static string ExportSerialNumber(string certificate)
{
if (certificate == null) throw new ArgumentNullException(nameof(certificate));
X509Certificate cert = ParseX509Certificate(certificate);
return cert.SerialNumber.ToString(16);
}
/// <summary>
/// <para>从 CRT/CER 证书中导出证书颁发时间。</para>
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <returns>证书颁发时间。</returns>
public static DateTimeOffset ExportEffectiveTime(string certificate)
{
if (certificate == null) throw new ArgumentNullException(nameof(certificate));
X509Certificate cert = ParseX509Certificate(certificate);
return new DateTimeOffset(cert.NotBefore);
}
/// <summary>
/// <para>从 CRT/CER 证书中导出证书过期时间。</para>
/// </summary>
/// <param name="certificate">证书PEM 格式)。</param>
/// <returns>证书过期时间。</returns>
public static DateTimeOffset ExportExpireTime(string certificate)
{
if (certificate == null) throw new ArgumentNullException(nameof(certificate));
X509Certificate cert = ParseX509Certificate(certificate);
return new DateTimeOffset(cert.NotAfter);
}
}
partial class SM2Utility
{
internal sealed class Cipher
{
private int ct = 1;
private ECPoint? p2;
private SM3Digest? sm3keybase;
private SM3Digest? sm3c3;
private byte[] key = new byte[32];
private byte keyOff = 0;
private void Reset()
{
sm3keybase = new SM3Digest();
sm3c3 = new SM3Digest();
byte[] p;
p = p2.Normalize().XCoord.ToBigInteger().ToByteArray32();
sm3keybase.BlockUpdate(p, 0, p.Length);
sm3c3.BlockUpdate(p, 0, p.Length);
p = p2.Normalize().YCoord.ToBigInteger().ToByteArray32();
sm3keybase.BlockUpdate(p, 0, p.Length);
ct = 1;
NextKey();
}
private void NextKey()
{
SM3Digest sm3keycur = new SM3Digest(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);
var testKey = HexUtility.ConvertBytesToHex(key);
keyOff = 0;
ct++;
}
public ECPoint Init_enc(SM2Factory sm2, ECPoint userKey)
{
BigInteger k = null;
ECPoint c1 = null;
AsymmetricCipherKeyPair key = sm2.ecc_key_pair_generator.GenerateKeyPair();
ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters)key.Private;
ECPublicKeyParameters ecpub = (ECPublicKeyParameters)key.Public;
k = ecpriv.D;
c1 = ecpub.Q;
p2 = userKey.Multiply(k);
Reset();
return c1;
}
public void Encrypt(byte[] data)
{
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)
{
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++];
}
sm3c3.BlockUpdate(data, 0, data.Length);
}
public void Dofinal(byte[] c3)
{
byte[] p = p2.Normalize().YCoord.ToBigInteger().ToByteArray32();
sm3c3.BlockUpdate(p, 0, p.Length);
sm3c3.DoFinal(c3, 0);
Reset();
}
}
internal sealed class SM2Factory
{
public static readonly string[] SM2_PARAMS = {
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", // p,0
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", // a,1
"28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", // b,2
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", // n,3
"32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", // gx,4
"BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0" // gy,5
};
public string[] ecc_param = SM2_PARAMS;
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;
public SM2Factory()
{
ecc_param = SM2_PARAMS;
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 SM2Signature Sm2Sign(byte[] md, BigInteger userD, ECPoint userKey)
{
BigInteger e = new BigInteger(1, md);
BigInteger k = null;
ECPoint kp = null;
BigInteger r = null;
BigInteger s = null;
do
{
do
{
// 正式环境
AsymmetricCipherKeyPair keypair = ecc_key_pair_generator.GenerateKeyPair();
ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters)keypair.Private;
ECPublicKeyParameters ecpub = (ECPublicKeyParameters)keypair.Public;
k = ecpriv.D;
kp = ecpub.Q;
//System.out.println("BigInteger:" + k + "\nECPoint:" + kp);
//System.out.println("计算曲线点X1: "+ kp.getXCoord().toBigInteger().toString(16));
//System.out.println("计算曲线点Y1: "+ kp.getYCoord().toBigInteger().toString(16));
//System.out.println("");
// r
r = e.Add(kp.XCoord.ToBigInteger());
r = r.Mod(this.ecc_n);
} while (r.Equals(BigInteger.Zero) || r.Add(k).Equals(this.ecc_n) || r.ToString(16).Length != 64);
// (1 + dA)~-1
BigInteger da_1 = userD.Add(BigInteger.One);
da_1 = da_1.ModInverse(this.ecc_n);
// s
s = r.Multiply(userD);
s = k.Subtract(s).Mod(this.ecc_n);
s = da_1.Multiply(s).Mod(this.ecc_n);
} while (s.Equals(BigInteger.Zero) || s.ToString(16).Length != 64);
var sM2Signature = new SM2Signature
{
R = r.ToByteArray32(),
S = s.ToByteArray32()
};
return sM2Signature;
}
public SM2Result Sm2Verify(byte[] md, ECPoint userKey, BigInteger r, BigInteger s)
{
var sm2Result = new SM2Result();
BigInteger e = new BigInteger(1, md);
BigInteger t = r.Add(s).Mod(this.ecc_n);
if (t.Equals(BigInteger.Zero))
{
return sm2Result;
}
else
{
ECPoint x1y1 = ecc_point_g.Multiply(s);
//System.out.println("计算曲线点X0: "+ x1y1.normalize().getXCoord().toBigInteger().toString(16));
//System.out.println("计算曲线点Y0: "+ x1y1.normalize().getYCoord().toBigInteger().toString(16));
//System.out.println("");
x1y1 = x1y1.Add(userKey.Multiply(t));
//System.out.println("计算曲线点X1: "+ x1y1.normalize().getXCoord().toBigInteger().toString(16));
//System.out.println("计算曲线点Y1: "+ x1y1.normalize().getYCoord().toBigInteger().toString(16));
//System.out.println("");
sm2Result.R = e.Add(x1y1.Normalize().XCoord.ToBigInteger()).Mod(this.ecc_n);
//System.out.println("R: " + sm2Result.R.toString(16));
return sm2Result;
}
}
public byte[] Sm2GetZ(byte[] userId, ECPoint userKey)
{
SM3Digest sm3 = new SM3Digest();
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.ToByteArray32();
sm3.BlockUpdate(p, 0, p.Length);
p = ecc_b.ToByteArray32();
sm3.BlockUpdate(p, 0, p.Length);
// gx,gy
p = ecc_gx.ToByteArray32();
sm3.BlockUpdate(p, 0, p.Length);
p = ecc_gy.ToByteArray32();
sm3.BlockUpdate(p, 0, p.Length);
// x,y
p = userKey.Normalize().XCoord.ToBigInteger().ToByteArray32();
sm3.BlockUpdate(p, 0, p.Length);
p = userKey.Normalize().YCoord.ToBigInteger().ToByteArray32();
sm3.BlockUpdate(p, 0, p.Length);
// Z
byte[] md = new byte[sm3.GetDigestSize()];
sm3.DoFinal(md, 0);
return md;
}
}
internal sealed class SM2Signature
{
public byte[] R { get; set; }
public byte[] S { get; set; }
public SM2Signature()
{
R = Array.Empty<byte>();
S = Array.Empty<byte>();
}
public SM2Signature(byte[] signBytes)
: this()
{
if (signBytes.Length == 65)
{
signBytes = signBytes.Skip(1).ToArray();
}
if (signBytes.Length == 64)
{
R = signBytes.Take(32).ToArray();
S = signBytes.Skip(32).ToArray();
}
else
{
throw new ArgumentException("Invalid signature data.", nameof(signBytes));
}
}
public byte[] ToByteArray()
{
return R.Concat(S).ToArray();
}
public byte[] ToByteArray04()
{
byte[] temp = new byte[65];
temp[0] = 0x04;
Array.Copy(this.R, 0, temp, 1, 32);
Array.Copy(this.S, 0, temp, 33, 32);
return temp;
}
}
internal sealed class SM2Result
{
public BigInteger R;
public BigInteger S;
public byte[] sa;
public byte[] sb;
public byte[] s1;
public byte[] s2;
public ECPoint keyra;
public ECPoint keyrb;
}
}
}

View File

@@ -0,0 +1,78 @@
using System;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
{
/// <summary>
/// SM4 算法工具类。
/// </summary>
public static class SM4Utility
{
private const string SM4_CIPHER_ALGORITHM_GCM = "SM4/GCM";
private const string SM4_CIPHER_PADDING_NOPADDING = "NoPadding";
/// <summary>
/// 基于 GCM 模式解密数据。
/// </summary>
/// <param name="keyBytes">SM4 密钥字节数组。</param>
/// <param name="nonceBytes">加密使用的初始化向量字节数组。</param>
/// <param name="aadBytes">加密使用的附加数据包字节数组。</param>
/// <param name="cipherBytes">待解密数据字节数组。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="SM4_CIPHER_PADDING_NOPADDING"/></param>
/// <returns>解密后的数据字节数组。</returns>
public static byte[] DecryptWithGCM(byte[] keyBytes, byte[] nonceBytes, byte[]? aadBytes, byte[] cipherBytes, string paddingMode = SM4_CIPHER_PADDING_NOPADDING)
{
const int KEY_LENGTH_BYTE = 16;
const int NONCE_LENGTH_BYTE = 12;
const int TAG_LENGTH_BYTE = 16;
if (keyBytes == null) throw new ArgumentNullException(nameof(keyBytes));
if (keyBytes.Length != KEY_LENGTH_BYTE) throw new ArgumentException($"Invalid key byte length (expected: {KEY_LENGTH_BYTE}, actual: {keyBytes.Length}).", nameof(keyBytes));
if (nonceBytes == null) throw new ArgumentNullException(nameof(nonceBytes));
if (nonceBytes.Length != NONCE_LENGTH_BYTE) throw new ArgumentException($"Invalid nonce byte length (expected: {NONCE_LENGTH_BYTE}, actual: {nonceBytes.Length}).", nameof(nonceBytes));
if (cipherBytes == null) throw new ArgumentNullException(nameof(cipherBytes));
if (cipherBytes.Length < TAG_LENGTH_BYTE) throw new ArgumentException($"Invalid cipher byte length (expected: more than {TAG_LENGTH_BYTE}, actual: {cipherBytes.Length}).", nameof(cipherBytes));
IBufferedCipher cipher = CipherUtilities.GetCipher(string.Format("{0}/{1}", SM4_CIPHER_ALGORITHM_GCM, paddingMode));
ICipherParameters cipherParams = new AeadParameters(
new KeyParameter(keyBytes),
TAG_LENGTH_BYTE * 8,
nonceBytes,
aadBytes
);
cipher.Init(false, cipherParams);
byte[] plainBytes = new byte[cipher.GetOutputSize(cipherBytes.Length)];
int len = cipher.ProcessBytes(cipherBytes, 0, cipherBytes.Length, plainBytes, 0);
cipher.DoFinal(plainBytes, len);
return plainBytes;
}
/// <summary>
/// 基于 GCM 模式解密数据。
/// </summary>
/// <param name="key">SM4 密钥。</param>
/// <param name="nonce">加密使用的初始化向量。</param>
/// <param name="aad">加密使用的附加数据包。</param>
/// <param name="cipherText">经 Base64 编码后的待解密数据。</param>
/// <param name="paddingMode">填充模式。(默认值:<see cref="SM4_CIPHER_PADDING_NOPADDING"/></param>
/// <returns>解密后的文本数据。</returns>
public static string DecryptWithGCM(string key, string nonce, string? aad, string cipherText, string paddingMode = SM4_CIPHER_PADDING_NOPADDING)
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (nonce == null) throw new ArgumentNullException(nameof(nonce));
if (cipherText == null) throw new ArgumentNullException(nameof(cipherText));
byte[] plainBytes = DecryptWithGCM(
keyBytes: Encoding.UTF8.GetBytes(key),
nonceBytes: Encoding.UTF8.GetBytes(nonce),
aadBytes: Encoding.UTF8.GetBytes(aad ?? string.Empty),
cipherBytes: Convert.FromBase64String(cipherText),
paddingMode: paddingMode
);
return Encoding.UTF8.GetString(plainBytes);
}
}
}