diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Utilities/RSAUtility.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Utilities/RSAUtility.cs index 58af4a62..cf457301 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Utilities/RSAUtility.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Utilities/RSAUtility.cs @@ -16,7 +16,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities private const string RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1 = "OAEPWITHSHA1ANDMGF1PADDING"; private const string RSA_SIGNER_ALGORITHM_SHA256 = "SHA-256withRSA"; - private static byte[] ConvertPkcs8PrivateKeyToByteArray(string privateKey) + private static byte[] ConvertPrivateKeyPkcs8PemToByteArray(string privateKey) { privateKey = privateKey .Replace("-----BEGIN PRIVATE KEY-----", string.Empty) @@ -25,7 +25,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities return Convert.FromBase64String(privateKey); } - private static byte[] ConvertPkcs8PublicKeyToByteArray(string publicKey) + private static byte[] ConvertPublicKeyPkcs8PemToByteArray(string publicKey) { publicKey = publicKey .Replace("-----BEGIN PUBLIC KEY-----", string.Empty) @@ -75,8 +75,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities if (privateKeyBytes == null) throw new ArgumentNullException(nameof(privateKeyBytes)); if (msgBytes == null) throw new ArgumentNullException(nameof(msgBytes)); - RsaKeyParameters rsaKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes); - return SignWithSHA256(rsaKeyParams, msgBytes); + RsaKeyParameters rsaPrivateKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes); + return SignWithSHA256(rsaPrivateKeyParams, msgBytes); } /// @@ -90,7 +90,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities if (privateKey == null) throw new ArgumentNullException(nameof(privateKey)); if (message == null) throw new ArgumentNullException(nameof(message)); - byte[] privateKeyBytes = ConvertPkcs8PrivateKeyToByteArray(privateKey); + byte[] privateKeyBytes = ConvertPrivateKeyPkcs8PemToByteArray(privateKey); byte[] msgBytes = Encoding.UTF8.GetBytes(message); byte[] signBytes = SignWithSHA256(privateKeyBytes, msgBytes); return Convert.ToBase64String(signBytes); @@ -109,8 +109,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities 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, msgBytes, signBytes); + RsaKeyParameters rsaPublicKeyParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes); + return VerifyWithSHA256(rsaPublicKeyParams, msgBytes, signBytes); } /// @@ -126,7 +126,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities if (message == null) throw new ArgumentNullException(nameof(message)); if (signature == null) throw new ArgumentNullException(nameof(signature)); - byte[] publicKeyBytes = ConvertPkcs8PublicKeyToByteArray(publicKey); + byte[] publicKeyBytes = ConvertPublicKeyPkcs8PemToByteArray(publicKey); byte[] msgBytes = Encoding.UTF8.GetBytes(message); byte[] signBytes = Convert.FromBase64String(signature); return VerifyWithSHA256(publicKeyBytes, msgBytes, signBytes); @@ -144,8 +144,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities if (privateKeyBytes == null) throw new ArgumentNullException(nameof(privateKeyBytes)); if (cipherBytes == null) throw new ArgumentNullException(nameof(cipherBytes)); - RsaKeyParameters rsaKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes); - return DecryptWithECB(rsaKeyParams, cipherBytes, paddingAlgorithm); + RsaKeyParameters rsaPrivateKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes); + return DecryptWithECB(rsaPrivateKeyParams, cipherBytes, paddingAlgorithm); } /// @@ -160,7 +160,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities if (privateKey == null) throw new ArgumentNullException(nameof(privateKey)); if (cipherText == null) throw new ArgumentNullException(nameof(cipherText)); - byte[] privateKeyBytes = ConvertPkcs8PrivateKeyToByteArray(privateKey); + byte[] privateKeyBytes = ConvertPrivateKeyPkcs8PemToByteArray(privateKey); byte[] cipherBytes = Convert.FromBase64String(cipherText); byte[] plainBytes = DecryptWithECB(privateKeyBytes, cipherBytes, paddingAlgorithm); return Encoding.UTF8.GetString(plainBytes); @@ -178,8 +178,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities if (publicKeyBytes == null) throw new ArgumentNullException(nameof(publicKeyBytes)); if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes)); - RsaKeyParameters rsaKeyParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes); - return EncryptWithECB(rsaKeyParams, plainBytes, paddingAlgorithm); + RsaKeyParameters rsaPublicKeyParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes); + return EncryptWithECB(rsaPublicKeyParams, plainBytes, paddingAlgorithm); } /// @@ -194,7 +194,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities if (publicKey == null) throw new ArgumentNullException(nameof(publicKey)); if (plainText == null) throw new ArgumentNullException(nameof(plainText)); - byte[] publicKeyBytes = ConvertPkcs8PublicKeyToByteArray(publicKey); + byte[] publicKeyBytes = ConvertPublicKeyPkcs8PemToByteArray(publicKey); byte[] plainBytes = Encoding.UTF8.GetBytes(plainText); byte[] cipherBytes = EncryptWithECB(publicKeyBytes, plainBytes, paddingAlgorithm); return Convert.ToBase64String(cipherBytes); diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/RSAUtility.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/RSAUtility.cs index 00c42a9a..eb856014 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/RSAUtility.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/RSAUtility.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using Org.BouncyCastle.Crypto; @@ -15,13 +16,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities /// public static class RSAUtility { - // REF: https://github.com/bcgit/bc-csharp/blob/master/crypto/src/security/CipherUtilities.cs private const string RSA_CIPHER_ALGORITHM_ECB = "RSA/ECB"; private const string RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1 = "OAEPWITHSHA1ANDMGF1PADDING"; - // REF: https://github.com/bcgit/bc-csharp/blob/master/crypto/src/security/SignerUtilities.cs private const string RSA_SIGNER_ALGORITHM_SHA256 = "SHA-256withRSA"; - private static byte[] ConvertPkcs8PrivateKeyToByteArray(string privateKey) + private static byte[] ConvertPrivateKeyPkcs8PemToByteArray(string privateKey) { privateKey = privateKey .Replace("-----BEGIN PRIVATE KEY-----", string.Empty) @@ -30,7 +29,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities return Convert.FromBase64String(privateKey); } - private static byte[] ConvertPkcs8PublicKeyToByteArray(string publicKey) + private static byte[] ConvertPublicKeyPkcs8PemToByteArray(string publicKey) { publicKey = publicKey .Replace("-----BEGIN PUBLIC KEY-----", string.Empty) @@ -39,7 +38,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities return Convert.FromBase64String(publicKey); } - private static X509Certificate ParseX509Certificate(string certificate) + private static X509Certificate ParseCertificatePemToX509(string certificate) { using (TextReader sreader = new StringReader(certificate)) { @@ -48,10 +47,14 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities } } - private static RsaKeyParameters ConvertCertificateToPublicKeyParams(string certificate) + private static RsaKeyParameters ParsePrivateKeyPemToPublicKeyParameters(byte[] privateKeyBytes) { - X509Certificate cert = ParseX509Certificate(certificate); - return (RsaKeyParameters)cert.GetPublicKey(); + return (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes); + } + + private static RsaKeyParameters ParsePublicKeyPemToPublicKeyParameters(byte[] publicKeyBytes) + { + return (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes); } private static byte[] SignWithSHA256(RsaKeyParameters rsaPrivateKeyParams, byte[] msgBytes) @@ -95,8 +98,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities if (privateKeyBytes == null) throw new ArgumentNullException(nameof(privateKeyBytes)); if (msgBytes == null) throw new ArgumentNullException(nameof(msgBytes)); - RsaKeyParameters rsaKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes); - return SignWithSHA256(rsaKeyParams, msgBytes); + RsaKeyParameters rsaPrivateKeyParams = ParsePrivateKeyPemToPublicKeyParameters(privateKeyBytes); + return SignWithSHA256(rsaPrivateKeyParams, msgBytes); } /// @@ -110,7 +113,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities if (privateKey == null) throw new ArgumentNullException(nameof(privateKey)); if (message == null) throw new ArgumentNullException(nameof(message)); - byte[] privateKeyBytes = ConvertPkcs8PrivateKeyToByteArray(privateKey); + byte[] privateKeyBytes = ConvertPrivateKeyPkcs8PemToByteArray(privateKey); byte[] msgBytes = Encoding.UTF8.GetBytes(message); byte[] signBytes = SignWithSHA256(privateKeyBytes, msgBytes); return Convert.ToBase64String(signBytes); @@ -129,8 +132,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities 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, msgBytes, signBytes); + RsaKeyParameters rsaPublicKeyParams = ParsePublicKeyPemToPublicKeyParameters(publicKeyBytes); + return VerifyWithSHA256(rsaPublicKeyParams, msgBytes, signBytes); } /// @@ -146,7 +149,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities if (message == null) throw new ArgumentNullException(nameof(message)); if (signature == null) throw new ArgumentNullException(nameof(signature)); - byte[] publicKeyBytes = ConvertPkcs8PublicKeyToByteArray(publicKey); + byte[] publicKeyBytes = ConvertPublicKeyPkcs8PemToByteArray(publicKey); byte[] msgBytes = Encoding.UTF8.GetBytes(message); byte[] signBytes = Convert.FromBase64String(signature); return VerifyWithSHA256(publicKeyBytes, msgBytes, signBytes); @@ -162,13 +165,43 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities public static bool VerifyWithSHA256ByCertificate(string certificate, string message, string signature) { if (certificate == null) throw new ArgumentNullException(nameof(certificate)); - if (message == null) throw new ArgumentNullException(nameof(message)); - if (signature == null) throw new ArgumentNullException(nameof(signature)); - RsaKeyParameters rsaKeyParams = ConvertCertificateToPublicKeyParams(certificate); - byte[] msgBytes = Encoding.UTF8.GetBytes(message); - byte[] signBytes = Convert.FromBase64String(signature); - return VerifyWithSHA256(rsaKeyParams, msgBytes, signBytes); + string publicKey = ExportPublicKeyFromCertificate(certificate); + return VerifyWithSHA256(publicKey, message, signature); + } + + /// + /// 使用私钥基于 ECB 模式解密数据。 + /// + /// PKCS#8 私钥字节数据。 + /// 待解密的数据字节数据。 + /// 填充模式。(默认值:) + /// 解密后的数据字节数组。 + public static byte[] DecryptWithECB(byte[] privateKeyBytes, byte[] cipherBytes, string paddingMode = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1) + { + if (privateKeyBytes == null) throw new ArgumentNullException(nameof(privateKeyBytes)); + if (cipherBytes == null) throw new ArgumentNullException(nameof(cipherBytes)); + + RsaKeyParameters rsaPrivateKeyParams = ParsePrivateKeyPemToPublicKeyParameters(privateKeyBytes); + return DecryptWithECB(rsaPrivateKeyParams, cipherBytes, paddingMode); + } + + /// + /// 使用私钥基于 ECB 模式解密数据。 + /// + /// PKCS#8 私钥(PEM 格式)。 + /// 经 Base64 编码的待解密数据。 + /// 填充模式。(默认值:) + /// 解密后的文本数据。 + public static string DecryptWithECB(string privateKey, string cipherText, string paddingMode = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1) + { + if (privateKey == null) throw new ArgumentNullException(nameof(privateKey)); + if (cipherText == null) throw new ArgumentNullException(nameof(cipherText)); + + byte[] privateKeyBytes = ConvertPrivateKeyPkcs8PemToByteArray(privateKey); + byte[] cipherBytes = Convert.FromBase64String(cipherText); + byte[] plainBytes = DecryptWithECB(privateKeyBytes, cipherBytes, paddingMode); + return Encoding.UTF8.GetString(plainBytes); } /// @@ -183,8 +216,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities if (publicKeyBytes == null) throw new ArgumentNullException(nameof(publicKeyBytes)); if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes)); - RsaKeyParameters rsaKeyParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes); - return EncryptWithECB(rsaKeyParams, plainBytes, paddingMode); + RsaKeyParameters rsaPublicKeyParams = ParsePublicKeyPemToPublicKeyParameters(publicKeyBytes); + return EncryptWithECB(rsaPublicKeyParams, plainBytes, paddingMode); } /// @@ -199,7 +232,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities if (publicKey == null) throw new ArgumentNullException(nameof(publicKey)); if (plainText == null) throw new ArgumentNullException(nameof(plainText)); - byte[] publicKeyBytes = ConvertPkcs8PublicKeyToByteArray(publicKey); + byte[] publicKeyBytes = ConvertPublicKeyPkcs8PemToByteArray(publicKey); byte[] plainBytes = Encoding.UTF8.GetBytes(plainText); byte[] cipherBytes = EncryptWithECB(publicKeyBytes, plainBytes, paddingMode); return Convert.ToBase64String(cipherBytes); @@ -215,46 +248,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities public static string EncryptWithECBByCertificate(string certificate, string plainText, string paddingMode = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1) { if (certificate == null) throw new ArgumentNullException(nameof(certificate)); - if (plainText == null) throw new ArgumentNullException(nameof(plainText)); - RsaKeyParameters rsaKeyParams = ConvertCertificateToPublicKeyParams(certificate); - byte[] plainBytes = Encoding.UTF8.GetBytes(plainText); - byte[] cipherBytes = EncryptWithECB(rsaKeyParams, plainBytes, paddingMode); - return Convert.ToBase64String(cipherBytes); - } - - /// - /// 使用私钥基于 ECB 模式解密数据。 - /// - /// PKCS#8 私钥字节数据。 - /// 待解密的数据字节数据。 - /// 填充模式。(默认值:) - /// 解密后的数据字节数组。 - public static byte[] DecryptWithECB(byte[] privateKeyBytes, byte[] cipherBytes, string paddingMode = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1) - { - if (privateKeyBytes == null) throw new ArgumentNullException(nameof(privateKeyBytes)); - if (cipherBytes == null) throw new ArgumentNullException(nameof(cipherBytes)); - - RsaKeyParameters rsaKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes); - return DecryptWithECB(rsaKeyParams, cipherBytes, paddingMode); - } - - /// - /// 使用私钥基于 ECB 模式解密数据。 - /// - /// PKCS#8 私钥(PEM 格式)。 - /// 经 Base64 编码的待解密数据。 - /// 填充模式。(默认值:) - /// 解密后的文本数据。 - public static string DecryptWithECB(string privateKey, string cipherText, string paddingMode = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1) - { - if (privateKey == null) throw new ArgumentNullException(nameof(privateKey)); - if (cipherText == null) throw new ArgumentNullException(nameof(cipherText)); - - byte[] privateKeyBytes = ConvertPkcs8PrivateKeyToByteArray(privateKey); - byte[] cipherBytes = Convert.FromBase64String(cipherText); - byte[] plainBytes = DecryptWithECB(privateKeyBytes, cipherBytes, paddingMode); - return Encoding.UTF8.GetString(plainBytes); + string publicKey = ExportPublicKeyFromCertificate(certificate); + return EncryptWithECB(publicKey, plainText, paddingMode); } /// @@ -272,9 +268,10 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities using (TextWriter swriter = new StringWriter()) { - RsaKeyParameters rsaKeyParams = ConvertCertificateToPublicKeyParams(certificate); + X509Certificate x509cert = ParseCertificatePemToX509(certificate); + RsaKeyParameters rsaPublicKeyParams = (RsaKeyParameters)x509cert.GetPublicKey(); PemWriter pemWriter = new PemWriter(swriter); - pemWriter.WriteObject(rsaKeyParams); + pemWriter.WriteObject(rsaPublicKeyParams); pemWriter.Writer.Flush(); return swriter.ToString()!; } @@ -289,8 +286,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities { if (certificate == null) throw new ArgumentNullException(nameof(certificate)); - X509Certificate cert = ParseX509Certificate(certificate); - return cert.SerialNumber.ToString(16); + X509Certificate x509cert = ParseCertificatePemToX509(certificate); + return x509cert.SerialNumber.ToString(16); } /// @@ -302,8 +299,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities { if (certificate == null) throw new ArgumentNullException(nameof(certificate)); - X509Certificate cert = ParseX509Certificate(certificate); - return new DateTimeOffset(cert.NotBefore); + X509Certificate x509cert = ParseCertificatePemToX509(certificate); + return new DateTimeOffset(x509cert.NotBefore); } /// @@ -315,8 +312,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities { if (certificate == null) throw new ArgumentNullException(nameof(certificate)); - X509Certificate cert = ParseX509Certificate(certificate); - return new DateTimeOffset(cert.NotAfter); + X509Certificate x509cert = ParseCertificatePemToX509(certificate); + return new DateTimeOffset(x509cert.NotAfter); } } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM2Utility.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM2Utility.cs index a48a31d9..a59f71b7 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM2Utility.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/SM2Utility.cs @@ -3,28 +3,50 @@ using System.IO; using System.Linq; using System.Text; 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.Digests; -using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Engines; 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.Utilities; +using Org.BouncyCastle.Utilities.Encoders; using Org.BouncyCastle.X509; namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities { /// /// SM2 算法工具类。 + /// 此实现遵循国家标准 GM/T 0009-2012 的有关规定。 /// - public static partial class SM2Utility + public static 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 readonly X9ECParameters _ecX9Parameters = GMNamedCurves.GetByName("SM2P256v1"); + private static readonly ECDomainParameters _ecDomainParameters = new ECDomainParameters(_ecX9Parameters.Curve, _ecX9Parameters.G, _ecX9Parameters.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 static readonly int SM2_C1_LENGTH; + private static readonly int SM2_C3_LENGTH; + private static readonly int SM2_RS_LENGTH; - private static byte[] ConvertPkcs8PrivateKeyToByteArray(string privateKey) + static SM2Utility() + { + SM2_C1_LENGTH = (_ecX9Parameters.Curve.FieldSize + 7) / 8 * 2 + 1; + SM2_C3_LENGTH = new SM3Digest().GetDigestSize(); + SM2_RS_LENGTH = 32; + + if (SM2_C1_LENGTH != 65) + throw new PlatformNotSupportedException($"Expected c1 length: {65}, actual: {SM2_C1_LENGTH}."); + + if (SM2_C3_LENGTH != 32) + throw new PlatformNotSupportedException($"Expected c3 length: {32}, actual: {SM2_C3_LENGTH}."); + } + + private static byte[] ConvertPrivateKeyPkcs8PemToByteArray(string privateKey) { privateKey = privateKey .Replace("-----BEGIN PRIVATE KEY-----", string.Empty) @@ -33,7 +55,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities return Convert.FromBase64String(privateKey); } - private static byte[] ConvertPkcs8PublicKeyToByteArray(string publicKey) + private static byte[] ConvertPublicKeyPkcs8PemToByteArray(string publicKey) { publicKey = publicKey .Replace("-----BEGIN PUBLIC KEY-----", string.Empty) @@ -42,7 +64,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities return Convert.FromBase64String(publicKey); } - private static X509Certificate ParseX509Certificate(string certificate) + private static X509Certificate ConvertCertificatePemToX509(string certificate) { using (TextReader sreader = new StringReader(certificate)) { @@ -51,107 +73,371 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities } } - private static ECPublicKeyParameters ConvertCertificateToPublicKeyParams(string certificate) + private static ECPrivateKeyParameters ParsePrivateKeyPemToPrivateKeyParameters(string privateKey) { - X509Certificate cert = ParseX509Certificate(certificate); - return (ECPublicKeyParameters)cert.GetPublicKey(); + byte[] privateKeyBytes = ConvertPrivateKeyPkcs8PemToByteArray(privateKey); + return ParsePrivateKeyPemToPrivateKeyParameters(privateKeyBytes); } - private static byte[] SignWithSM3(ECPrivateKeyParameters ecPrivateKeyParams, byte[] userIdBytes, byte[] plainBytes) + private static ECPrivateKeyParameters ParsePrivateKeyPemToPrivateKeyParameters(byte[] privateKeyBytes) { - 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(); + return (ECPrivateKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes); } - private static bool VerifyWithSM3(ECPublicKeyParameters ecPublicKeyParams, byte[] userIdBytes, byte[] plainBytes, byte[] signBytes) + private static ECPrivateKeyParameters ParseECPrivateKeyToPrivateKeyParameters(string ecPrivateKeyHex) { - ISigner signer = new SM2Signer(new SM3Digest()); - ICipherParameters idParams = new ParametersWithID(new ParametersWithRandom(ecPublicKeyParams), userIdBytes); - signer.Init(false, idParams); - signer.BlockUpdate(plainBytes, 0, plainBytes.Length); + BigInteger ecPrivateKeyParamsD = new BigInteger(ecPrivateKeyHex, 16); + return new ECPrivateKeyParameters(ecPrivateKeyParamsD, _ecDomainParameters); + } + + private static ECPublicKeyParameters ParsePublicKeyPemToPublicKeyParameters(byte[] publicKeyBytes) + { + return (ECPublicKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes); + } + + private static ECPublicKeyParameters ParseECPublicKeyToPublicKeyParameters(string ecPublicKeyHex) + { + byte[] ecPublicKeyBytes = Hex.Decode(ecPublicKeyHex); + + 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(_ecX9Parameters.Curve.CreatePoint(ecPublicKeyParamsX, ecPublicKeyParamsY), _ecDomainParameters); + } + + private static byte[] ConvertC1C3C2ToC1C2C3(byte[] c1c3c2) + { + byte[] tmp = new byte[c1c3c2.Length]; + Buffer.BlockCopy(c1c3c2, 0, tmp, 0, SM2_C1_LENGTH); + Buffer.BlockCopy(c1c3c2, SM2_C1_LENGTH + SM2_C3_LENGTH, tmp, SM2_C1_LENGTH, c1c3c2.Length - SM2_C1_LENGTH - SM2_C3_LENGTH); + Buffer.BlockCopy(c1c3c2, SM2_C1_LENGTH, tmp, c1c3c2.Length - SM2_C3_LENGTH, SM2_C3_LENGTH); + return tmp; + } + + private static byte[] ConvertC1C2C3ToC1C3C2(byte[] c1c2c3) + { + byte[] tmp = new byte[c1c2c3.Length]; + Buffer.BlockCopy(c1c2c3, 0, tmp, 0, SM2_C1_LENGTH); //c1 + Buffer.BlockCopy(c1c2c3, c1c2c3.Length - SM2_C3_LENGTH, tmp, SM2_C1_LENGTH, SM2_C3_LENGTH); + Buffer.BlockCopy(c1c2c3, SM2_C1_LENGTH, tmp, SM2_C1_LENGTH + SM2_C3_LENGTH, c1c2c3.Length - SM2_C1_LENGTH - SM2_C3_LENGTH); + return tmp; + } + + private static byte[] ConvertC1C3C2ToAsn1(byte[] c1c3c2) + { + byte[] c1 = Arrays.CopyOfRange(c1c3c2, 0, SM2_C1_LENGTH); + byte[] c3 = Arrays.CopyOfRange(c1c3c2, SM2_C1_LENGTH, SM2_C1_LENGTH + SM2_C3_LENGTH); + byte[] c2 = Arrays.CopyOfRange(c1c3c2, SM2_C1_LENGTH + SM2_C3_LENGTH, c1c3c2.Length); + byte[] c1X = Arrays.CopyOfRange(c1, 1, 33); + byte[] c1Y = Arrays.CopyOfRange(c1, 33, 65); + + BigInteger r = new BigInteger(1, c1X); + BigInteger s = new BigInteger(1, c1Y); + + DerInteger x = new DerInteger(r); + DerInteger y = new DerInteger(s); + DerOctetString derDig = new DerOctetString(c3); + DerOctetString derEnc = new DerOctetString(c2); + + Asn1EncodableVector vector = new Asn1EncodableVector(); + vector.Add(x); + vector.Add(y); + vector.Add(derDig); + vector.Add(derEnc); + + DerSequence sequence = new DerSequence(vector); + return sequence.GetEncoded("DER"); + } + + private static byte[] ConvertAsn1ToC1C3C2(byte[] asn1) + { + Asn1Sequence sequence = Asn1Sequence.GetInstance(asn1); + + BigInteger x = DerInteger.GetInstance(sequence[0]).Value; + BigInteger y = DerInteger.GetInstance(sequence[1]).Value; + + byte[] c3 = Asn1OctetString.GetInstance(sequence[2]).GetOctets(); + byte[] c2 = Asn1OctetString.GetInstance(sequence[3]).GetOctets(); + ECPoint c1Point = _ecX9Parameters.Curve.CreatePoint(x, y); + byte[] c1 = c1Point.GetEncoded(false); + + return Arrays.ConcatenateAll(c1, c3, c2); + } + + 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[] msgBytes, bool asn1Encoding) + { + ISigner signer = SignerUtilities.GetSigner("SM3withSM2"); + signer.Init(true, new ParametersWithID(sm2PrivateKeyParams, uidBytes)); + signer.BlockUpdate(msgBytes, 0, msgBytes.Length); + byte[] signBytes = signer.GenerateSignature(); + + // BouncyCastle 库的签名结果默认 ASN.1 编码,如不需要需要手动转换 + if (!asn1Encoding) + { + signBytes = ConvertAsn1ToRs(signBytes); + } + + return signBytes; + } + + private static bool VerifyWithSM3(ECPublicKeyParameters sm2PublicKeyParams, byte[] uidBytes, byte[] msgBytes, byte[] signBytes, bool asn1Encoding) + { + ISigner signer = SignerUtilities.GetSigner("SM3withSM2"); + signer.Init(false, new ParametersWithID(sm2PublicKeyParams, uidBytes)); + signer.BlockUpdate(msgBytes, 0, msgBytes.Length); + + // BouncyCastle 库的签名结果默认 ASN.1 编码,如不需要需要手动转换 + if (!asn1Encoding) + { + signBytes = ConvertRsToAsn1(signBytes); + } + return signer.VerifySignature(signBytes); } - /// - /// 使用私钥基于 SM3 算法生成签名。 - /// - /// PKCS#8 私钥字节数组。 - /// 待签名的数据字节数组。 - /// 签名字节数组。 - public static byte[] SignWithSM3(byte[] privateKeyBytes, byte[] plainBytes) + private static byte[] Decrypt(ECPrivateKeyParameters sm2PrivateKeyParams, byte[] cipherBytes, bool asn1Encoding) { - return SignWithSM3( - privateKeyBytes: privateKeyBytes, - userIdBytes: DEFAULT_USERID_BYTES, - plainBytes: plainBytes - ); + // BouncyCastle 库的加密结果默认非 ASN.1 编码,如有需要需要手动转换 + if (asn1Encoding) + { + cipherBytes = ConvertAsn1ToC1C3C2(cipherBytes); + } + + // Java 版 BouncyCastle 库生成的密文,开头需补一个字节 0x04,才能在 .NET 版 BouncyCastle 库中解密 + if (cipherBytes.FirstOrDefault() != 0x04) + { + byte[] tmp = new byte[cipherBytes.Length + 1]; + Buffer.BlockCopy(cipherBytes, 0, tmp, 1, cipherBytes.Length); + tmp[0] = 0x04; + cipherBytes = tmp; + } + + // BouncyCastle 库对 SM2 的加密结果为 C1|C2|C3,但国标要求为 C1|C3|C2 + // 此工具类仅支持国标(也是微信支付所要求的),所以先转换一次入参的密文数据 + cipherBytes = ConvertC1C3C2ToC1C2C3(cipherBytes); + + SM2Engine sm2Engine = new SM2Engine(); + sm2Engine.Init(false, sm2PrivateKeyParams); + return sm2Engine.ProcessBlock(cipherBytes, 0, cipherBytes.Length); + } + + private static byte[] Encrypt(ECPublicKeyParameters sm2PublicKeyParams, byte[] plainBytes, bool asn1Encoding) + { + SM2Engine sm2Engine = new SM2Engine(); + sm2Engine.Init(true, new ParametersWithRandom(sm2PublicKeyParams, new SecureRandom())); + byte[] cipherBytes = sm2Engine.ProcessBlock(plainBytes, 0, plainBytes.Length); + + // BouncyCastle 库对 SM2 的加密结果为 C1|C2|C3,但国标 GM/T 0009-2012 要求为 C1|C3|C2 + // 此工具类仅支持国标(也是微信支付所要求的),所以先转换一次出参的密文数据 + cipherBytes = ConvertC1C2C3ToC1C3C2(cipherBytes); + + // BouncyCastle 库的加密结果默认非 ASN.1 编码,如有需要需要手动转换 + if (asn1Encoding) + { + cipherBytes = ConvertC1C3C2ToAsn1(cipherBytes); + } + + // .NET 版 BouncyCastle 库生成的密文,开头需删一个字节 0x04,才能在 Java 版 BouncyCastle 库中解密 + if (cipherBytes.FirstOrDefault() == 0x04) + { + byte[] tmp = new byte[cipherBytes.Length - 1]; + Buffer.BlockCopy(cipherBytes, 1, tmp, 0, tmp.Length); + cipherBytes = tmp; + } + + return cipherBytes; } /// /// 使用私钥基于 SM3 算法生成签名。 /// /// PKCS#8 私钥字节数组。 - /// 用户身份标识字节数组。 - /// 待签名的数据字节数组。 + /// 用户标识符字节数组。 + /// 数据字节数组。 + /// 指示签名结果是否为 ASN.1 编码的形式。(默认值:true) /// 签名字节数组。 - public static byte[] SignWithSM3(byte[] privateKeyBytes, byte[] userIdBytes, byte[] plainBytes) + public static byte[] SignWithSM3(byte[] privateKeyBytes, byte[] uidBytes, byte[] msgBytes, bool asn1Encoding = true) { if (privateKeyBytes == null) throw new ArgumentNullException(nameof(privateKeyBytes)); - if (userIdBytes == null) throw new ArgumentNullException(nameof(userIdBytes)); - if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes)); + if (uidBytes == null) throw new ArgumentNullException(nameof(uidBytes)); + if (msgBytes == null) throw new ArgumentNullException(nameof(msgBytes)); - //ECPrivateKeyParameters ecKeyParams = (ECPrivateKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes); - //return SignWithSM3(ecKeyParams, userIdBytes, plainBytes); + ECPrivateKeyParameters sm2PrivateKeyParams = ParsePrivateKeyPemToPrivateKeyParameters(privateKeyBytes); + return SignWithSM3(sm2PrivateKeyParams, uidBytes, msgBytes, asn1Encoding); + } - 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(); + /// + /// 使用私钥基于 SM3 算法生成签名。 + /// + /// PKCS#8 私钥字节数组。 + /// 待签名的数据字节数组。 + /// 指示签名结果是否为 ASN.1 编码的形式。(默认值:true) + /// 签名字节数组。 + public static byte[] SignWithSM3(byte[] privateKeyBytes, byte[] msgBytes, bool asn1Encoding = true) + { + return SignWithSM3( + privateKeyBytes: privateKeyBytes, + uidBytes: SM2_DEFAULT_UID, + msgBytes: msgBytes, + asn1Encoding: asn1Encoding + ); } /// /// 使用私钥基于 SM3 算法生成签名。 /// /// PKCS#8 私钥(PEM 格式)。 - /// 待签名的文本数据。 + /// 待签名的文本数据。 + /// 指示签名结果是否为 ASN.1 编码的形式。(默认值:true) /// 经 Base64 编码的签名。 - public static string SignWithSM3(string privateKey, string plainText) + public static string SignWithSM3(string privateKey, string message, bool asn1Encoding = true) { 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 = SignWithSM3(privateKeyBytes, plainBytes); + byte[] signBytes = SignWithSM3( + privateKeyBytes: ConvertPrivateKeyPkcs8PemToByteArray(privateKey), + msgBytes: Encoding.UTF8.GetBytes(message), + asn1Encoding: asn1Encoding + ); return Convert.ToBase64String(signBytes); } /// - /// 使用公钥基于 SM3 算法验证签名。 + /// 使用 EC 十六进制私钥基于 SM3 算法生成签名。 /// - /// PKCS#8 公钥字节数据。 - /// 待验证的数据字节数据。 - /// 待验证的签名字节数据。 - /// 验证结果。 - public static bool VerifyWithSM3(byte[] publicKeyBytes, byte[] plainBytes, byte[] signBytes) + /// EC 私钥字节数据。 + /// 用户标识符字节数组。 + /// 待签名的数据字节数组。 + /// 指示签名结果是否为 ASN.1 编码的形式。(默认值:true) + /// 签名字节数组。 + public static byte[] SignWithSM3ByECPrivateKey(byte[] ecPrivateKeyBytes, byte[] uidBytes, byte[] msgBytes, bool asn1Encoding = true) { - return VerifyWithSM3( - publicKeyBytes: publicKeyBytes, - userIdBytes: DEFAULT_USERID_BYTES, - plainBytes: plainBytes, - signBytes: signBytes + if (ecPrivateKeyBytes == null) throw new ArgumentNullException(nameof(ecPrivateKeyBytes)); + if (uidBytes == null) throw new ArgumentNullException(nameof(uidBytes)); + if (msgBytes == null) throw new ArgumentNullException(nameof(msgBytes)); + + return SignWithSM3ByECPrivateKey( + ecPrivateKeyHex: Hex.ToHexString(ecPrivateKeyBytes), + uidBytes: uidBytes, + msgBytes: msgBytes, + asn1Encoding: asn1Encoding + ); + } + + /// + /// 使用 EC 十六进制私钥基于 SM3 算法生成签名。 + /// + /// EC 私钥字节数据。 + /// 待签名的数据字节数组。 + /// 指示签名结果是否为 ASN.1 编码的形式。(默认值:true) + /// 签名字节数组。 + public static byte[] SignWithSM3ByECPrivateKey(byte[] ecPrivateKeyBytes, byte[] msgBytes, bool asn1Encoding = true) + { + return SignWithSM3ByECPrivateKey( + ecPrivateKeyBytes: ecPrivateKeyBytes, + uidBytes: SM2_DEFAULT_UID, + msgBytes: msgBytes, + asn1Encoding: asn1Encoding + ); + } + + /// + /// 使用 EC 十六进制私钥基于 SM3 算法生成签名。 + /// + /// EC 私钥(十六进制格式)。 + /// 用户标识符字节数组。 + /// 待签名的数据字节数组。 + /// 指示签名结果是否为 ASN.1 编码的形式。(默认值:true) + /// 签名字节数组。 + public static byte[] SignWithSM3ByECPrivateKey(string ecPrivateKeyHex, byte[] uidBytes, byte[] msgBytes, bool asn1Encoding = true) + { + if (ecPrivateKeyHex == null) throw new ArgumentNullException(nameof(ecPrivateKeyHex)); + if (uidBytes == null) throw new ArgumentNullException(nameof(uidBytes)); + if (msgBytes == null) throw new ArgumentNullException(nameof(msgBytes)); + + ECPrivateKeyParameters ecPrivateKeyParams = ParseECPrivateKeyToPrivateKeyParameters(ecPrivateKeyHex); + return SignWithSM3(ecPrivateKeyParams, uidBytes, msgBytes, asn1Encoding); + } + + /// + /// 使用 EC 十六进制私钥基于 SM3 算法生成签名。 + /// + /// EC 私钥(十六进制格式)。 + /// 待签名的数据字节数组。 + /// 指示签名结果是否为 ASN.1 编码的形式。(默认值:true) + /// 签名字节数组。 + public static byte[] SignWithSM3ByECPrivateKey(string ecPrivateKeyHex, byte[] msgBytes, bool asn1Encoding = true) + { + return SignWithSM3ByECPrivateKey( + ecPrivateKeyHex: ecPrivateKeyHex, + uidBytes: SM2_DEFAULT_UID, + msgBytes: msgBytes, + asn1Encoding: asn1Encoding ); } @@ -159,38 +445,316 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities /// 使用公钥基于 SM3 算法验证签名。 /// /// PKCS#8 公钥字节数据。 - /// 用户身份标识字节数组。 - /// 待验证的数据字节数据。 + /// 用户标识符字节数组。 + /// 待验证的数据字节数据。 /// 待验证的签名字节数据。 + /// 指示签名结果是否为 ASN.1 编码的形式。(默认值:true) /// 验证结果。 - public static bool VerifyWithSM3(byte[] publicKeyBytes, byte[] userIdBytes, byte[] plainBytes, byte[] signBytes) + public static bool VerifyWithSM3(byte[] publicKeyBytes, byte[] uidBytes, byte[] msgBytes, byte[] signBytes, bool asn1Encoding = true) { 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 (uidBytes == null) throw new ArgumentNullException(nameof(uidBytes)); + if (msgBytes == null) throw new ArgumentNullException(nameof(msgBytes)); if (signBytes == null) throw new ArgumentNullException(nameof(signBytes)); - ECPublicKeyParameters ecPublicKeyParams = (ECPublicKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes); - return VerifyWithSM3(ecPublicKeyParams, userIdBytes, plainBytes, signBytes); + ECPublicKeyParameters sm2PublicKeyParams = ParsePublicKeyPemToPublicKeyParameters(publicKeyBytes); + return VerifyWithSM3(sm2PublicKeyParams, uidBytes, msgBytes, signBytes, asn1Encoding); + } + + /// + /// 使用公钥基于 SM3 算法验证签名。 + /// + /// PKCS#8 公钥字节数据。 + /// 待验证的数据字节数据。 + /// 待验证的签名字节数据。 + /// 指示签名结果是否为 ASN.1 编码的形式。(默认值:true) + /// 验证结果。 + public static bool VerifyWithSM3(byte[] publicKeyBytes, byte[] msgBytes, byte[] signBytes, bool asn1Encoding = true) + { + return VerifyWithSM3( + publicKeyBytes: publicKeyBytes, + uidBytes: SM2_DEFAULT_UID, + msgBytes: msgBytes, + signBytes: signBytes, + asn1Encoding: asn1Encoding + ); } /// /// 使用公钥基于 SM3 算法验证签名。 /// /// PKCS#8 公钥(PEM 格式)。 - /// 待验证的文本数据。 + /// 待验证的文本数据。 /// 经 Base64 编码的待验证的签名。 /// 验证结果。 - public static bool VerifyWithSM3(string publicKey, string plainText, string signature) + public static bool VerifyWithSM3(string publicKey, string message, string signature, bool asn1Encoding = true) + { + if (publicKey == null) throw new ArgumentNullException(nameof(publicKey)); + if (message == null) throw new ArgumentNullException(nameof(message)); + if (signature == null) throw new ArgumentNullException(nameof(signature)); + + return VerifyWithSM3( + publicKeyBytes: ConvertPublicKeyPkcs8PemToByteArray(publicKey), + msgBytes: Encoding.UTF8.GetBytes(message), + signBytes: Convert.FromBase64String(signature), + asn1Encoding: asn1Encoding + ); + } + + /// + /// 使用证书基于 SM3 算法验证签名。 + /// + /// 证书(PEM 格式)。 + /// 待验证的文本数据。 + /// 经 Base64 编码的待验证的签名。 + /// 指示签名结果是否为 ASN.1 编码的形式。(默认值:true) + /// 验证结果。 + public static bool VerifyWithSM3ByCertificate(string certificate, string message, string signature, bool asn1Encoding = true) + { + if (certificate == null) throw new ArgumentNullException(nameof(certificate)); + + string publicKey = ExportPublicKeyFromCertificate(certificate); + return VerifyWithSM3( + publicKey: publicKey, + message: message, + signature: signature, + asn1Encoding: asn1Encoding + ); + } + + /// + /// 使用 EC 十六进制公钥基于 SM3 算法生成签名。 + /// + /// EC 公钥字节数据。 + /// 用户标识符字节数组。 + /// 待签名的数据字节数组。 + /// 待验证的签名字节数据。 + /// 指示签名结果是否为 ASN.1 编码的形式。(默认值:true) + /// 签名字节数组。 + public static bool VerifyWithSM3ByECPublicKey(byte[] ecPublicKeyBytes, byte[] uidBytes, byte[] msgBytes, byte[] signBytes, bool asn1Encoding = true) + { + if (ecPublicKeyBytes == null) throw new ArgumentNullException(nameof(ecPublicKeyBytes)); + if (uidBytes == null) throw new ArgumentNullException(nameof(uidBytes)); + if (msgBytes == null) throw new ArgumentNullException(nameof(msgBytes)); + + return VerifyWithSM3ByECPublicKey( + ecPublicKeyHex: Hex.ToHexString(ecPublicKeyBytes), + uidBytes: uidBytes, + msgBytes: msgBytes, + signBytes: signBytes, + asn1Encoding: asn1Encoding + ); + } + + /// + /// 使用 EC 十六进制公钥基于 SM3 算法生成签名。 + /// + /// EC 公钥字节数据。 + /// 待签名的数据字节数组。 + /// 待验证的签名字节数据。 + /// 指示签名结果是否为 ASN.1 编码的形式。(默认值:true) + /// 签名字节数组。 + public static bool VerifyWithSM3ByECPublicKey(byte[] ecPublicKeyBytes, byte[] msgBytes, byte[] signBytes, bool asn1Encoding = true) + { + return VerifyWithSM3ByECPublicKey( + ecPublicKeyBytes: ecPublicKeyBytes, + uidBytes: SM2_DEFAULT_UID, + msgBytes: msgBytes, + signBytes: signBytes, + asn1Encoding: asn1Encoding + ); + } + + /// + /// 使用 EC 十六进制公钥基于 SM3 算法生成签名。 + /// + /// EC 公钥(十六进制格式)。 + /// 用户标识符字节数组。 + /// 待签名的数据字节数组。 + /// 待验证的签名字节数据。 + /// 指示签名结果是否为 ASN.1 编码的形式。(默认值:true) + /// 签名字节数组。 + public static bool VerifyWithSM3ByECPublicKey(string ecPublicKeyHex, byte[] uidBytes, byte[] msgBytes, byte[] signBytes, bool asn1Encoding = true) + { + if (ecPublicKeyHex == null) throw new ArgumentNullException(nameof(ecPublicKeyHex)); + if (uidBytes == null) throw new ArgumentNullException(nameof(uidBytes)); + if (msgBytes == null) throw new ArgumentNullException(nameof(msgBytes)); + + ECPublicKeyParameters ecPublicKeyParams = ParseECPublicKeyToPublicKeyParameters(ecPublicKeyHex); + return VerifyWithSM3(ecPublicKeyParams, uidBytes, msgBytes, signBytes, asn1Encoding); + } + + /// + /// 使用 EC 十六进制公钥基于 SM3 算法生成签名。 + /// + /// EC 公钥(十六进制格式)。 + /// 待签名的数据字节数组。 + /// 待验证的签名字节数据。 + /// 指示签名结果是否为 ASN.1 编码的形式。(默认值:true) + /// 签名字节数组。 + public static bool VerifyWithSM3ByECPublicKey(string ecPublicKeyHex, byte[] msgBytes, byte[] signBytes, bool asn1Encoding = true) + { + return VerifyWithSM3ByECPublicKey( + ecPublicKeyHex: ecPublicKeyHex, + uidBytes: SM2_DEFAULT_UID, + msgBytes: msgBytes, + signBytes: signBytes, + asn1Encoding: asn1Encoding + ); + } + + /// + /// 使用私钥解密数据。 + /// + /// PKCS#8 私钥字节数据。 + /// 待解密的数据字节数据。 + /// 指示加密结果是否为 ASN.1 编码的形式。(默认值:true) + /// 解密后的数据字节数组。 + public static byte[] Decrypt(byte[] privateKeyBytes, byte[] cipherBytes, bool asn1Encoding = true) + { + if (privateKeyBytes == null) throw new ArgumentNullException(nameof(privateKeyBytes)); + if (cipherBytes == null) throw new ArgumentNullException(nameof(cipherBytes)); + + ECPrivateKeyParameters sm2PrivateKeyParams = ParsePrivateKeyPemToPrivateKeyParameters(privateKeyBytes); + return Decrypt(sm2PrivateKeyParams, cipherBytes, asn1Encoding); + } + + /// + /// 使用私钥解密数据。 + /// + /// PKCS#8 私钥(PEM 格式)。 + /// 经 Base64 编码的待解密数据。 + /// 指示加密结果是否为 ASN.1 编码的形式。(默认值:true) + /// 解密后的文本数据。 + public static string Decrypt(string privateKey, string cipherText, bool asn1Encoding = true) + { + if (privateKey == null) throw new ArgumentNullException(nameof(privateKey)); + if (cipherText == null) throw new ArgumentNullException(nameof(cipherText)); + + byte[] plainBytes = Decrypt( + privateKeyBytes: ConvertPrivateKeyPkcs8PemToByteArray(privateKey), + cipherBytes: Convert.FromBase64String(cipherText), + asn1Encoding: asn1Encoding + ); + return Encoding.UTF8.GetString(plainBytes); + } + + /// + /// 使用 EC 十六进制私钥解密数据。 + /// + /// EC 私钥字节数据。 + /// 待解密的数据字节数据。 + /// 指示加密结果是否为 ASN.1 编码的形式。(默认值:true) + /// 解密后的数据字节数组。 + public static byte[] DecryptByECPrivateKey(byte[] ecPrivateKeyBytes, byte[] cipherBytes, bool asn1Encoding = true) + { + if (ecPrivateKeyBytes == null) throw new ArgumentNullException(nameof(ecPrivateKeyBytes)); + + return DecryptByECPrivateKey( + ecPrivateKeyHex: Hex.ToHexString(ecPrivateKeyBytes), + cipherBytes: cipherBytes, + asn1Encoding: asn1Encoding + ); + } + + /// + /// 使用 EC 十六进制私钥解密数据。 + /// + /// EC 私钥(十六进制格式)。 + /// 待解密的数据字节数据。 + /// 指示加密结果是否为 ASN.1 编码的形式。(默认值:true) + /// 解密后的文本数据。 + public static byte[] DecryptByECPrivateKey(string ecPrivateKeyHex, byte[] cipherBytes, bool asn1Encoding = true) + { + if (ecPrivateKeyHex == null) throw new ArgumentNullException(nameof(ecPrivateKeyHex)); + + ECPrivateKeyParameters ecPrivateKeyParams = ParseECPrivateKeyToPrivateKeyParameters(ecPrivateKeyHex); + return Decrypt(ecPrivateKeyParams, cipherBytes, asn1Encoding); + } + + /// + /// 使用公钥加密数据。 + /// + /// PKCS#8 公钥字节数据。 + /// 待加密的数据字节数据。 + /// 指示加密结果是否为 ASN.1 编码的形式。(默认值:true) + /// 加密后的数据字节数组。 + public static byte[] Encrypt(byte[] publicKeyBytes, byte[] plainBytes, bool asn1Encoding = true) + { + if (publicKeyBytes == null) throw new ArgumentNullException(nameof(publicKeyBytes)); + if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes)); + + ECPublicKeyParameters sm2PublicKeyParams = ParsePublicKeyPemToPublicKeyParameters(publicKeyBytes); + return Encrypt(sm2PublicKeyParams, plainBytes, asn1Encoding); + } + + /// + /// 使用公钥加密数据。 + /// + /// PKCS#8 公钥(PEM 格式)。 + /// 待加密的文本数据。 + /// 指示加密结果是否为 ASN.1 编码的形式。(默认值:true) + /// 经 Base64 编码的加密数据。 + public static string Encrypt(string publicKey, string plainText, bool asn1Encoding = true) { 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); + byte[] cipherBytes = Encrypt( + publicKeyBytes: ConvertPublicKeyPkcs8PemToByteArray(publicKey), + plainBytes: Encoding.UTF8.GetBytes(plainText), + asn1Encoding: asn1Encoding + ); + return Convert.ToBase64String(cipherBytes); + } + + /// + /// 使用证书加密数据。 + /// + /// 证书(PEM 格式)。 + /// 待加密的文本数据。 + /// 指示加密结果是否为 ASN.1 编码的形式。(默认值:true) + /// 经 Base64 编码的加密数据。 + public static string EncryptByCertificate(string certificate, string plainText, bool asn1Encoding = true) + { + if (certificate == null) throw new ArgumentNullException(nameof(certificate)); + + return Encrypt( + publicKey: ExportPublicKeyFromCertificate(certificate), + plainText: plainText, + asn1Encoding: asn1Encoding + ); + } + + /// + /// 使用 EC 十六进制公钥加密数据。 + /// + /// EC 公钥字节数据。 + /// 待加密的数据字节数据。 + /// 指示加密结果是否为 ASN.1 编码的形式。(默认值:true) + /// 加密后的数据字节数组。 + public static byte[] EncryptByECPublicKey(byte[] ecPublicKeyBytes, byte[] plainBytes, bool asn1Encoding = true) + { + return EncryptByECPublicKey( + ecPublicKeyHex: Hex.ToHexString(ecPublicKeyBytes), + plainBytes: plainBytes, + asn1Encoding: asn1Encoding + ); + } + + /// + /// 使用 EC 十六进制公钥加密数据。 + /// + /// EC 公钥(十六进制格式)。 + /// 待加密的数据字节数据。 + /// 指示加密结果是否为 ASN.1 编码的形式。(默认值:true) + /// 加密后的数据字节数组。 + public static byte[] EncryptByECPublicKey(string ecPublicKeyHex, byte[] plainBytes, bool asn1Encoding = true) + { + if (ecPublicKeyHex == null) throw new ArgumentNullException(nameof(ecPublicKeyHex)); + + ECPublicKeyParameters ecPublicKeyParams = ParseECPublicKeyToPublicKeyParameters(ecPublicKeyHex); + return Encrypt(ecPublicKeyParams, plainBytes, asn1Encoding); } /// @@ -202,15 +766,16 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities /// /// 证书(PEM 格式)。 /// PKCS#8 公钥(PEM 格式)。 - public static string ExportPublicKey(string certificate) + public static string ExportPublicKeyFromCertificate(string certificate) { if (certificate == null) throw new ArgumentNullException(nameof(certificate)); using (TextWriter swriter = new StringWriter()) { - ECKeyParameters ecKeyParams = ConvertCertificateToPublicKeyParams(certificate); + X509Certificate x509cert = ConvertCertificatePemToX509(certificate); + ECPublicKeyParameters exPublicKeyParams = (ECPublicKeyParameters)x509cert.GetPublicKey(); PemWriter pemWriter = new PemWriter(swriter); - pemWriter.WriteObject(ecKeyParams); + pemWriter.WriteObject(exPublicKeyParams); pemWriter.Writer.Flush(); return swriter.ToString()!; } @@ -221,11 +786,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities /// /// 证书(PEM 格式)。 /// 证书序列号。 - public static string ExportSerialNumber(string certificate) + public static string ExportSerialNumberFromCertificate(string certificate) { if (certificate == null) throw new ArgumentNullException(nameof(certificate)); - X509Certificate cert = ParseX509Certificate(certificate); + X509Certificate cert = ConvertCertificatePemToX509(certificate); return cert.SerialNumber.ToString(16); } @@ -234,11 +799,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities /// /// 证书(PEM 格式)。 /// 证书颁发时间。 - public static DateTimeOffset ExportEffectiveTime(string certificate) + public static DateTimeOffset ExportEffectiveTimeFromCertificate(string certificate) { if (certificate == null) throw new ArgumentNullException(nameof(certificate)); - X509Certificate cert = ParseX509Certificate(certificate); + X509Certificate cert = ConvertCertificatePemToX509(certificate); return new DateTimeOffset(cert.NotBefore); } @@ -247,353 +812,61 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities /// /// 证书(PEM 格式)。 /// 证书过期时间。 - public static DateTimeOffset ExportExpireTime(string certificate) + public static DateTimeOffset ExportExpireTimeFromCertificate(string certificate) { if (certificate == null) throw new ArgumentNullException(nameof(certificate)); - X509Certificate cert = ParseX509Certificate(certificate); + X509Certificate cert = ConvertCertificatePemToX509(certificate); return new DateTimeOffset(cert.NotAfter); } - } - partial class SM2Utility - { - internal sealed class Cipher + /// + /// 从 CRT/CER 证书中导出 EC 的十六进制公钥。 + /// + /// 即从 -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- + /// 转为 04+X|Y 结构的十六进制字符串。 + /// + /// + /// 证书(PEM 格式)。 + /// EC 公钥(十六进制格式)。 + public static string ExportECPublicKeyFromCertificate(string certificate) { - 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(); - } + string publicKey = ExportPublicKeyFromCertificate(certificate); + return ExportECPublicKeyFromPublicKey(publicKey); } - internal sealed class SM2Factory + /// + /// 从 PKCS#8 公钥中导出 EC 的十六进制公钥。 + /// + /// 即从 -----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY----- + /// 转为 04+X|Y 结构的 130(128+2) 位的十六进制字符串。 + /// + /// + /// PKCS#8 公钥(PEM 格式)。 + /// EC 公钥(十六进制格式)。 + public static string ExportECPublicKeyFromPublicKey(string publicKey) { - 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; - } + byte[] publicKeyBytes = ConvertPublicKeyPkcs8PemToByteArray(publicKey); + ECPublicKeyParameters sm2PublicKeyParams = ParsePublicKeyPemToPublicKeyParameters(publicKeyBytes); + ECPoint ecPublicKeyPoint = sm2PublicKeyParams.Q; + string ecPublicKeyX = ecPublicKeyPoint.XCoord.ToBigInteger().ToString(16); + string ecPublicKeyY = ecPublicKeyPoint.YCoord.ToBigInteger().ToString(16); + return string.Format("04{0}{1}", ecPublicKeyX, ecPublicKeyY); } - internal sealed class SM2Signature + /// + /// 从 PKCS#8 私钥中导出 EC 的十六进制私钥。 + /// + /// 即从 -----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY----- + /// 转为 64 位的十六进制字符串。 + /// + /// + /// PKCS#8 私钥(PEM 格式)。 + /// EC 私钥(十六进制格式)。 + public static string ExportECPrivateKeyFromPrivateKey(string privateKey) { - public byte[] R { get; set; } - - public byte[] S { get; set; } - - public SM2Signature() - { - R = Array.Empty(); - S = Array.Empty(); - } - - 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; + ECPrivateKeyParameters sm2PrivateKeyParams = ParsePrivateKeyPemToPrivateKeyParameters(privateKey); + return sm2PrivateKeyParams.D.ToString(16); } } } diff --git a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_RSAUtilityTests.cs b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_RSAUtilityTests.cs index 138dbad7..fb1703ce 100644 --- a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_RSAUtilityTests.cs +++ b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_RSAUtilityTests.cs @@ -57,6 +57,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests string expectedSign = "EzeVEIoBhzOhXpwbXdJjIuGIGRc6ArKO7sVo2fuAdzYTDgorAEufEnw7lPPXV1GTfFcHOnsAJH9kGJmg7Orwlkh7x7TyOGrkMEhWGulA9SIdmou4iBsHpIZ/TERSgGt/VTmqmfpkzJqrvbQWIQENwo7Lr6uJSJBND0YT3nIBP8TzbO3cHnQb6chHIBHrDF5vOO7HHu+Cga2MZnAtRizhO8BhK0jOmyro32CgIML3EVX8yuPy0kOk6aN1R8xFblZUD4NU2M6zzQpydmxaHr9B1WNFoMwmpoAS5BuKJMYOHO5cc6DhB+0fAGxaWtKp6759KhKCf8M65zh3WKS4l262SGuWq4qG1+AKf2DOgCon769+A4z8flOmvl0iIwoH9FThGJoP156rpAJW7v/bWputSeC6WToUTBRmGWdwWySVwW5AZ26OAFFWs1CmrGp3jF5E2oUy1mQwgfM0QN6DW+wD769ggIYH9HLHqDHbF5UyF7eNh3s8Qy23xXEKZWNMAJ0IdtdMQ7FRRgLFSCai7HELLlBJSCz7P5WTeYZGQpbvnUShkRvzujjO6XlGiKKI0EwKb121z8N6KRpvs4SnRztWBGoXbzHZgnXKXU/BWWADemqB2cvaT3Bj0k3/N3sea0dAEtlNEklRWoyyNUUlscK9zg4LBlHrhbbFo66uuub8ySo="; Assert.Equal(expectedSign, actualSign); + Assert.True(Utilities.RSAUtility.VerifyWithSHA256(RSA_PEM_PUBLIC_KEY, msgText, actualSign)); } [Fact(DisplayName = "测试用例:SHA256WithRSA 签名验证")] @@ -66,7 +67,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests string signText = "aHX+MrmZHDEraMKBEPV2Vnps1B9b25lGbv/rdppx/S7+oaXtjKJprzCq5H7RCpvrKS3xYIeTEPwQGC3Vots7dCdLi8v8ew1vvtXf8qNAnd7CTMHqu3wSohXzgyASTmNbXE2ml9LbWYPPYMvPJXROQbGVjoOrsErWBPPJYXuO3lIckIfwI05OTdl4H3+BvpD/ZoljRp8Qgo9+paGvarBc++TaAh0FXnQf0TGNFUIeHHiAKBee5oCBTuZZM9J5RPw0oIq/g7Wun+e/zWiwVBPHltOgZrV46uagSAE6nBDHk+hlNxDivCxkJdBVCSIYFFmBXIcnGZ/u4ZfBui/k1jGoKibyvPK4z2+6GSlj41Yo81kuSBfzLiSsx33EPR1eIJJkwDTsvap0ymL9pfIqMiLuiteH5kGmL/dyONy9oAJywLEeITfoVyElM/CY6Dc+xDhRnjN7Hu54meYyXRZrnCtQ3YhzEr1immNBn6npgA/qi9aHsuWFOw8b8aSwOHDHTDmjmvV+axI8CVMrR0MjB9QNCWrKLq2B9iQX9MtLgcUyDsQvzAsxUJm/OEfzUjs9SHvmgmyAvzNAuTdO7wLQ+ZmKg0yZne6nvcrJVvfh3lD5ZPt7NY57Y6OIJluqKUT5H+a3H6W9Q1Z+cBMnHGYaaK7Tv8IcDdEYqTIG8hc5BqjFOzE="; Assert.True(Utilities.RSAUtility.VerifyWithSHA256(RSA_PEM_PUBLIC_KEY, msgText, signText)); + Assert.False(Utilities.RSAUtility.VerifyWithSHA256(RSA_PEM_PUBLIC_KEY, msgText, "FAKE SIGN")); Assert.True(Utilities.RSAUtility.VerifyWithSHA256ByCertificate(RSA_PEM_CERTIFICATE, msgText, signText)); + Assert.False(Utilities.RSAUtility.VerifyWithSHA256ByCertificate(RSA_PEM_CERTIFICATE, msgText, "FAKE SIGN")); } [Fact(DisplayName = "测试用例:使用 RSA 公钥加密")] @@ -78,7 +81,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests Assert.NotNull(actualCipherByPublicKey); Assert.NotNull(actualCipherByCertificate); - Assert.NotEqual(actualCipherByPublicKey, actualCipherByCertificate); + Assert.Equal(plainText, Utilities.RSAUtility.DecryptWithECB(RSA_PEM_PRIVATE_KEY, actualCipherByPublicKey)); + Assert.Equal(plainText, Utilities.RSAUtility.DecryptWithECB(RSA_PEM_PRIVATE_KEY, actualCipherByCertificate)); } [Fact(DisplayName = "测试用例:使用 RSA 私钥解密")] diff --git a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_SM2UtilityTests.cs b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_SM2UtilityTests.cs new file mode 100644 index 00000000..31db08d6 --- /dev/null +++ b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_SM2UtilityTests.cs @@ -0,0 +1,128 @@ +using System; +using System.Text; +using Xunit; + +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests +{ + public class TestCase_SM2UtilityTests + { + // 此处测试的 SM2 证书/公钥/私钥是自签名生成的,仅供执行 SM2 相关的单元测试,不能用于调用微信支付 API。 + private const string SM2_CERT_SN = "e5a81b02429d8d08"; + private const string SM2_CERT_START_DATE = "2022-11-09 21:12:20"; + private const string SM2_CERT_END_DATE = "2023-11-09 21:12:20"; + private const string SM2_PEM_CERTIFICATE = "-----BEGIN CERTIFICATE-----\nMIICNzCCAdygAwIBAgIJAOWoGwJCnY0IMAoGCCqBHM9VAYN1MGcxCzAJBgNVBAYT\nAkNOMRAwDgYDVQQIDAdCZWlqaW5nMRAwDgYDVQQHDAdIYWlEaWFuMRMwEQYDVQQK\nDApHTUNlcnQub3JnMR8wHQYDVQQDDBZHTUNlcnQgR00gUm9vdCBDQSAtIDAxMB4X\nDTIyMTEwOTEzMTIyMFoXDTIzMTEwOTEzMTIyMFowSzEtMCsGA1UEAwwkU0tJVC5G\nbHVybEh0dHBDbGllbnQuV2VjaGF0LlRlbnBheVYzMQ0wCwYDVQQKDARTS0lUMQsw\nCQYDVQQGEwJDTjBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABMXP1hZc2zBzreRN\nZgOR9hklE01tw10RDUfj176EXcVoVOvITMENJ3HREQtDPlOfz8i1SXCQEwclYyxI\n2KcTdKqjgYwwgYkwDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCA/gwLAYJYIZIAYb4\nQgENBB8WHUdNQ2VydC5vcmcgU2lnbmVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBRj\nIhoxmSgP84XT/scjkQNSWylMFTAfBgNVHSMEGDAWgBR/Wl47AIRZKg+YvqEObzmV\nQxBNBzAKBggqgRzPVQGDdQNJADBGAiEAnXykM0qDOWay2EMB6+c6YJ7h4n7Wbju7\nXuT5RkuM/3ICIQDAA3sLba/dQMhmKkCoJl31iZwYKz7NP+0aq6NhWDommQ==\n-----END CERTIFICATE-----"; + private const string SM2_PEM_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAExc/WFlzbMHOt5E1mA5H2GSUTTW3D\nXRENR+PXvoRdxWhU68hMwQ0ncdERC0M+U5/PyLVJcJATByVjLEjYpxN0qg==\n-----END PUBLIC KEY-----"; + private const string SM2_PEM_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQg3WePog9R4UV/EVlk\nCw8YHu+rXC/imiB89jFmaAPeXz6gCgYIKoEcz1UBgi2hRANCAATFz9YWXNswc63k\nTWYDkfYZJRNNbcNdEQ1H49e+hF3FaFTryEzBDSdx0RELQz5Tn8/ItUlwkBMHJWMs\nSNinE3Sq\n-----END PRIVATE KEY-----"; + private const string SM2_HEX_EC_PRIVATE_KEY = "dd678fa20f51e1457f1159640b0f181eefab5c2fe29a207cf631666803de5f3e"; + private const string SM2_HEX_EC_PUBLIC_KEY = "04c5cfd6165cdb3073ade44d660391f61925134d6dc35d110d47e3d7be845dc56854ebc84cc10d2771d1110b433e539fcfc8b5497090130725632c48d8a71374aa"; + + [Fact(DisplayName = "测试用例:从 SM2 证书中导出公钥")] + public void TestSM2ExportPublicKeyFromCertificate() + { + string actualPublicKey = Utilities.SM2Utility.ExportPublicKeyFromCertificate(SM2_PEM_CERTIFICATE).Replace("\r", "").Replace("\n", ""); + string expectedPublicKey = SM2_PEM_PUBLIC_KEY.Replace("\r", "").Replace("\n", ""); + + Assert.Equal(expectedPublicKey, actualPublicKey, ignoreLineEndingDifferences: true); + + string actualECHexPublicKey = Utilities.SM2Utility.ExportECPublicKeyFromCertificate(SM2_PEM_CERTIFICATE); + string expectedECHexPublicKey = SM2_HEX_EC_PUBLIC_KEY; + Assert.Equal(expectedECHexPublicKey, actualECHexPublicKey, ignoreCase: true); + } + + [Fact(DisplayName = "测试用例:从 SM2 证书中导出证书序列号")] + public void TestSM2ExportSerialNumberFromCertificate() + { + string actualSerialNumber = Utilities.SM2Utility.ExportSerialNumberFromCertificate(SM2_PEM_CERTIFICATE); + string expectedSerialNumber = SM2_CERT_SN; + + Assert.Equal(expectedSerialNumber, actualSerialNumber, ignoreCase: true); + } + + [Fact(DisplayName = "测试用例:从 SM2 证书中导出证书颁发时间")] + public void TestSM2ExportEffectiveTimeFromCertificate() + { + DateTimeOffset actualEffectiveTime = Utilities.SM2Utility.ExportEffectiveTimeFromCertificate(SM2_PEM_CERTIFICATE); + DateTimeOffset expectedEffectiveTime = DateTimeOffset.Parse(SM2_CERT_START_DATE); + + Assert.Equal(expectedEffectiveTime, actualEffectiveTime); + } + + [Fact(DisplayName = "测试用例:从 SM2 证书中导出证书过期时间")] + public void TestSM2ExportExpireTimeFromCertificate() + { + DateTimeOffset actualExpireTime = Utilities.SM2Utility.ExportExpireTimeFromCertificate(SM2_PEM_CERTIFICATE); + DateTimeOffset expectedExpireTime = DateTimeOffset.Parse(SM2_CERT_END_DATE); + + Assert.Equal(expectedExpireTime, actualExpireTime); + } + + [Fact(DisplayName = "测试用例:从 SM2 公钥中导出 EC 公钥")] + public void TestSM2ExportECPublicKeyFromPublicKey() + { + string actualECHexPublicKey = Utilities.SM2Utility.ExportECPublicKeyFromPublicKey(SM2_PEM_PUBLIC_KEY); + string expectedSM2HexPublicKey = SM2_HEX_EC_PUBLIC_KEY; + Assert.Equal(expectedSM2HexPublicKey, actualECHexPublicKey, ignoreCase: true); + } + + [Fact(DisplayName = "测试用例:从 SM2 私钥中导出 EC 私钥")] + public void TestSM2ExportECPrivateKeyFromPrivateKey() + { + string actualSM2HexPrivateKey = Utilities.SM2Utility.ExportECPrivateKeyFromPrivateKey(SM2_PEM_PRIVATE_KEY); + string expectedSM2HexPrivateKey = SM2_HEX_EC_PRIVATE_KEY; + Assert.Equal(expectedSM2HexPrivateKey, actualSM2HexPrivateKey, ignoreCase: true); + } + + [Fact(DisplayName = "测试用例:SM2WithSM3 签名生成")] + public void TestSM2SignatureSM2WithSM3Sign() + { + string msgText = "SM2WithSM3SignTest"; + string actualSignByPrivateKey = Utilities.SM2Utility.SignWithSM3(SM2_PEM_PRIVATE_KEY, msgText); + string actualSignByECPrivateKey = Convert.ToBase64String(Utilities.SM2Utility.SignWithSM3ByECPrivateKey(SM2_HEX_EC_PRIVATE_KEY, Encoding.UTF8.GetBytes(msgText))); + + Assert.NotNull(actualSignByPrivateKey); + Assert.NotNull(actualSignByECPrivateKey); + Assert.True(Utilities.SM2Utility.VerifyWithSM3(SM2_PEM_PUBLIC_KEY, msgText, actualSignByPrivateKey)); + } + + [Fact(DisplayName = "测试用例:SM2WithSM3 签名验证")] + public void TestSM2SignatureSM2WithSM3Verify() + { + string msgText = "SM2WithSM3SignTest"; + string signText = "MEUCIQCDzgpF2Z//sbFzASVQnwme2phm4ho5cr8/1Pz0+MONTwIgeQvhoWOTk1rngYRSlHeqqwtNFVD/vf3qtgl9mecvERI="; + + Assert.True(Utilities.SM2Utility.VerifyWithSM3(SM2_PEM_PUBLIC_KEY, msgText, signText)); + Assert.False(Utilities.SM2Utility.VerifyWithSM3(SM2_PEM_PUBLIC_KEY, msgText, "FAKE SIGN")); + Assert.True(Utilities.SM2Utility.VerifyWithSM3ByCertificate(SM2_PEM_CERTIFICATE, msgText, signText)); + Assert.False(Utilities.SM2Utility.VerifyWithSM3ByCertificate(SM2_PEM_CERTIFICATE, msgText, "FAKE SIGN")); + Assert.True(Utilities.SM2Utility.VerifyWithSM3ByECPublicKey(SM2_HEX_EC_PUBLIC_KEY, Encoding.UTF8.GetBytes(msgText), Convert.FromBase64String("MEUCIQCDzgpF2Z//sbFzASVQnwme2phm4ho5cr8/1Pz0+MONTwIgeQvhoWOTk1rngYRSlHeqqwtNFVD/vf3qtgl9mecvERI="))); + Assert.False(Utilities.SM2Utility.VerifyWithSM3ByECPublicKey(SM2_HEX_EC_PUBLIC_KEY, Encoding.UTF8.GetBytes(msgText), Encoding.UTF8.GetBytes("FAKE SIGN"))); + } + + [Fact(DisplayName = "测试用例:使用 SM2 公钥加密")] + public void TestSM2Encrypt() + { + string plainText = "SM2EncryptTest"; + string actualCipherByPublicKey = Utilities.SM2Utility.Encrypt(SM2_PEM_PUBLIC_KEY, plainText); + string actualCipherByCertificate = Utilities.SM2Utility.EncryptByCertificate(SM2_PEM_CERTIFICATE, plainText); + string actualCipherByECPublicKey = Convert.ToBase64String(Utilities.SM2Utility.EncryptByECPublicKey(SM2_HEX_EC_PUBLIC_KEY, Encoding.UTF8.GetBytes(plainText))); + + Assert.NotNull(actualCipherByPublicKey); + Assert.NotNull(actualCipherByCertificate); + Assert.NotNull(actualCipherByECPublicKey); + Assert.Equal(plainText, Utilities.SM2Utility.Decrypt(SM2_PEM_PRIVATE_KEY, actualCipherByPublicKey)); + Assert.Equal(plainText, Utilities.SM2Utility.Decrypt(SM2_PEM_PRIVATE_KEY, actualCipherByCertificate)); + Assert.Equal(plainText, Utilities.SM2Utility.Decrypt(SM2_PEM_PRIVATE_KEY, actualCipherByECPublicKey)); + } + + [Fact(DisplayName = "测试用例:使用 SM2 私钥解密")] + public void TestSM2Decrypt() + { + string cipherText = "MHYCIGJ7gjFjd6U7kOj63HLbRgPAn6cVf4eDF4emz9oCX5gKAiBAHmgAvH2WU/2+dyqMK7/Q8eD/Q9LhYFV2gqc+fv7EiAQgiHX2wr7GCnBbAsfR3stJ1i/Csc0Mq3RzVd+ZefVlr7gEDvfJIMlMcs4Q2HoMd8Jk"; + string actualPlainByPrivateKey = Utilities.SM2Utility.Decrypt(SM2_PEM_PRIVATE_KEY, cipherText); + string actualPlainByECPrivateKey = Encoding.UTF8.GetString(Utilities.SM2Utility.DecryptByECPrivateKey(SM2_HEX_EC_PRIVATE_KEY, Convert.FromBase64String(cipherText))); + string expectedPlain = "SM2DecryptTest"; + Assert.Equal(expectedPlain, actualPlainByPrivateKey); + Assert.Equal(expectedPlain, actualPlainByECPrivateKey); + } + } +}