mirror of
https://gitee.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git
synced 2025-12-29 18:04:42 +08:00
feat(wxapi): 新增安全鉴权模式所需的 AES、RSA、SM2、SM4 等算法工具类
This commit is contained in:
@@ -40,6 +40,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.3.1" />
|
||||
<PackageReference Include="SKIT.FlurlHttpClient.Common" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Security;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities
|
||||
{
|
||||
@@ -10,11 +14,16 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities
|
||||
/// </summary>
|
||||
public static class AESUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// 填充模式:NoPadding。
|
||||
/// </summary>
|
||||
public const string PADDING_MODE_NOPADDING = "NoPadding";
|
||||
|
||||
/// <summary>
|
||||
/// 基于 CBC 模式解密数据。
|
||||
/// </summary>
|
||||
/// <param name="keyBytes">AES 密钥字节数组。</param>
|
||||
/// <param name="ivBytes">加密使用的初始化向量字节数组。</param>
|
||||
/// <param name="ivBytes">初始化向量字节数组。</param>
|
||||
/// <param name="cipherBytes">待解密数据字节数组。</param>
|
||||
/// <returns>解密后的数据字节数组。</returns>
|
||||
public static byte[] DecryptWithCBC(byte[] keyBytes, byte[] ivBytes, byte[] cipherBytes)
|
||||
@@ -56,11 +65,86 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities
|
||||
return EncodedString.ToLiteralString(plainBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 GCM 模式解密数据。
|
||||
/// </summary>
|
||||
/// <param name="keyBytes">AES 密钥字节数组。</param>
|
||||
/// <param name="nonceBytes">初始化向量字节数组。</param>
|
||||
/// <param name="associatedDataBytes">附加数据字节数组。</param>
|
||||
/// <param name="cipherBytes">待解密数据字节数组。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_NOPADDING"/>)</param>
|
||||
/// <returns>解密后的数据字节数组。</returns>
|
||||
public static byte[] DecryptWithGCM(byte[] keyBytes, byte[] nonceBytes, byte[]? associatedDataBytes, byte[] cipherBytes, string paddingMode = PADDING_MODE_NOPADDING)
|
||||
{
|
||||
const int KEY_LENGTH_BYTE = 32;
|
||||
const int NONCE_LENGTH_BYTE = 12;
|
||||
const int TAG_LENGTH_BYTE = 16;
|
||||
|
||||
if (keyBytes is 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 is 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 is 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));
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
using (AesGcm aes = new AesGcm(keyBytes))
|
||||
{
|
||||
byte[] cipherWithoutTagBytes = new byte[cipherBytes.Length - TAG_LENGTH_BYTE];
|
||||
byte[] tagBytes = new byte[TAG_LENGTH_BYTE];
|
||||
Buffer.BlockCopy(cipherBytes, 0, cipherWithoutTagBytes, 0, cipherWithoutTagBytes.Length);
|
||||
Buffer.BlockCopy(cipherBytes, cipherWithoutTagBytes.Length, tagBytes, 0, tagBytes.Length);
|
||||
|
||||
byte[] plainBytes = new byte[cipherWithoutTagBytes.Length];
|
||||
aes.Decrypt(nonceBytes, cipherWithoutTagBytes, tagBytes, plainBytes, associatedDataBytes);
|
||||
return plainBytes;
|
||||
}
|
||||
#else
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"AES/GCM/{paddingMode}");
|
||||
ICipherParameters cipherParams = new AeadParameters(
|
||||
new KeyParameter(keyBytes),
|
||||
TAG_LENGTH_BYTE * 8,
|
||||
nonceBytes,
|
||||
associatedDataBytes
|
||||
);
|
||||
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;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 GCM 模式解密数据。
|
||||
/// </summary>
|
||||
/// <param name="encodingKey">经过编码后的(通常为 Base64)AES 密钥。</param>
|
||||
/// <param name="encodingNonce">经过编码后的(通常为 Base64)初始化向量。</param>
|
||||
/// <param name="encodingAssociatedData">经过编码后的(通常为 Base64)附加数据。</param>
|
||||
/// <param name="encodingCipher">经过编码后的(通常为 Base64)待解密数据。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_NOPADDING"/>)</param>
|
||||
/// <returns>解密后的数据。</returns>
|
||||
public static EncodedString DecryptWithGCM(EncodedString encodingKey, EncodedString encodingNonce, EncodedString encodingAssociatedData, EncodedString encodingCipher, string paddingMode = PADDING_MODE_NOPADDING)
|
||||
{
|
||||
if (encodingKey.Value is null) throw new ArgumentNullException(nameof(encodingKey));
|
||||
if (encodingNonce.Value is null) throw new ArgumentNullException(nameof(encodingNonce));
|
||||
if (encodingCipher.Value is null) throw new ArgumentNullException(nameof(encodingCipher));
|
||||
|
||||
byte[] plainBytes = DecryptWithGCM(
|
||||
keyBytes: EncodedString.FromString(encodingKey, fallbackEncodingKind: EncodingKinds.Base64),
|
||||
nonceBytes: EncodedString.FromString(encodingNonce, fallbackEncodingKind: EncodingKinds.Base64),
|
||||
associatedDataBytes: encodingAssociatedData.Value is not null ? EncodedString.FromString(encodingAssociatedData, fallbackEncodingKind: EncodingKinds.Base64) : null,
|
||||
cipherBytes: EncodedString.FromString(encodingCipher, fallbackEncodingKind: EncodingKinds.Base64),
|
||||
paddingMode: paddingMode
|
||||
);
|
||||
return EncodedString.ToLiteralString(plainBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 CBC 模式加密数据。
|
||||
/// </summary>
|
||||
/// <param name="keyBytes">AES 密钥字节数组。</param>
|
||||
/// <param name="ivBytes">加密使用的初始化向量字节数组。</param>
|
||||
/// <param name="ivBytes">初始化向量字节数组。</param>
|
||||
/// <param name="plainBytes">待加密数据字节数组。</param>
|
||||
/// <returns>加密后的数据字节数组。</returns>
|
||||
public static byte[] EncryptWithCBC(byte[] keyBytes, byte[] ivBytes, byte[] plainBytes)
|
||||
@@ -101,5 +185,76 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities
|
||||
);
|
||||
return EncodedString.ToBase64String(plainBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 GCM 模式加密数据。
|
||||
/// </summary>
|
||||
/// <param name="keyBytes">AES 密钥字节数组。</param>
|
||||
/// <param name="nonceBytes">初始化向量字节数组。</param>
|
||||
/// <param name="associatedDataBytes">附加数据字节数组。</param>
|
||||
/// <param name="plainBytes">待加密数据字节数组。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_NOPADDING"/>)</param>
|
||||
/// <returns>加密后的数据字节数组。</returns>
|
||||
public static byte[] EncryptWithGCM(byte[] keyBytes, byte[] nonceBytes, byte[]? associatedDataBytes, byte[] plainBytes, string paddingMode = PADDING_MODE_NOPADDING)
|
||||
{
|
||||
const int TAG_LENGTH_BYTE = 16;
|
||||
|
||||
if (keyBytes is null) throw new ArgumentNullException(nameof(keyBytes));
|
||||
if (nonceBytes is null) throw new ArgumentNullException(nameof(nonceBytes));
|
||||
if (plainBytes is null) throw new ArgumentNullException(nameof(plainBytes));
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
using (AesGcm aes = new AesGcm(keyBytes))
|
||||
{
|
||||
byte[] cipherBytes = new byte[plainBytes.Length];
|
||||
byte[] tagBytes = new byte[TAG_LENGTH_BYTE];
|
||||
aes.Encrypt(nonceBytes, plainBytes, cipherBytes, tagBytes, associatedDataBytes);
|
||||
|
||||
byte[] cipherWithTagBytes = new byte[cipherBytes.Length + tagBytes.Length];
|
||||
Buffer.BlockCopy(cipherBytes, 0, cipherWithTagBytes, 0, cipherBytes.Length);
|
||||
Buffer.BlockCopy(tagBytes, 0, cipherWithTagBytes, cipherBytes.Length, tagBytes.Length);
|
||||
return cipherWithTagBytes;
|
||||
}
|
||||
#else
|
||||
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"AES/GCM/{paddingMode}");
|
||||
ICipherParameters cipherParams = new AeadParameters(
|
||||
new KeyParameter(keyBytes),
|
||||
TAG_LENGTH_BYTE * 8,
|
||||
nonceBytes,
|
||||
associatedDataBytes
|
||||
);
|
||||
cipher.Init(true, cipherParams);
|
||||
byte[] cipherBytes = new byte[cipher.GetOutputSize(plainBytes.Length)];
|
||||
int len = cipher.ProcessBytes(plainBytes, 0, plainBytes.Length, cipherBytes, 0);
|
||||
cipher.DoFinal(cipherBytes, len);
|
||||
return cipherBytes;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 GCM 模式加密数据。
|
||||
/// </summary>
|
||||
/// <param name="encodingKey">经过编码后的(通常为 Base64)AES 密钥。</param>
|
||||
/// <param name="encodingNonce">经过编码后的(通常为 Base64)初始化向量。</param>
|
||||
/// <param name="encodingAssociatedData">经过编码后的(通常为 Base64)附加数据。</param>
|
||||
/// <param name="plainData">待加密数据。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_NOPADDING"/>)</param>
|
||||
/// <returns>经过 Base64 编码的加密后的数据。</returns>
|
||||
public static EncodedString EncryptWithGCM(EncodedString encodingKey, EncodedString encodingNonce, EncodedString encodingAssociatedData, string plainData, string paddingMode = PADDING_MODE_NOPADDING)
|
||||
{
|
||||
if (encodingKey.Value is null) throw new ArgumentNullException(nameof(encodingKey));
|
||||
if (encodingNonce.Value is null) throw new ArgumentNullException(nameof(encodingNonce));
|
||||
if (plainData is null) throw new ArgumentNullException(nameof(plainData));
|
||||
|
||||
byte[] plainBytes = EncryptWithGCM(
|
||||
keyBytes: EncodedString.FromString(encodingKey, fallbackEncodingKind: EncodingKinds.Base64),
|
||||
nonceBytes: EncodedString.FromString(encodingNonce, fallbackEncodingKind: EncodingKinds.Base64),
|
||||
associatedDataBytes: encodingAssociatedData.Value is not null ? EncodedString.FromString(encodingAssociatedData, fallbackEncodingKind: EncodingKinds.Base64) : null,
|
||||
plainBytes: Encoding.UTF8.GetBytes(plainData),
|
||||
paddingMode: paddingMode
|
||||
);
|
||||
return EncodedString.ToBase64String(plainBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
217
src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/RSAUtility.cs
Normal file
217
src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/RSAUtility.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.RegularExpressions;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.OpenSsl;
|
||||
using Org.BouncyCastle.Security;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
|
||||
/// <summary>
|
||||
/// RSA 算法工具类。
|
||||
/// </summary>
|
||||
public static class RSAUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// 签名算法:SHA-256withRSA。
|
||||
/// </summary>
|
||||
private const string DIGEST_ALGORITHM_SHA256 = "SHA-256withRSA";
|
||||
|
||||
private static byte[] ConvertPrivateKeyPemToByteArray(string privateKeyPem)
|
||||
{
|
||||
if (!privateKeyPem.StartsWith("-----BEGIN PRIVATE KEY-----"))
|
||||
{
|
||||
using (TextReader textReader = new StringReader(privateKeyPem))
|
||||
using (PemReader pemReader = new PemReader(textReader))
|
||||
{
|
||||
object pemObject = pemReader.ReadObject();
|
||||
|
||||
if (pemObject is AsymmetricCipherKeyPair)
|
||||
{
|
||||
// PKCS#1 格式
|
||||
AsymmetricCipherKeyPair cipherKeyPair = (AsymmetricCipherKeyPair)pemObject;
|
||||
using (TextWriter textWriter = new StringWriter())
|
||||
using (PemWriter pemWriter = new PemWriter(textWriter))
|
||||
{
|
||||
Pkcs8Generator pkcs8 = new Pkcs8Generator(cipherKeyPair.Private);
|
||||
pemWriter.WriteObject(pkcs8);
|
||||
pemWriter.Writer.Close();
|
||||
|
||||
privateKeyPem = textWriter.ToString()!;
|
||||
}
|
||||
}
|
||||
else if (pemObject is RsaPrivateCrtKeyParameters)
|
||||
{
|
||||
// PKCS#8 格式
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Private key format is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
privateKeyPem = privateKeyPem
|
||||
.Replace("-----BEGIN PRIVATE KEY-----", string.Empty)
|
||||
.Replace("-----END PRIVATE KEY-----", string.Empty);
|
||||
privateKeyPem = Regex.Replace(privateKeyPem, "\\s+", string.Empty);
|
||||
return Convert.FromBase64String(privateKeyPem);
|
||||
}
|
||||
|
||||
private static byte[] ConvertPublicKeyPemToByteArray(string publicKeyPem)
|
||||
{
|
||||
if (!publicKeyPem.StartsWith("-----BEGIN PUBLIC KEY-----"))
|
||||
{
|
||||
using (TextReader textReader = new StringReader(publicKeyPem))
|
||||
using (PemReader pemReader = new PemReader(textReader))
|
||||
{
|
||||
object pemObject = pemReader.ReadObject();
|
||||
if (pemObject is RsaKeyParameters)
|
||||
{
|
||||
// PKCS#1 或 PKCS#8 格式
|
||||
RsaKeyParameters rsaKeyParams = (RsaKeyParameters)pemObject;
|
||||
using (TextWriter textWriter = new StringWriter())
|
||||
using (PemWriter pemWriter = new PemWriter(textWriter))
|
||||
{
|
||||
pemWriter.WriteObject(rsaKeyParams);
|
||||
pemWriter.Writer.Close();
|
||||
|
||||
publicKeyPem = textWriter.ToString()!;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Public key format is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publicKeyPem = publicKeyPem
|
||||
.Replace("-----BEGIN PUBLIC KEY-----", string.Empty)
|
||||
.Replace("-----END PUBLIC KEY-----", string.Empty);
|
||||
publicKeyPem = Regex.Replace(publicKeyPem, "\\s+", string.Empty);
|
||||
return Convert.FromBase64String(publicKeyPem);
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
#else
|
||||
private static RsaKeyParameters ParsePrivateKeyToParameters(byte[] privateKeyBytes)
|
||||
{
|
||||
return (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes);
|
||||
}
|
||||
|
||||
private static RsaKeyParameters ParsePublicKeyToParameters(byte[] publicKeyBytes)
|
||||
{
|
||||
return (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
|
||||
}
|
||||
|
||||
private static byte[] Sign(RsaKeyParameters rsaPrivateKeyParams, byte[] messageBytes, string digestAlgorithm)
|
||||
{
|
||||
ISigner signer = SignerUtilities.GetSigner(digestAlgorithm);
|
||||
signer.Init(true, rsaPrivateKeyParams);
|
||||
signer.BlockUpdate(messageBytes, 0, messageBytes.Length);
|
||||
return signer.GenerateSignature();
|
||||
}
|
||||
|
||||
private static bool Verify(RsaKeyParameters rsaPublicKeyParams, byte[] messageBytes, byte[] signBytes, string digestAlgorithm)
|
||||
{
|
||||
ISigner signer = SignerUtilities.GetSigner(digestAlgorithm);
|
||||
signer.Init(false, rsaPublicKeyParams);
|
||||
signer.BlockUpdate(messageBytes, 0, messageBytes.Length);
|
||||
return signer.VerifySignature(signBytes);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 使用私钥基于 SHA-256 算法生成签名。
|
||||
/// </summary>
|
||||
/// <param name="privateKeyBytes">PKCS#8 私钥字节数组。</param>
|
||||
/// <param name="messageBytes">待签名的数据字节数组。</param>
|
||||
/// <param name="digestAlgorithm">签名算法。(默认值:<see cref="DIGEST_ALGORITHM_SHA256"/>)</param>
|
||||
/// <returns>签名字节数组。</returns>
|
||||
public static byte[] SignWithSHA256(byte[] privateKeyBytes, byte[] messageBytes)
|
||||
{
|
||||
if (privateKeyBytes is null) throw new ArgumentNullException(nameof(privateKeyBytes));
|
||||
if (messageBytes is null) throw new ArgumentNullException(nameof(messageBytes));
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
using (RSA rsa = RSA.Create())
|
||||
{
|
||||
rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);
|
||||
return rsa.SignData(messageBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
}
|
||||
#else
|
||||
RsaKeyParameters rsaPrivateKeyParams = ParsePrivateKeyToParameters(privateKeyBytes);
|
||||
return Sign(rsaPrivateKeyParams, messageBytes, DIGEST_ALGORITHM_SHA256);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用私钥基于 SHA-256 算法生成签名。
|
||||
/// </summary>
|
||||
/// <param name="privateKeyPem">PKCS#1/PKCS#8 私钥(PEM 格式)。</param>
|
||||
/// <param name="messageData">待签名的数据。</param>
|
||||
/// <param name="digestAlgorithm">签名算法。(默认值:<see cref="DIGEST_ALGORITHM_SHA256"/>)</param>
|
||||
/// <returns>经过 Base64 编码的签名。</returns>
|
||||
public static EncodedString SignWithSHA256(string privateKeyPem, string messageData)
|
||||
{
|
||||
if (privateKeyPem is null) throw new ArgumentNullException(nameof(privateKeyPem));
|
||||
if (messageData is null) throw new ArgumentNullException(nameof(messageData));
|
||||
|
||||
byte[] privateKeyBytes = ConvertPrivateKeyPemToByteArray(privateKeyPem);
|
||||
byte[] messageBytes = EncodedString.FromLiteralString(messageData);
|
||||
byte[] signBytes = SignWithSHA256(privateKeyBytes, messageBytes);
|
||||
return EncodedString.ToBase64String(signBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用公钥基于 SHA-256 算法验证签名。
|
||||
/// </summary>
|
||||
/// <param name="publicKeyBytes">PKCS#8 公钥字节数组。</param>
|
||||
/// <param name="messageBytes">待验证的数据字节数组。</param>
|
||||
/// <param name="signBytes">签名字节数组。</param>
|
||||
/// <param name="digestAlgorithm">签名算法。(默认值:<see cref="DIGEST_ALGORITHM_SHA256"/>)</param>
|
||||
/// <returns>验证结果。</returns>
|
||||
public static bool VerifyWithSHA256(byte[] publicKeyBytes, byte[] messageBytes, byte[] signBytes)
|
||||
{
|
||||
if (publicKeyBytes is null) throw new ArgumentNullException(nameof(publicKeyBytes));
|
||||
if (messageBytes is null) throw new ArgumentNullException(nameof(messageBytes));
|
||||
if (signBytes is null) throw new ArgumentNullException(nameof(signBytes));
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
using (RSA rsa = RSA.Create())
|
||||
{
|
||||
rsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _);
|
||||
return rsa.VerifyData(messageBytes, signBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
}
|
||||
#else
|
||||
RsaKeyParameters rsaPublicKeyParams = ParsePublicKeyToParameters(publicKeyBytes);
|
||||
return Verify(rsaPublicKeyParams, messageBytes, signBytes, DIGEST_ALGORITHM_SHA256);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用公钥基于 SHA-256 算法验证签名。
|
||||
/// </summary>
|
||||
/// <param name="publicKeyPem">PKCS#1/PKCS#8 公钥(PEM 格式)。</param>
|
||||
/// <param name="messageData">待验证的数据。</param>
|
||||
/// <param name="encodingSignature">经过编码后的(通常为 Base64)签名。</param>
|
||||
/// <param name="digestAlgorithm">签名算法。(默认值:<see cref="DIGEST_ALGORITHM_SHA256"/>)</param>
|
||||
/// <returns>验证结果。</returns>
|
||||
public static bool VerifyWithSHA256(string publicKeyPem, string messageData, EncodedString encodingSignature)
|
||||
{
|
||||
if (publicKeyPem is null) throw new ArgumentNullException(nameof(publicKeyPem));
|
||||
if (messageData is null) throw new ArgumentNullException(nameof(messageData));
|
||||
if (encodingSignature.Value is null) throw new ArgumentNullException(nameof(encodingSignature));
|
||||
|
||||
byte[] publicKeyBytes = ConvertPublicKeyPemToByteArray(publicKeyPem);
|
||||
byte[] messageBytes = EncodedString.FromLiteralString(messageData);
|
||||
byte[] signBytes = EncodedString.FromString(encodingSignature, fallbackEncodingKind: EncodingKinds.Base64);
|
||||
return VerifyWithSHA256(publicKeyBytes, messageBytes, signBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
437
src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/SM2Utility.cs
Normal file
437
src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/SM2Utility.cs
Normal file
@@ -0,0 +1,437 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Org.BouncyCastle.Asn1;
|
||||
using Org.BouncyCastle.Asn1.GM;
|
||||
using Org.BouncyCastle.Asn1.X9;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Math;
|
||||
using Org.BouncyCastle.Security;
|
||||
using Org.BouncyCastle.Utilities;
|
||||
using Org.BouncyCastle.Utilities.Encoders;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
|
||||
/// <summary>
|
||||
/// SM2 算法工具类。
|
||||
/// <para>此实现遵循国家标准 GM/T 0009-2012 的有关规定。</para>
|
||||
/// </summary>
|
||||
public static class SM2Utility
|
||||
{
|
||||
private static readonly X9ECParameters SM2_ECX9_PARAMS = GMNamedCurves.GetByName("SM2P256v1");
|
||||
private static readonly ECDomainParameters SM2_DOMAIN_PARAMS = new ECDomainParameters(SM2_ECX9_PARAMS.Curve, SM2_ECX9_PARAMS.G, SM2_ECX9_PARAMS.N);
|
||||
private static readonly byte[] SM2_DEFAULT_UID = new byte[] { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38 };
|
||||
private const int SM2_RS_LENGTH = 32;
|
||||
|
||||
private static byte[] ConvertPrivateKeyPemToByteArray(string privateKeyPem)
|
||||
{
|
||||
privateKeyPem = privateKeyPem
|
||||
.Replace("-----BEGIN PRIVATE KEY-----", string.Empty)
|
||||
.Replace("-----END PRIVATE KEY-----", string.Empty);
|
||||
privateKeyPem = Regex.Replace(privateKeyPem, "\\s+", string.Empty);
|
||||
return Convert.FromBase64String(privateKeyPem);
|
||||
}
|
||||
|
||||
private static byte[] ConvertPublicKeyPemToByteArray(string publicKeyPem)
|
||||
{
|
||||
publicKeyPem = publicKeyPem
|
||||
.Replace("-----BEGIN PUBLIC KEY-----", string.Empty)
|
||||
.Replace("-----END PUBLIC KEY-----", string.Empty);
|
||||
publicKeyPem = Regex.Replace(publicKeyPem, "\\s+", string.Empty);
|
||||
return Convert.FromBase64String(publicKeyPem);
|
||||
}
|
||||
|
||||
private static ECPrivateKeyParameters ParsePrivateKeyToParameters(byte[] privateKeyBytes)
|
||||
{
|
||||
return (ECPrivateKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes);
|
||||
}
|
||||
|
||||
private static ECPrivateKeyParameters ParseECPrivateKeyToParameters(byte[] ecPrivateKeyBytes)
|
||||
{
|
||||
BigInteger ecPrivateKeyParamsD = new BigInteger(Hex.ToHexString(ecPrivateKeyBytes), 16);
|
||||
return new ECPrivateKeyParameters(ecPrivateKeyParamsD, SM2_DOMAIN_PARAMS);
|
||||
}
|
||||
|
||||
private static ECPublicKeyParameters ParsePublicKeyToParameters(byte[] publicKeyBytes)
|
||||
{
|
||||
return (ECPublicKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
|
||||
}
|
||||
|
||||
private static ECPublicKeyParameters ParseECPublicKeyToParameters(byte[] ecPublicKeyBytes)
|
||||
{
|
||||
const int KEY_BYTE_LENGTH = 64;
|
||||
|
||||
bool unzipped = ecPublicKeyBytes.FirstOrDefault() == 0x04;
|
||||
if (unzipped && ecPublicKeyBytes.Length != KEY_BYTE_LENGTH + 1)
|
||||
throw new ArgumentException($"Invalid key byte length (expected: {KEY_BYTE_LENGTH}, actual: {ecPublicKeyBytes.Length - 1}).", nameof(ecPublicKeyBytes));
|
||||
if (!unzipped && ecPublicKeyBytes.Length != KEY_BYTE_LENGTH)
|
||||
throw new ArgumentException($"Invalid key byte length (expected: {KEY_BYTE_LENGTH}, actual: {ecPublicKeyBytes.Length}).", nameof(ecPublicKeyBytes));
|
||||
|
||||
byte[] ecPublicKeyXBytes = new byte[KEY_BYTE_LENGTH / 2];
|
||||
byte[] ecPublicKeyYBytes = new byte[KEY_BYTE_LENGTH / 2];
|
||||
Buffer.BlockCopy(ecPublicKeyBytes, unzipped ? 1 : 0, ecPublicKeyXBytes, 0, ecPublicKeyXBytes.Length);
|
||||
Buffer.BlockCopy(ecPublicKeyBytes, ecPublicKeyXBytes.Length + (unzipped ? 1 : 0), ecPublicKeyYBytes, 0, ecPublicKeyYBytes.Length);
|
||||
|
||||
BigInteger ecPublicKeyParamsX = new BigInteger(Hex.ToHexString(ecPublicKeyXBytes), 16);
|
||||
BigInteger ecPublicKeyParamsY = new BigInteger(Hex.ToHexString(ecPublicKeyYBytes), 16);
|
||||
return new ECPublicKeyParameters(SM2_ECX9_PARAMS.Curve.CreatePoint(ecPublicKeyParamsX, ecPublicKeyParamsY), SM2_DOMAIN_PARAMS);
|
||||
}
|
||||
|
||||
private static byte[] ConvertRsToAsn1(byte[] rs)
|
||||
{
|
||||
BigInteger r = new BigInteger(1, Arrays.CopyOfRange(rs, 0, SM2_RS_LENGTH));
|
||||
BigInteger s = new BigInteger(1, Arrays.CopyOfRange(rs, SM2_RS_LENGTH, SM2_RS_LENGTH * 2));
|
||||
|
||||
Asn1EncodableVector vector = new Asn1EncodableVector();
|
||||
vector.Add(new DerInteger(r));
|
||||
vector.Add(new DerInteger(s));
|
||||
|
||||
DerSequence sequence = new DerSequence(vector);
|
||||
return sequence.GetEncoded("DER");
|
||||
}
|
||||
|
||||
private static byte[] ConvertAsn1ToRs(byte[] asn1)
|
||||
{
|
||||
Asn1Sequence sequence = Asn1Sequence.GetInstance(asn1);
|
||||
byte[] r = ConvertBigIntegerToFixedLengthByteArray(DerInteger.GetInstance(sequence[0]).Value);
|
||||
byte[] s = ConvertBigIntegerToFixedLengthByteArray(DerInteger.GetInstance(sequence[1]).Value);
|
||||
|
||||
byte[] tmp = new byte[SM2_RS_LENGTH * 2];
|
||||
Buffer.BlockCopy(r, 0, tmp, 0, r.Length);
|
||||
Buffer.BlockCopy(s, 0, tmp, SM2_RS_LENGTH, s.Length);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
private static byte[] ConvertBigIntegerToFixedLengthByteArray(BigInteger bigInt)
|
||||
{
|
||||
// For SM2P256v1, N is 00fffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123,
|
||||
// R and S are the tmp of mod N, so they should be less than N and have length <= 32
|
||||
|
||||
byte[] rs = bigInt.ToByteArray();
|
||||
if (rs.Length == SM2_RS_LENGTH)
|
||||
{
|
||||
return rs;
|
||||
}
|
||||
else if (rs.Length == SM2_RS_LENGTH + 1 && rs[0] == 0)
|
||||
{
|
||||
return Arrays.CopyOfRange(rs, 1, SM2_RS_LENGTH + 1);
|
||||
}
|
||||
else if (rs.Length < SM2_RS_LENGTH)
|
||||
{
|
||||
byte[] result = new byte[SM2_RS_LENGTH];
|
||||
Arrays.Fill(result, (byte)0);
|
||||
Buffer.BlockCopy(rs, 0, result, SM2_RS_LENGTH - rs.Length, rs.Length);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] SignWithSM3(ECPrivateKeyParameters sm2PrivateKeyParams, byte[] uidBytes, byte[] messageBytes, bool asn1Encoding)
|
||||
{
|
||||
ISigner signer = SignerUtilities.GetSigner("SM3withSM2");
|
||||
signer.Init(true, new ParametersWithID(sm2PrivateKeyParams, uidBytes));
|
||||
signer.BlockUpdate(messageBytes, 0, messageBytes.Length);
|
||||
byte[] signBytes = signer.GenerateSignature();
|
||||
|
||||
// BouncyCastle 库的签名结果默认 ASN.1 编码,如不需要需要手动转换
|
||||
if (!asn1Encoding)
|
||||
{
|
||||
signBytes = ConvertAsn1ToRs(signBytes);
|
||||
}
|
||||
|
||||
return signBytes;
|
||||
}
|
||||
|
||||
private static bool VerifyWithSM3(ECPublicKeyParameters sm2PublicKeyParams, byte[] uidBytes, byte[] messageBytes, byte[] signBytes, bool asn1Encoding)
|
||||
{
|
||||
ISigner signer = SignerUtilities.GetSigner("SM3withSM2");
|
||||
signer.Init(false, new ParametersWithID(sm2PublicKeyParams, uidBytes));
|
||||
signer.BlockUpdate(messageBytes, 0, messageBytes.Length);
|
||||
|
||||
// BouncyCastle 库的签名结果默认 ASN.1 编码,如不需要需要手动转换
|
||||
if (!asn1Encoding)
|
||||
{
|
||||
signBytes = ConvertRsToAsn1(signBytes);
|
||||
}
|
||||
|
||||
return signer.VerifySignature(signBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用私钥基于 SM3 算法生成签名。
|
||||
/// </summary>
|
||||
/// <param name="privateKeyBytes">PKCS#8 私钥字节数组。</param>
|
||||
/// <param name="uidBytes">用户标识符字节数组。</param>
|
||||
/// <param name="messageBytes">数据字节数组。</param>
|
||||
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。(默认值:true)</param>
|
||||
/// <returns>签名字节数组。</returns>
|
||||
public static byte[] SignWithSM3(byte[] privateKeyBytes, byte[] uidBytes, byte[] messageBytes, bool asn1Encoding = true)
|
||||
{
|
||||
if (privateKeyBytes is null) throw new ArgumentNullException(nameof(privateKeyBytes));
|
||||
if (uidBytes is null) throw new ArgumentNullException(nameof(uidBytes));
|
||||
if (messageBytes is null) throw new ArgumentNullException(nameof(messageBytes));
|
||||
|
||||
ECPrivateKeyParameters sm2PrivateKeyParams = ParsePrivateKeyToParameters(privateKeyBytes);
|
||||
return SignWithSM3(sm2PrivateKeyParams, uidBytes, messageBytes, asn1Encoding);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用私钥基于 SM3 算法生成签名。
|
||||
/// </summary>
|
||||
/// <param name="privateKeyBytes">PKCS#8 私钥字节数组。</param>
|
||||
/// <param name="messageBytes">待签名的数据字节数组。</param>
|
||||
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。(默认值:true)</param>
|
||||
/// <returns>签名字节数组。</returns>
|
||||
public static byte[] SignWithSM3(byte[] privateKeyBytes, byte[] messageBytes, bool asn1Encoding = true)
|
||||
{
|
||||
return SignWithSM3(
|
||||
privateKeyBytes: privateKeyBytes,
|
||||
uidBytes: SM2_DEFAULT_UID,
|
||||
messageBytes: messageBytes,
|
||||
asn1Encoding: asn1Encoding
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用私钥基于 SM3 算法生成签名。
|
||||
/// </summary>
|
||||
/// <param name="privateKeyPem">PKCS#8 私钥(PEM 格式)。</param>
|
||||
/// <param name="messageData">待签名的数据。</param>
|
||||
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。(默认值:true)</param>
|
||||
/// <returns>经过 Base64 编码的签名。</returns>
|
||||
public static EncodedString SignWithSM3(string privateKeyPem, string messageData, bool asn1Encoding = true)
|
||||
{
|
||||
if (privateKeyPem is null) throw new ArgumentNullException(nameof(privateKeyPem));
|
||||
if (messageData is null) throw new ArgumentNullException(nameof(messageData));
|
||||
|
||||
byte[] signBytes = SignWithSM3(
|
||||
privateKeyBytes: ConvertPrivateKeyPemToByteArray(privateKeyPem),
|
||||
messageBytes: EncodedString.FromLiteralString(messageData),
|
||||
asn1Encoding: asn1Encoding
|
||||
);
|
||||
return EncodedString.ToBase64String(signBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 EC 私钥基于 SM3 算法生成签名。
|
||||
/// </summary>
|
||||
/// <param name="ecPrivateKeyBytes">EC 私钥字节数组。</param>
|
||||
/// <param name="uidBytes">用户标识符字节数组。</param>
|
||||
/// <param name="messageBytes">待签名的数据字节数组。</param>
|
||||
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。(默认值:true)</param>
|
||||
/// <returns>签名字节数组。</returns>
|
||||
public static byte[] SignWithSM3ByECPrivateKey(byte[] ecPrivateKeyBytes, byte[] uidBytes, byte[] messageBytes, bool asn1Encoding = true)
|
||||
{
|
||||
if (ecPrivateKeyBytes is null) throw new ArgumentNullException(nameof(ecPrivateKeyBytes));
|
||||
if (uidBytes is null) throw new ArgumentNullException(nameof(uidBytes));
|
||||
if (messageBytes is null) throw new ArgumentNullException(nameof(messageBytes));
|
||||
|
||||
ECPrivateKeyParameters sm2PrivateKeyParams = ParseECPrivateKeyToParameters(ecPrivateKeyBytes);
|
||||
return SignWithSM3(sm2PrivateKeyParams, uidBytes, messageBytes, asn1Encoding);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 EC 私钥基于 SM3 算法生成签名。
|
||||
/// </summary>
|
||||
/// <param name="ecPrivateKeyBytes">EC 私钥字节数组。</param>
|
||||
/// <param name="messageBytes">待签名的数据字节数组。</param>
|
||||
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。(默认值:true)</param>
|
||||
/// <returns>签名字节数组。</returns>
|
||||
public static byte[] SignWithSM3ByECPrivateKey(byte[] ecPrivateKeyBytes, byte[] messageBytes, bool asn1Encoding = true)
|
||||
{
|
||||
return SignWithSM3ByECPrivateKey(
|
||||
ecPrivateKeyBytes: ecPrivateKeyBytes,
|
||||
uidBytes: SM2_DEFAULT_UID,
|
||||
messageBytes: messageBytes,
|
||||
asn1Encoding: asn1Encoding
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 EC 私钥基于 SM3 算法生成签名。
|
||||
/// </summary>
|
||||
/// <param name="encodingECPrivateKey">经过编码后的(通常为十六进制)EC 私钥。</param>
|
||||
/// <param name="uidBytes">用户标识符字节数组。</param>
|
||||
/// <param name="messageBytes">待签名的数据字节数组。</param>
|
||||
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。(默认值:true)</param>
|
||||
/// <returns>签名字节数组。</returns>
|
||||
public static byte[] SignWithSM3ByECPrivateKey(EncodedString encodingECPrivateKey, byte[] uidBytes, byte[] messageBytes, bool asn1Encoding = true)
|
||||
{
|
||||
if (encodingECPrivateKey.Value is null) throw new ArgumentNullException(nameof(encodingECPrivateKey));
|
||||
if (uidBytes is null) throw new ArgumentNullException(nameof(uidBytes));
|
||||
if (messageBytes is null) throw new ArgumentNullException(nameof(messageBytes));
|
||||
|
||||
return SignWithSM3ByECPrivateKey(
|
||||
ecPrivateKeyBytes: EncodedString.FromString(encodingECPrivateKey, fallbackEncodingKind: EncodingKinds.Hex),
|
||||
uidBytes: uidBytes,
|
||||
messageBytes: messageBytes,
|
||||
asn1Encoding: asn1Encoding
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 EC 私钥基于 SM3 算法生成签名。
|
||||
/// </summary>
|
||||
/// <param name="encodingECPrivateKey">经过编码后的(通常为十六进制)EC 私钥。</param>
|
||||
/// <param name="messageBytes">待签名的数据字节数组。</param>
|
||||
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。(默认值:true)</param>
|
||||
/// <returns>签名字节数组。</returns>
|
||||
public static byte[] SignWithSM3ByECPrivateKey(EncodedString encodingECPrivateKey, byte[] messageBytes, bool asn1Encoding = true)
|
||||
{
|
||||
return SignWithSM3ByECPrivateKey(
|
||||
encodingECPrivateKey: encodingECPrivateKey,
|
||||
uidBytes: SM2_DEFAULT_UID,
|
||||
messageBytes: messageBytes,
|
||||
asn1Encoding: asn1Encoding
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用公钥基于 SM3 算法验证签名。
|
||||
/// </summary>
|
||||
/// <param name="publicKeyBytes">PKCS#8 公钥字节数组。</param>
|
||||
/// <param name="uidBytes">用户标识符字节数组。</param>
|
||||
/// <param name="messageBytes">待验证的数据字节数组。</param>
|
||||
/// <param name="signBytes">签名字节数组。</param>
|
||||
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。(默认值:true)</param>
|
||||
/// <returns>验证结果。</returns>
|
||||
public static bool VerifyWithSM3(byte[] publicKeyBytes, byte[] uidBytes, byte[] messageBytes, byte[] signBytes, bool asn1Encoding = true)
|
||||
{
|
||||
if (publicKeyBytes is null) throw new ArgumentNullException(nameof(publicKeyBytes));
|
||||
if (uidBytes is null) throw new ArgumentNullException(nameof(uidBytes));
|
||||
if (messageBytes is null) throw new ArgumentNullException(nameof(messageBytes));
|
||||
if (signBytes is null) throw new ArgumentNullException(nameof(signBytes));
|
||||
|
||||
ECPublicKeyParameters sm2PublicKeyParams = ParsePublicKeyToParameters(publicKeyBytes);
|
||||
return VerifyWithSM3(sm2PublicKeyParams, uidBytes, messageBytes, signBytes, asn1Encoding);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用公钥基于 SM3 算法验证签名。
|
||||
/// </summary>
|
||||
/// <param name="publicKeyBytes">PKCS#8 公钥字节数组。</param>
|
||||
/// <param name="messageBytes">待验证的数据字节数组。</param>
|
||||
/// <param name="signBytes">签名字节数组。</param>
|
||||
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。(默认值:true)</param>
|
||||
/// <returns>验证结果。</returns>
|
||||
public static bool VerifyWithSM3(byte[] publicKeyBytes, byte[] messageBytes, byte[] signBytes, bool asn1Encoding = true)
|
||||
{
|
||||
return VerifyWithSM3(
|
||||
publicKeyBytes: publicKeyBytes,
|
||||
uidBytes: SM2_DEFAULT_UID,
|
||||
messageBytes: messageBytes,
|
||||
signBytes: signBytes,
|
||||
asn1Encoding: asn1Encoding
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用公钥基于 SM3 算法验证签名。
|
||||
/// </summary>
|
||||
/// <param name="publicKeyPem">PKCS#8 公钥(PEM 格式)。</param>
|
||||
/// <param name="messageData">待验证的数据。</param>
|
||||
/// <param name="encodingSignature">经过编码后的(通常为 Base64)签名。</param>
|
||||
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。(默认值:true)</param>
|
||||
/// <returns>验证结果。</returns>
|
||||
public static bool VerifyWithSM3(string publicKeyPem, string messageData, EncodedString encodingSignature, bool asn1Encoding = true)
|
||||
{
|
||||
if (publicKeyPem is null) throw new ArgumentNullException(nameof(publicKeyPem));
|
||||
if (messageData is null) throw new ArgumentNullException(nameof(messageData));
|
||||
if (encodingSignature.Value is null) throw new ArgumentNullException(nameof(encodingSignature));
|
||||
|
||||
return VerifyWithSM3(
|
||||
publicKeyBytes: ConvertPublicKeyPemToByteArray(publicKeyPem),
|
||||
messageBytes: EncodedString.FromLiteralString(messageData),
|
||||
signBytes: EncodedString.FromString(encodingSignature, fallbackEncodingKind: EncodingKinds.Base64),
|
||||
asn1Encoding: asn1Encoding
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 EC 公钥基于 SM3 算法生成签名。
|
||||
/// </summary>
|
||||
/// <param name="ecPublicKeyBytes">EC 公钥字节数组。</param>
|
||||
/// <param name="uidBytes">用户标识符字节数组。</param>
|
||||
/// <param name="messageBytes">待验证的数据字节数组。</param>
|
||||
/// <param name="signBytes">签名字节数组。</param>
|
||||
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。(默认值:true)</param>
|
||||
/// <returns>签名字节数组。</returns>
|
||||
public static bool VerifyWithSM3ByECPublicKey(byte[] ecPublicKeyBytes, byte[] uidBytes, byte[] messageBytes, byte[] signBytes, bool asn1Encoding = true)
|
||||
{
|
||||
if (ecPublicKeyBytes is null) throw new ArgumentNullException(nameof(ecPublicKeyBytes));
|
||||
if (uidBytes is null) throw new ArgumentNullException(nameof(uidBytes));
|
||||
if (messageBytes is null) throw new ArgumentNullException(nameof(messageBytes));
|
||||
|
||||
ECPublicKeyParameters sm2PublicKeyParams = ParseECPublicKeyToParameters(ecPublicKeyBytes);
|
||||
return VerifyWithSM3(sm2PublicKeyParams, uidBytes, messageBytes, signBytes, asn1Encoding);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 EC 公钥基于 SM3 算法生成签名。
|
||||
/// </summary>
|
||||
/// <param name="ecPublicKeyBytes">EC 公钥字节数组。</param>
|
||||
/// <param name="messageBytes">待验证的数据字节数组。</param>
|
||||
/// <param name="signBytes">签名字节数组。</param>
|
||||
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。(默认值:true)</param>
|
||||
/// <returns>签名字节数组。</returns>
|
||||
public static bool VerifyWithSM3ByECPublicKey(byte[] ecPublicKeyBytes, byte[] messageBytes, byte[] signBytes, bool asn1Encoding = true)
|
||||
{
|
||||
return VerifyWithSM3ByECPublicKey(
|
||||
ecPublicKeyBytes: ecPublicKeyBytes,
|
||||
uidBytes: SM2_DEFAULT_UID,
|
||||
messageBytes: messageBytes,
|
||||
signBytes: signBytes,
|
||||
asn1Encoding: asn1Encoding
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 EC 公钥基于 SM3 算法生成签名。
|
||||
/// </summary>
|
||||
/// <param name="encodingECPublicKey">经过编码后的(通常为十六进制)EC 公钥。</param>
|
||||
/// <param name="uidBytes">用户标识符字节数组。</param>
|
||||
/// <param name="messageBytes">待验证的数据字节数组。</param>
|
||||
/// <param name="signBytes">签名字节数组。</param>
|
||||
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。(默认值:true)</param>
|
||||
/// <returns>签名字节数组。</returns>
|
||||
public static bool VerifyWithSM3ByECPublicKey(EncodedString encodingECPublicKey, byte[] uidBytes, byte[] messageBytes, byte[] signBytes, bool asn1Encoding = true)
|
||||
{
|
||||
if (encodingECPublicKey.Value is null) throw new ArgumentNullException(nameof(encodingECPublicKey));
|
||||
if (uidBytes is null) throw new ArgumentNullException(nameof(uidBytes));
|
||||
if (messageBytes is null) throw new ArgumentNullException(nameof(messageBytes));
|
||||
|
||||
return VerifyWithSM3ByECPublicKey(
|
||||
ecPublicKeyBytes: EncodedString.FromString(encodingECPublicKey, fallbackEncodingKind: EncodingKinds.Hex),
|
||||
uidBytes: uidBytes,
|
||||
messageBytes: messageBytes,
|
||||
signBytes: signBytes,
|
||||
asn1Encoding: asn1Encoding
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 EC 公钥基于 SM3 算法生成签名。
|
||||
/// </summary>
|
||||
/// <param name="encodingECPublicKey">经过编码后的(通常为十六进制)EC 公钥。</param>
|
||||
/// <param name="messageBytes">待验证的数据字节数组。</param>
|
||||
/// <param name="signBytes">签名字节数组。</param>
|
||||
/// <param name="asn1Encoding">指示签名结果是否为 ASN.1 编码的形式。(默认值:true)</param>
|
||||
/// <returns>签名字节数组。</returns>
|
||||
public static bool VerifyWithSM3ByECPublicKey(EncodedString encodingECPublicKey, byte[] messageBytes, byte[] signBytes, bool asn1Encoding = true)
|
||||
{
|
||||
return VerifyWithSM3ByECPublicKey(
|
||||
encodingECPublicKey: encodingECPublicKey,
|
||||
uidBytes: SM2_DEFAULT_UID,
|
||||
messageBytes: messageBytes,
|
||||
signBytes: signBytes,
|
||||
asn1Encoding: asn1Encoding
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
138
src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/SM4Utility.cs
Normal file
138
src/SKIT.FlurlHttpClient.Wechat.Api/Utilities/SM4Utility.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Security;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
|
||||
/// <summary>
|
||||
/// SM4 算法工具类。
|
||||
/// </summary>
|
||||
public static class SM4Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// 填充模式:NoPadding。
|
||||
/// </summary>
|
||||
public const string PADDING_MODE_NOPADDING = "NoPadding";
|
||||
|
||||
/// <summary>
|
||||
/// 基于 GCM 模式解密数据。
|
||||
/// </summary>
|
||||
/// <param name="keyBytes">SM4 密钥字节数组。</param>
|
||||
/// <param name="nonceBytes">初始化向量字节数组。</param>
|
||||
/// <param name="associatedDataBytes">附加数据字节数组。</param>
|
||||
/// <param name="cipherBytes">待解密数据字节数组。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_NOPADDING"/>)</param>
|
||||
/// <returns>解密后的数据字节数组。</returns>
|
||||
public static byte[] DecryptWithGCM(byte[] keyBytes, byte[] nonceBytes, byte[]? associatedDataBytes, byte[] cipherBytes, string paddingMode = PADDING_MODE_NOPADDING)
|
||||
{
|
||||
const int KEY_LENGTH_BYTE = 16;
|
||||
const int NONCE_LENGTH_BYTE = 12;
|
||||
const int TAG_LENGTH_BYTE = 16;
|
||||
|
||||
if (keyBytes is 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 is 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 is 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($"SM4/GCM/{paddingMode}");
|
||||
ICipherParameters cipherParams = new AeadParameters(
|
||||
new KeyParameter(keyBytes),
|
||||
TAG_LENGTH_BYTE * 8,
|
||||
nonceBytes,
|
||||
associatedDataBytes
|
||||
);
|
||||
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="encodingKey">经过编码后的(通常为 Base64)SM4 密钥。</param>
|
||||
/// <param name="encodingNonce">经过编码后的(通常为 Base64)初始化向量。</param>
|
||||
/// <param name="encodingAssociatedData">经过编码后的(通常为 Base64)附加数据。</param>
|
||||
/// <param name="encodingCipher">经过编码后的(通常为 Base64)待解密数据。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_NOPADDING"/>)</param>
|
||||
/// <returns>解密后的数据。</returns>
|
||||
public static EncodedString DecryptWithGCM(EncodedString encodingKey, EncodedString encodingNonce, EncodedString encodingAssociatedData, EncodedString encodingCipher, string paddingMode = PADDING_MODE_NOPADDING)
|
||||
{
|
||||
if (encodingKey.Value is null) throw new ArgumentNullException(nameof(encodingKey));
|
||||
if (encodingNonce.Value is null) throw new ArgumentNullException(nameof(encodingNonce));
|
||||
if (encodingCipher.Value is null) throw new ArgumentNullException(nameof(encodingCipher));
|
||||
|
||||
byte[] plainBytes = DecryptWithGCM(
|
||||
keyBytes: EncodedString.FromString(encodingKey, fallbackEncodingKind: EncodingKinds.Base64),
|
||||
nonceBytes: EncodedString.FromString(encodingNonce, fallbackEncodingKind: EncodingKinds.Base64),
|
||||
associatedDataBytes: encodingAssociatedData.Value is not null ? EncodedString.FromString(encodingAssociatedData, fallbackEncodingKind: EncodingKinds.Base64) : null,
|
||||
cipherBytes: EncodedString.FromString(encodingCipher, fallbackEncodingKind: EncodingKinds.Base64),
|
||||
paddingMode: paddingMode
|
||||
);
|
||||
return EncodedString.ToLiteralString(plainBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 GCM 模式加密数据。
|
||||
/// </summary>
|
||||
/// <param name="keyBytes">SM4 密钥字节数组。</param>
|
||||
/// <param name="nonceBytes">初始化向量字节数组。</param>
|
||||
/// <param name="associatedDataBytes">附加数据字节数组。</param>
|
||||
/// <param name="plainBytes">待加密数据字节数组。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_NOPADDING"/>)</param>
|
||||
/// <returns>加密后的数据字节数组。</returns>
|
||||
public static byte[] EncryptWithGCM(byte[] keyBytes, byte[] nonceBytes, byte[]? associatedDataBytes, byte[] plainBytes, string paddingMode = PADDING_MODE_NOPADDING)
|
||||
{
|
||||
const int TAG_LENGTH_BYTE = 16;
|
||||
|
||||
if (keyBytes is null) throw new ArgumentNullException(nameof(keyBytes));
|
||||
if (nonceBytes is null) throw new ArgumentNullException(nameof(nonceBytes));
|
||||
if (plainBytes is null) throw new ArgumentNullException(nameof(plainBytes));
|
||||
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"SM4/GCM/{paddingMode}");
|
||||
ICipherParameters cipherParams = new AeadParameters(
|
||||
new KeyParameter(keyBytes),
|
||||
TAG_LENGTH_BYTE * 8,
|
||||
nonceBytes,
|
||||
associatedDataBytes
|
||||
);
|
||||
cipher.Init(true, cipherParams);
|
||||
byte[] cipherBytes = new byte[cipher.GetOutputSize(plainBytes.Length)];
|
||||
int len = cipher.ProcessBytes(plainBytes, 0, plainBytes.Length, cipherBytes, 0);
|
||||
cipher.DoFinal(cipherBytes, len);
|
||||
return cipherBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 GCM 模式加密数据。
|
||||
/// </summary>
|
||||
/// <param name="encodingKey">经过编码后的(通常为 Base64)SM4 密钥。</param>
|
||||
/// <param name="encodingNonce">经过编码后的(通常为 Base64)初始化向量。</param>
|
||||
/// <param name="encodingAssociatedData">经过编码后的(通常为 Base64)附加数据。</param>
|
||||
/// <param name="plainData">待加密数据。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="PADDING_MODE_NOPADDING"/>)</param>
|
||||
/// <returns>经过 Base64 编码的加密后的数据。</returns>
|
||||
public static EncodedString EncryptWithGCM(EncodedString encodingKey, EncodedString encodingNonce, EncodedString encodingAssociatedData, string plainData, string paddingMode = PADDING_MODE_NOPADDING)
|
||||
{
|
||||
if (encodingKey.Value is null) throw new ArgumentNullException(nameof(encodingKey));
|
||||
if (encodingNonce.Value is null) throw new ArgumentNullException(nameof(encodingNonce));
|
||||
if (plainData is null) throw new ArgumentNullException(nameof(plainData));
|
||||
|
||||
byte[] plainBytes = EncryptWithGCM(
|
||||
keyBytes: EncodedString.FromString(encodingKey, fallbackEncodingKind: EncodingKinds.Base64),
|
||||
nonceBytes: EncodedString.FromString(encodingNonce, fallbackEncodingKind: EncodingKinds.Base64),
|
||||
associatedDataBytes: encodingAssociatedData.Value is not null ? EncodedString.FromString(encodingAssociatedData, fallbackEncodingKind: EncodingKinds.Base64) : null,
|
||||
plainBytes: Encoding.UTF8.GetBytes(plainData),
|
||||
paddingMode: paddingMode
|
||||
);
|
||||
return EncodedString.ToBase64String(plainBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user