收藏
回答

微信支付v3 jsapi 在.net8 环境下不能解密支付后的通知报文,如何解决?

商户号
1719172590

我正在开发一个微信里H5支付功能,目前被不能解密通知报文折磨1个星期了,求支援,

我的代码在最下面

运行到这一步报错

 byte[] combinedCiphertext = new byte[ciphertextBytes.Length + tag.Length];
                Buffer.BlockCopy(ciphertextBytes, 0, combinedCiphertext, 0, ciphertextBytes.Length);
                Buffer.BlockCopy(tag, 0, combinedCiphertext, ciphertextBytes.Length, tag.Length);


报错和日志如下,

2025-06-22 10:13:44.934 +08:00 [INF] APIv3密钥指纹: F6EE716DF91A4CAC

2025-06-22 10:13:44.936 +08:00 [INF] 分离后的密文长度: 423, 标签: 1429155F665DC3D543A1FE8DB52B0661

2025-06-22 10:13:44.936 +08:00 [INF] 关联数据为 null,使用空字节数组

2025-06-22 10:13:44.948 +08:00 [INF] 解密前的参数 - Key: 3931333239353433303130383539343830343139383938343533373730393738, Nonce: k2Nhn3Yjg2cv, AD: , , Tag: 1429155F665DC3D543A1FE8DB52B0661

2025-06-22 10:13:50.156 +08:00 [ERR] ---------- RemoteServiceErrorInfo ----------

{

  "code": null,

  "message": "对不起,在处理您的请求期间产生了一个服务器内部错误!!",

  "details": null,

  "data": {},

  "validationErrors": null

}


2025-06-22 10:13:50.157 +08:00 [ERR] mac check in GCM failed

