feat(wxapi): 新增安全鉴权模式所需的 AES、RSA、SM2、SM4 等算法工具类

This commit is contained in:
Fu Diwei
2024-05-21 11:31:17 +08:00
committed by RHQYZ
parent 2bd10cc533
commit 7fff2b70ec
9 changed files with 1099 additions and 2 deletions

View File

@@ -40,6 +40,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BouncyCastle.Cryptography" Version="2.3.1" />
<PackageReference Include="SKIT.FlurlHttpClient.Common" Version="3.0.0" />
</ItemGroup>

View File

@@ -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">经过编码后的(通常为 Base64AES 密钥。</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">经过编码后的(通常为 Base64AES 密钥。</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);
}
}
}

View 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);
}
}
}

View 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
);
}
}
}

View 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">经过编码后的(通常为 Base64SM4 密钥。</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">经过编码后的(通常为 Base64SM4 密钥。</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);
}
}
}