Org.BouncyCastle.Crypto.InvalidCipherTextException: mac check in GCM failed

   at Org.BouncyCastle.Crypto.Modes.GcmBlockCipher.DoFinal(Span`1 output)

   at Org.BouncyCastle.Crypto.Modes.GcmBlockCipher.DoFinal(Byte[] output, Int32 outOff)

   at LaborMall.WeChatPay.WeChatPayV3Service.DecryptWithDotNet(Byte[] key, Byte[] nonce, Byte[] ciphertextBytes, Byte[] tag, Byte[] associatedData) in E:\BSApp\mall\labormall-master\dev\aspnet-core\src\LaborMall.Application\WeChatPay\WeChatPayV3Service.cs:line 497

   at LaborMall.WeChatPay.WeChatPayV3Service.AesGcmDecrypt(String associatedData, String nonce, String ciphertext) in E:\BSApp\mall\labormall-master\dev\aspnet-core\src\LaborMall.Application\WeChatPay\WeChatPayV3Service.cs:line 467

   at LaborMall.WeChatPay.WeChatPayV3Service.Decrypt(WeChatPayEncryptedData encryptedData) in E:\BSApp\mall\labormall-master\dev\aspnet-core\src\LaborMall.Application\WeChatPay\WeChatPayV3Service.cs:line 362

   at LaborMall.WeChatPay.WeChatPayV3Service.GetNonce() in E:\BSApp\mall\labormall-master\dev\aspnet-core\src\LaborMall.Application\WeChatPay\WeChatPayV3Service.cs:line 272

   at LaborMall.Communication.Controllers.WeChatPayController.PayNotification2Async(String input) in E:\BSApp\mall\labormall-master\dev\aspnet-core\hosts\LaborMall.Communication.Host\Controllers\WeChatPayController.cs:line 133

   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()

--- End of stack trace from previous location ---

   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)


以下是源码


using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using LaborMall.WeChatPay.Dtos;
using Aliyun.Credentials.Http;
using System.IO;
using Volo.Abp;
using LaborMall.Data;
using LaborMall.Favorites;
using Microsoft.Extensions.Logging;
using static System.Runtime.InteropServices.JavaScript.JSType;
using Quartz.Logging;
using Microsoft.IdentityModel.Tokens;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Engines;
using Quartz.Util;
using Org.BouncyCastle.Utilities;
using System.Net;
using Org.BouncyCastle.Crypto;
namespace LaborMall.WeChatPay
{   public class WeChatPayV3Service
    {
        private readonly string _apiUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
        private readonly string _oauthUrl = "https://api.weixin.qq.com/sns/oauth2/access_token";
        private readonly string _mchId;      // 商户号
        private readonly string _appId;      // 公众号AppId
        private readonly string _appSecret;   // 公众号AppSecret
        private readonly string _apiV3Key;   // APIv3密钥
        private readonly ICertificateService _certificateService;
        private readonly string _serialNo;   // 商户证书序列号
        private readonly HttpClient _httpClient;
        private readonly ILogger<WeChatPayV3Service> _logger;


        public WeChatPayV3Service(
            string mchId,
            string appId,
            string appSecret,
            string apiV3Key,
            ICertificateService certificateService, // 先注入证书服务
            string serialNo,
            ILogger<WeChatPayV3Service> logger,
            HttpClient httpClient = null)
        {
            _mchId = mchId;
            _appId = appId;
            _appSecret = appSecret;
            _apiV3Key = apiV3Key;
            _certificateService = certificateService;
            _serialNo = serialNo;
            _httpClient = httpClient ?? new HttpClient();
            _logger = logger;
        }


        /// <summary>
        /// 通过code获取用户OpenId
        /// </summary>
        public async Task<string> GetUserOpenIdAsync(string code)
        {
            if (string.IsNullOrEmpty(code))
            {
                throw new ArgumentException("授权码不能为空");
            }


            var url = $"{_oauthUrl}?appid={_appId}&secret={_appSecret}&code={HttpUtility.UrlEncode(code)}&grant_type=authorization_code";
            var response = await _httpClient.GetAsync(url);
            response.EnsureSuccessStatusCode();


            var content = await response.Content.ReadAsStringAsync();
            var tokenResponse = JObject.Parse(content);
            var openId = tokenResponse["openid"]?.ToString();


            if (string.IsNullOrEmpty(openId))
            {
                throw new Exception("获取OpenId失败");
            }


            return openId;
        }


        /// <summary>
        /// JSAPI下单
        /// </summary>
        /// <summary>
        /// JSAPI下单
        /// </summary>
        public async Task<string> CreateJsApiOrderAsync(
            string openId,
            string outTradeNo,
            string description,
            int totalAmount,
            string notifyUrl,
            string attach = null)
        {
            try
            {
                // 1. 构建请求体
                var body = new
                {
                    appid = _appId,
                    mchid = _mchId,
                    description = description,
                    out_trade_no = outTradeNo,
                    notify_url = notifyUrl,
                    amount = new { total = totalAmount, currency = "CNY" },
                    payer = new { openid = openId },
                    attach = attach,
                    time_expire = DateTime.UtcNow.AddMinutes(30).ToString("yyyy-MM-ddTHH:mm:sszzz")
                };


                string bodyJson = JsonConvert.SerializeObject(body);
                _logger.LogInformation($"请求体JSON: {bodyJson}");


                // 2. 生成签名并发送请求
                var request = new HttpRequestMessage(HttpMethod.Post, _apiUrl);
                request.Content = new StringContent(bodyJson, Encoding.UTF8, "application/json");


                string timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
                string nonce = Guid.NewGuid().ToString("N");


                _logger.LogInformation($"时间戳: {timestamp}");
                _logger.LogInformation($"随机数: {nonce}");


                // 使用新的API请求签名方法
                string signature = GenerateApiRequestSignature(request.Method.Method, _apiUrl, timestamp, nonce, bodyJson);


                string authHeader = GetAuthHeader(timestamp, nonce, signature);
                request.Headers.Add("Authorization", $"WECHATPAY2-SHA256-RSA2048 {authHeader}");
                request.Headers.Add("Accept", "application/json");
                request.Headers.Add("Wechatpay-Serial", _serialNo);
                request.Headers.Add("User-Agent", "LaborMallApp/1.0");


                // 记录完整的请求头信息
                _logger.LogInformation("请求头信息:");
                foreach (var header in request.Headers)
                {
                    _logger.LogInformation($"{header.Key}: {string.Join(", ", header.Value)}");
                }


                // 3. 发送请求并解析响应
                var response = await _httpClient.SendAsync(request);
                string responseJson = await response.Content.ReadAsStringAsync();


                _logger.LogInformation($"响应状态码: {response.StatusCode}");
                _logger.LogInformation($"响应内容: {responseJson}");


                if (!response.IsSuccessStatusCode)
                    throw new Exception($"微信支付请求失败: {response.StatusCode}\n{responseJson}");


                dynamic result = JsonConvert.DeserializeObject(responseJson);
                return result.prepay_id;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "JSAPI下单过程中发生错误");
                throw;
            }
        }


        /// <summary>
        /// 生成前端调起支付参数
        /// </summary>
        public Dictionary<string, string> GetJsApiSdkParams(string prepayId)
        {
            string timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
            string nonceStr = Guid.NewGuid().ToString("N");
            string package = $"prepay_id={prepayId}";


            string message = $"{_appId}\n{timestamp}\n{nonceStr}\n{package}\n";
            string paySign = GenerateSignature(message); // 修改后的签名方法


            return new Dictionary<string, string>
    {
        { "appId", _appId },
        { "timeStamp", timestamp },
        { "nonceStr", nonceStr },
        { "package", package },
        { "signType", "RSA" },
        { "paySign", paySign }
    };
        }
        /// <summary>
        /// 生成API请求签名 (用于微信支付V3 API请求)
        /// </summary>
        private string GenerateApiRequestSignature(string method, string url, string timestamp, string nonce, string body)
        {
            try
            {
                // 规范化URL路径
                var uri = new Uri(url);
                string path = uri.AbsolutePath;


                // 构造规范化的签名消息
                string message = $"{method.ToUpperInvariant()}\n{path}\n{timestamp}\n{nonce}\n{body}\n";


                _logger.LogInformation($"生成API请求签名的消息内容:\n{message}");


                return GenerateSignature(message);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "生成API请求签名时发生错误");
                throw;
            }
        }


        /// <summary>
        /// 基础签名方法 (实际执行RSA签名)
        /// </summary>
        private string GenerateSignature(string message)
        {
            using var rsa = _certificateService.LoadPrivateKey();
            byte[] data = Encoding.UTF8.GetBytes(message);
            byte[] signature = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
            string base64Sign = Convert.ToBase64String(signature);


            _logger.LogInformation($"生成的签名: {base64Sign}");
            return base64Sign;
        }
        private bool VerifySignature(string message, string signature, string privateKey)
        {
            using (var rsa = RSA.Create())
            {
                rsa.ImportFromPem(privateKey);
                return rsa.VerifyData(
                    Encoding.UTF8.GetBytes(message),
                    Convert.FromBase64String(signature),
                    HashAlgorithmName.SHA256,
                    RSASignaturePadding.Pkcs1);
            }
        }
        private string GetAuthHeader(string timestamp, string nonce, string signature)
        {
            // 确保所有值都是ASCII编码
            var authValues = new Dictionary<string, string>
    {
        { "mchid", _mchId },
        { "nonce_str", nonce },
        { "timestamp", timestamp },
        { "serial_no", _serialNo },
        { "signature", signature }
    };


            var sb = new StringBuilder();
            foreach (var kv in authValues)
            {
                if (sb.Length > 0)
                    sb.Append(",");
                sb.Append($"{kv.Key}=\"{kv.Value}\"");
            }


            return sb.ToString();
        }


        //////////////////////////////////////////////////////////////通知处理部分、、、、、、、、、、、、、、、、、、、
        public void GetNonce()
        {
            // 测试
            // var decryptor = new WeChatPayV3Decryptor("你的真实APIv3密钥", logger);
            // { "id":"fc46e8ac-c0b6-5397-93f8-c857106144f0","create_time":null,"event_type":null,"resource_type":null,"resource":{ "original_type":null,"algorithm":"AEAD_AES_256_GCM","ciphertext":"INPe4z+FBIOoIatLGoNMmVtdrD8GD/Ic9u/mrzgyjNcEgH81EXl690d5XqeZlWRc0kORrWJdMWeF0C3ir7Farg9MsR1FRmZv+ULFsEtbKkzTVfn87gBqFLNauJIVj1uAwSqPF9wHCt3NdB+71i8Jpb6Fa8Lovk/n1KDCd5oDyB7d5NZ7Pf3+Z5lBM64I7aiaoiRPE9dmkl9odTpM6yXdOa7iDaGSe7pqob4y0btE+siRqrH9lnfEqHv2rnxuA7+Ma1I/gd8lHCnNjSwPOCXF1N0V/TVbvg2wH4yIldvhafkWwi+FVstfl9APQj+2HRTk705ULnMcs7Hn3ySL3utE6fJtQnUgJPEG8x397ZjcSSQtO8+r+rJdOuevx7FKHAzeOP0ny/0fD45Eig07LoGnlwbPmDugaKc5U080XFbploOGxLoTW8jFpFAQYBJLldmLjMpX30GzyqTZxx9T6LlumO/vc0Cy6ebUrz7iOYjMmAIruFkJGvj4M+VgJWk95JUnwNJZHKbBDT+Gu4QaS/OA9/ZjJM+Fq2VPZhLyYlc0uP7svCIwMmz7R6iKb+zevUarst1aGRMTFA==","nonce":"sb51ZQpI9N43","associated_data":null},"summary":"支付成功"}
            // 处理回调
            var decryptedData = Decrypt(new WeChatPayEncryptedData
            {
                Algorithm = "AEAD_AES_256_GCM",
                Ciphertext = "bDEfmqkHlnR/h9GoUXmNtrdOZYse/hoY0EezSkx+esohSrm55riylrQIWaANNPTWk/bvEkIH9Gt7I0U3LTDfKjnVt3Y0MfAups1WwQF4dBB9juOCwUHLoYS4kpI8FOq0UAx+OIkEOC4e21uKdRlkxi9Y6V6jxgCEpz53+4pZ64CeCFboPcBxyQFOjd0dUqK3T4X2unBIuyXbs3FbjWDyRbashR7diXhjzUbO8OWrM/lN5VBvUGXtJb2mYRy4OOKC1Z0mL8t1zCecvx3wqc0xUVu4L0TI5Af2ma/4Jiz09v5dBhjiduOFClGPPgBKMnpyNnzEMldLVsrYnPxjsQkPW+2NCel5UMEz/W/JfD+MidlcUM9UTzMPZh1ZMkAQnjIDn2mzHh0dhLQF7y998ZHwccsMOo5bSvYTPSGDjyy/5S6MczUpjaZRlaDs0Hyz/Mzmaq2H7XSzGRWdp3/Hdb3p/VNLYvHvHQnsMWKGRq7LUB+GnLaHSSXiDEg0p8irl+XkcpVO007MrPOnbv6K7uGYaAVIBs2urJKA7H7ul45Fa2UnpwydhEEKFCkVX2Zdw9VDof6NtSsGYQ==",
                Nonce = "k2Nhn3Yjg2cv",
                AssociatedData = null
            });




            // 处理业务逻辑
            Console.WriteLine($"订单号: {decryptedData.OutTradeNo}, 金额: {decryptedData.Amount.Total}");


        }






        /// <summary>
        /// 处理微信支付通知
        /// </summary>
        [RemoteService(false)]
        public async Task<AppResult<string>> HandlePaymentNotificationAsync(WeChatPayNotifyRequestDto input)
        {

            try
            {
                // 1. 记录原始回调数据
                string rawJson = JsonConvert.SerializeObject(input);
                _logger.LogInformation($"收到支付回调: {rawJson}");


                // 2. 验证数据结构
                if (input?.Resource == null)
                    throw new ArgumentNullException("Resource");


                // 3. 解密数据
                WeChatPayDecryptedDataDto decryptedJson = Decrypt(input.Resource);


                // 4. 验证解密结果
                if (string.IsNullOrEmpty(decryptedJson.OutTradeNo))
                    throw new Exception("解密结果为空");


                // 5. 返回成功响应
                return new AppResult<string>
                {
                    Success = true,
                    Data = decryptedJson.OutTradeNo,
                };
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "处理支付回调失败");
                return new AppResult<string>
                {
                    Success = false,
                    Message = ex.Message
                };
            }
        }










        private async Task ProcessPaymentResultAsync(WeChatPayDecryptedDataDto data)
        {
            // 根据支付结果更新业务状态
            // 例如:更新订单状态为已支付
            _logger.LogInformation($"处理微信支付结果: 订单号={data.OutTradeNo}, 交易状态={data.TradeState}");


            // 这里可以添加你的业务逻辑
            // 例如:var order = await _orderRepository.FindAsync(x => x.OrderNo == data.OutTradeNo);
            // if (order != null) { ... }
        }


        /// <summary>
        /// 解密微信支付回调数据(最终版)
        /// </summary>
        public WeChatPayDecryptedDataDto Decrypt(WeChatPayEncryptedData encryptedData)
        {
            //  encryptedData.AssociatedData = encryptedData.AssociatedData ?? "resource_type=encrypt-resource";
            // 1. 验证并修复输入数据
            if (encryptedData == null) throw new ArgumentNullException(nameof(encryptedData));


            //var fixedData = ValidateAndFixEncryptedData(encryptedData);
            //   _logger.LogInformation($"解密参数 - Nonce: {fixedData.Nonce}, AssociatedData: {fixedData.AssociatedData}, Ciphertext长度: {fixedData.Ciphertext.Length}");


            string decryptedJson = AesGcmDecrypt(encryptedData.AssociatedData, encryptedData.Nonce, encryptedData.Ciphertext);
            // 6. 解析结果


            _logger.LogDebug($"解密成功,原始JSON: {decryptedJson}");


            return JsonConvert.DeserializeObject<WeChatPayDecryptedDataDto>(decryptedJson);


        }


        /// <summary>
        /// 证书/回调报文解密
        /// </summary>
        /// <param name="associatedData"></param>
        /// <param name="nonce"></param>
        /// <param name="ciphertext"></param>
        /// <returns></returns>

        public string AesGcmDecrypt(string associatedData, string nonce, string ciphertext)
        {


            // 1. 严格验证密钥格式
            if (string.IsNullOrEmpty(_apiV3Key))
                throw new ArgumentNullException(nameof(_apiV3Key));



            if (_apiV3Key.Length != 32)
                throw new ArgumentException($"APIv3密钥长度必须为32字节, 当前长度: {_apiV3Key.Length}");
            // 3. 记录密钥指纹用于验证
            using (var sha256 = SHA256.Create())
            {
                byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(_apiV3Key));
                string keyFingerprint = BitConverter.ToString(hash).Replace("-", "").Substring(0, 16);
                _logger.LogInformation($"APIv3密钥指纹: {keyFingerprint}");
            }
            // 4. 增强Base64处理
            string sanitizedCiphertext = ciphertext;


            // 4.1 移除所有非Base64字符
            sanitizedCiphertext = Regex.Replace(sanitizedCiphertext, @"[^a-zA-Z0-9\+/\-_=]", "");


            // 4.2 处理URL编码字符
            if (sanitizedCiphertext.Contains('%'))
            {
                try
                {
                    sanitizedCiphertext = WebUtility.UrlDecode(sanitizedCiphertext);
                    _logger.LogInformation("URL解码后: " + sanitizedCiphertext);
                }
                catch (Exception decodeEx)
                {
                    _logger.LogWarning($"URL解码失败: {decodeEx.Message}");
                }
            }

            // 4.3 标准化Base64字符
            sanitizedCiphertext = sanitizedCiphertext
                .Replace('_', '/')
                .Replace('-', '+');


            // 4.4 添加Base64填充
            int mod4 = sanitizedCiphertext.Length % 4;
            if (mod4 > 0) sanitizedCiphertext += new string('=', 4 - mod4);


            // 5. 尝试Base64解码
            byte[] ciphertextBytes;
         
                ciphertextBytes = Convert.FromBase64String(sanitizedCiphertext);
           

            // 6. 分离加密数据和认证标签
            if (ciphertextBytes.Length < 16)
                throw new ArgumentException("密文太短,无法包含认证标签");


            // 最后16字节是认证标签
            int ciphertextLength = ciphertextBytes.Length - 16;
            byte[] tag = new byte[16];
            byte[] actualCiphertext = new byte[ciphertextLength];


            // 复制前 N-16 字节为实际密文
            Buffer.BlockCopy(ciphertextBytes, 0, actualCiphertext, 0, ciphertextLength);
            // 复制最后16字节为标签
            Buffer.BlockCopy(ciphertextBytes, ciphertextLength, tag, 0, 16);


            _logger.LogInformation($"分离后的密文长度: {actualCiphertext.Length}, 标签: {BitConverter.ToString(tag).Replace("-", "")}");

            // 7. 准备参数
            byte[] key = Encoding.UTF8.GetBytes(_apiV3Key);
            byte[] nonceBytes = Encoding.UTF8.GetBytes(nonce);
            //byte[] adBytes = string.IsNullOrEmpty(associatedData) ? new byte[0] : Encoding.UTF8.GetBytes(associatedData ?? ""); 
            // 处理关联数据:区分 null 和空字符串
            byte[] adBytes;
            if (associatedData == null)
            {
                _logger.LogInformation("关联数据为 null,使用空字节数组");
                adBytes = Array.Empty<byte>();
            }
            else
            {
                adBytes = Encoding.UTF8.GetBytes(associatedData);
            }
            // 8. 使用.NET原生库解密
            return DecryptWithDotNet(key, nonceBytes, ciphertextBytes, tag, adBytes);
        }
        /// <summary>
        /// 使用.NET原生库进行解密
        /// </summary>
        private string DecryptWithDotNet(byte[] key, byte[] nonce, byte[] ciphertextBytes, byte[] tag, byte[] associatedData)
        {
            try
            {
               // byte[] plaintext = new byte[ciphertext.Length];


                _logger.LogInformation($"解密前的参数 - Key: {BitConverter.ToString(key).Replace("-", "")}, Nonce: {Encoding.UTF8.GetString(nonce)}, AD: {Encoding.UTF8.GetString(associatedData)}, , Tag: {BitConverter.ToString(tag).Replace("-", "")}");


                GcmBlockCipher gcmBlockCipher = new(new AesEngine());
                AeadParameters aeadParameters = new(
                    new KeyParameter(Encoding.UTF8.GetBytes(_apiV3Key)),
                    128,
                   nonce, //Encoding.UTF8.GetBytes(nonce),
                  associatedData);//  Encoding.UTF8.GetBytes(associatedData));
                gcmBlockCipher.Init(false, aeadParameters);


              
                // 2. 合并密文和认证标签(BouncyCastle要求)
                byte[] combinedCiphertext = new byte[ciphertextBytes.Length + tag.Length];
                Buffer.BlockCopy(ciphertextBytes, 0, combinedCiphertext, 0, ciphertextBytes.Length);
                Buffer.BlockCopy(tag, 0, combinedCiphertext, ciphertextBytes.Length, tag.Length);


                // 3. 执行解密
                byte[] plaintext = new byte[gcmBlockCipher.GetOutputSize(combinedCiphertext.Length)];
                int len = gcmBlockCipher.ProcessBytes(combinedCiphertext, 0, combinedCiphertext.Length, plaintext, 0);
                gcmBlockCipher.DoFinal(plaintext, len);


                _logger.LogInformation($"解密成功,明文: {Encoding.UTF8.GetString(plaintext)}");


                return Encoding.UTF8.GetString(plaintext);
            }
            catch (AuthenticationTagMismatchException ex)
            {
                // 详细记录错误信息
                _logger.LogError($"认证标签不匹配: " +
                    $"Key: {BitConverter.ToString(key).Replace("-", "").Substring(0, 8)}..., " +
                    $"Nonce: {Encoding.UTF8.GetString(nonce)}, " +
                    $"AD: {Encoding.UTF8.GetString(associatedData)}, " +
                    $"Ciphertext length: {ciphertextBytes.Length}, " +
                    $"Tag: {BitConverter.ToString(tag).Replace("-", "")}");


                throw new WeChatPayDecryptionException("解密失败: 认证标签不匹配", ex);
            }
        }
   
    
    }


    public class WeChatPayDecryptionException : Exception
    {
        public WeChatPayDecryptionException(string message, Exception innerException)
            : base(message, innerException)
        {
        }
    }


}


回答关注问题邀请回答
收藏

3 个回答

  • 支付社区运营
    支付社区运营
    06-23
    "associated_data":null这个不能为空的。解密需要byte[] associatedData,byte[] nonce,String ciphertext,以及32位的商户密钥
    参考如下:https://pay.weixin.qq.com/doc/v3/merchant/4012071382
    
    微信发的回调body也是有值的,完整格式如下:
    {"id":"da2b8a7f-dad3-5f69-b5eb-2446b94543","create_time":"2024-08-16T15:00:03+08:00","resource_type":"encrypt-resource","event_type":"TRANSACTION.SUCCESS","summary":"支付成功","resource":{"original_type":"transaction","algorithm":"AEAD_AES_256_GCM","ciphertext":"Zkwzt1znCk78U2/xRl2ZVHlwaxcTg02MWN/36VW3cTFGJJWCDucckbqEPFCt5HIAwuhz1+oVbI1NJDqqmdfJAKSeZvDWivzgkLehZSI211qZhPruF8+/Oo8fuEmTigQo6BL1XUDUKSyegcYphyU+hFayU6gXAa7hatmbOr36dALy5Ijb+o6f+VDJ33A77lGgFP5xD1IhaqLkaYnDseBxot5bn3fwtASpNN176VKnKDb4co5E73kJzgtF3qPZTv40Nud0qakFHjs8h7NI7/hKKZVRL8wd3NHXLFfWGlq11GBjpH4Pm7OtZMifdHEa2pxZDiiYnAdO49cx1EMToN+0cdKv4dxWLxsy4wJhja44zaIfaViGSxbOojo1017C1eNQvfLraEqRnASvkilb/MQUPhLNM93PI1I20M6rrsKMNsF0U3IKYHvay+r9V+twrzXuHbgBWWVUtxVwvpIEzuPkJ756zRK5mf/XOLyotW4E42DBVgJZ+11OyTY8aSANWUl2G4zJoVlQJgHgNy0JNF2w0/7kU02o6fkDp81lOP7QU/4PeyzIqchDoqqhxKGOfMrun+V6/avn+tcZ2qIK+2etIZ4NwTun6G/Q2Cn8hRUwcXU28eWJvMrFeg==","associated_data":"transaction","nonce":"Oiyu5riw0HOJ"}}
    



    06-23
    有用
    回复
  • Memory
    Memory
    06-22

    你的associated_data为什么没有值呢?

    06-22
    有用 1
    回复
  • 时空系
    时空系
    06-22

    2025-06-22 11:27:04.639 +08:00 [INF] 收到支付回调: {"id":"baf703ab-dbea-5293-9c8e-371145be20e0","create_time":null,"event_type":null,"resource_type":null,"resource":{"original_type":null,"algorithm":"AEAD_AES_256_GCM","ciphertext":"ztv2nfR3NtzUZ2YG3pBQlxZwOsng693/90z9FfQiQwpzgQEJxLdnBOXAcpPm3SyJr6CwmAG5F4x5LVihgiSvXe9K1tdWOG9mKapdiwOuka7toBaSmtvo2DT0IrAwOnqKTxpyWcCDLUQMLinrxNVoGccL0rRo5RZjAkb0bcPc/7qiC4cSVZB21Lh1P2Gc6VIts3K1REfu/vaczkIfi4MIH8aFkP64n4FElmdiztXzlpMbE3EQUP44cDebLCP8oLlHt3uPwnpjUtaTN3CaBe7Sdw0bGdWTl6HcA1sflNcMJt8b8oZgx0QcjzqP4bzWpm58JXVCv5cMPjgK6c6MgNSdgu0TIcA72J+uS62FahxoSGhpPY7NrxsAwiNsMZ6CsbGzED3jlOiG1qRDfY5a3e/K/B0wuepAfSZPKouCRFB2VZ0CoEmdtDB0tnTqpVKOWWxAHzudP9TYxwylYgwqQTGnYst1m3WrogV+SYg8hblbUGNL6ws21csBI5Ygo1GY2YR5mleDUQ3pjb6x1UhZQTzbzZkdFnvVKJEUdhov2uoxRxKOBS5zc7BSrT4xEKpPuAUB9z8N9Jappg==","nonce":"FrUTbzH9bpvp","associated_data":null},"summary":"支付成功"}

    2025-06-22 11:27:04.640 +08:00 [INF] APIv3密钥指纹: F6EE716DF91A4CAC

    2025-06-22 11:27:04.641 +08:00 [INF] 分离后的密文长度: 423, 标签: AD3E3110AA4FB80501F73F0DF496A9A6

    2025-06-22 11:27:04.641 +08:00 [INF] 关联数据为 null,使用空字节数组

    2025-06-22 11:27:04.663 +08:00 [INF] 解密前的参数 - Key: 3931333239353433303130383539343830343139383938343533373730393738, Nonce: FrUTbzH9bpvp, AD: , , Tag: AD3E3110AA4FB80501F73F0DF496A9A6

    2025-06-22 11:27:04.704 +08:00 [ERR] 处理支付回调失败

    Org.BouncyCastle.Crypto.InvalidCipherTextException: mac check in GCM failed

       at Org.BouncyCastle.Crypto.Modes.GcmBlockCipher.DoFinal(Span`1 output)

       at Org.BouncyCastle.Crypto.Modes.GcmBlockCipher.DoFinal(Byte[] output, Int32 outOff)

       at LaborMall.WeChatPay.WeChatPayV3Service.DecryptWithDotNet(Byte[] key, Byte[] nonce, Byte[] ciphertextBytes, Byte[] tag, Byte[] associatedData) in E:\BSApp\mall\labormall-master\dev\aspnet-core\src\LaborMall.Application\WeChatPay\WeChatPayV3Service.cs:line 496

       at LaborMall.WeChatPay.WeChatPayV3Service.AesGcmDecrypt(String associatedData, String nonce, String ciphertext) in E:\BSApp\mall\labormall-master\dev\aspnet-core\src\LaborMall.Application\WeChatPay\WeChatPayV3Service.cs:line 467

       at LaborMall.WeChatPay.WeChatPayV3Service.Decrypt(WeChatPayEncryptedData encryptedData) in E:\BSApp\mall\labormall-master\dev\aspnet-core\src\LaborMall.Application\WeChatPay\WeChatPayV3Service.cs:line 362

       at LaborMall.WeChatPay.WeChatPayV3Service.HandlePaymentNotificationAsync(WeChatPayNotifyRequestDto input) in E:\BSApp\mall\labormall-master\dev\aspnet-core\src\LaborMall.Application\WeChatPay\WeChatPayV3Service.cs:line 309

    官方文档里就没有值呀

    06-22
    有用
    回复 1
    • Memory
      Memory
      06-23
      要看你自己接受到的内容,而不是去看示例
      06-23
      回复
登录 后发表内容
问题标签