收藏
回答

V3版支付,支付后解密回调信息时失败,提示:mac check in GCM failed?

/// <summary>
/// 解密微信支付回调中的加密资源(AES-256-GCM)
/// </summary>
/// <param name="callbackBody">回调原始Body</param>
/// <param name="apiV3Key">APIv3密钥(32位)</param>
/// <returns>解密后的支付结果JSON</returns>
public string DecryptPaymentData(string callbackBody, string apiV3Key)
{
    try
    {
        // 记录原始回调数据(用于调试)
        _logger.Info($"原始回调数据: {callbackBody}");


        // 1. 验证APIv3密钥长度(必须32位)
        if (apiV3Key.Length != 32)
        {
            throw new ArgumentException($"APIv3密钥长度错误(实际:{apiV3Key.Length}位,要求:32位)");
        }


        // 2. 解析回调Body,提取加密资源
        JObject callbackJson = JObject.Parse(callbackBody);
        JObject resource = callbackJson["resource"] as JObject;
        if (resource == null)
            throw new KeyNotFoundException("回调Body中未找到resource节点");


        // 3. 提取解密参数(严格对应微信字段)
        string algorithm = resource["algorithm"]?.ToString() ?? "";
        string ciphertext = resource["ciphertext"]?.ToString() ?? "";
        string nonce = resource["nonce"]?.ToString() ?? "";
        string associatedData = resource["associated_data"]?.ToString() ?? "";
        string originalType = resource["original_type"]?.ToString() ?? "";


        // 记录参数用于调试
        _logger.Info($"算法: {algorithm}(需为AEAD_AES_256_GCM)");
        _logger.Info($"原始Nonce: {nonce}(长度:{nonce.Length}字符)");
        _logger.Info($"原始Associated Data: {associatedData}");
        _logger.Info($"原始类型: {originalType}");
        _logger.Info($"Ciphertext长度: {ciphertext.Length}");


        // 验证参数非空
        if (string.IsNullOrEmpty(ciphertext))
            throw new ArgumentException("ciphertext为空");
        if (string.IsNullOrEmpty(nonce))
            throw new ArgumentException("nonce为空");
        if (algorithm != "AEAD_AES_256_GCM")
            throw new ArgumentException($"不支持的加密算法:{algorithm}(要求AEAD_AES_256_GCM)");


        // 4. 转换参数为字节数组(核心修正)
        byte[] keyBytes = Encoding.UTF8.GetBytes(apiV3Key); // 密钥:UTF8编码(32字节)


        // Nonce:直接UTF8编码(微信是12字符明文,无需Base64解码)
        byte[] nonceBytes = Encoding.UTF8.GetBytes(nonce);


        // 关联数据:为空时传null(微信规范)
        byte[] associatedDataBytes = string.IsNullOrEmpty(associatedData)
            ? null
            : Encoding.UTF8.GetBytes(associatedData);


        // 密文:Base64解码
        byte[] ciphertextBytes = Convert.FromBase64String(ciphertext);


        // 5. 严格验证参数长度(微信强制规范)
        if (nonceBytes.Length != 12)
        {
            throw new ArgumentException(
                $"nonce长度错误(原始字符串:{nonce},UTF8编码后{nonceBytes.Length}字节,要求12字节)。" +
                $"可能原因:回调数据被截断,或nonce提取错误。"
            );
        }
        if (ciphertextBytes.Length <= 16)
            throw new ArgumentException($"ciphertext长度不足(实际:{ciphertextBytes.Length}字节,需>16字节,确保包含16字节Tag)");


        // 6. 记录转换后参数(调试用)
        _logger.Info($"APIv3密钥长度: {keyBytes.Length}(正确应为32)");
        _logger.Info($"Nonce长度: {nonceBytes.Length}(正确应为12)");
        _logger.Info($"关联数据长度: {associatedDataBytes?.Length ?? 0}");
        _logger.Info($"密文长度: {ciphertextBytes.Length}");
        _logger.Info($"Nonce内容: {BitConverter.ToString(nonceBytes)}");
        _logger.Info($"关联数据内容: {associatedData}");


        // 7. 执行解密
        byte[] plaintextBytes = Decrypt(
            ciphertextBytes: ciphertextBytes,
            keyBytes: keyBytes,
            nonceBytes: nonceBytes,
            associatedDataBytes: associatedDataBytes,
            tagLength: 16
        );


        // 8. 转换为明文
        string plaintext = Encoding.UTF8.GetString(plaintextBytes);
        _logger.Info($"解密成功: {plaintext}");
        return plaintext;
    }
    catch (Exception ex)
    {
        _logger.Error($"解密回调数据失败:{ex.Message}", ex);
        throw new InvalidOperationException($"解密回调数据失败:{ex.Message}", ex);
    }
}


/// <summary>
/// AES-256-GCM解密(严格遵循微信支付规范)
/// </summary>
public byte[] Decrypt(
    byte[] ciphertextBytes,
    byte[] keyBytes,
    byte[] nonceBytes,
    byte[] associatedDataBytes = null,
    int tagLength = 16)
{
    try
    {
        // 分割密文和Tag(微信ciphertext格式:密文 + 16字节Tag)
        byte[] ciphertext = new byte[ciphertextBytes.Length - tagLength];
        byte[] tag = new byte[tagLength];
        Array.Copy(ciphertextBytes, 0, ciphertext, 0, ciphertext.Length);
        Array.Copy(ciphertextBytes, ciphertext.Length, tag, 0, tagLength);


        _logger.Info($"密文部分长度: {ciphertext.Length}");
        _logger.Info($"Tag长度: {tag.Length}(正确应为16)");
        _logger.Info($"Tag内容: {BitConverter.ToString(tag)}");


        // 初始化GCM引擎
        GcmBlockCipher cipher = new GcmBlockCipher(new AesEngine());
        AeadParameters parameters = new AeadParameters(
            new KeyParameter(keyBytes),
            tagLength * 8, // Tag长度(位)
            nonceBytes,    // 随机数(必须与加密时一致)
            associatedDataBytes // 关联数据(可为null)
        );


        // 初始化解密模式
        cipher.Init(false, parameters);


        // 执行解密
        byte[] output = new byte[cipher.GetOutputSize(ciphertext.Length)];
        int len = cipher.ProcessBytes(ciphertext, 0, ciphertext.Length, output, 0);
        cipher.DoFinal(output, len); // 此处会校验MAC,失败则抛异常


        return output;
    }
    catch (InvalidCipherTextException ex)
    {
        // MAC校验失败(最可能:密钥错误、nonce错误、数据篡改)
        _logger.Error($"AES-GCM解密失败(MAC校验不通过): {ex.Message}", ex);
        _logger.Error($"失败参数: 密钥={BitConverter.ToString(keyBytes)}, Nonce={BitConverter.ToString(nonceBytes)}");
        throw new CryptographicException("AES-GCM解密失败(密钥错误、数据篡改或参数不匹配)", ex);
    }
    catch (Exception ex)
    {
        _logger.Error($"AES-GCM解密过程异常: {ex.Message}", ex);
        throw new CryptographicException("AES-GCM解密过程发生异常", ex);
    }
}
[2025-09-10 08:50:37.393] [INFO] Cnso.Wechat.Payment.Services.WechatPayV3NotifyService: 原始回调数据: {"id":"143cca6d-1e34-5f05-a732-dbfb8b797b19","create_time":"2025-09-10T08:50:36+08:00","resource_type":"encrypt-resource","event_type":"TRANSACTION.SUCCESS","summary":"支付成功","resource":{"original_type":"transaction","algorithm":"AEAD_AES_256_GCM","ciphertext":"Fdk3D38J+AnhticRwDPONqkY/z+Puhi04F8fpRi1TOIh5agrHGcmRWKHECIjwrRExkqaGw/fdPcI58Yp5WWxLNE7Fzy6sY9CxbTW2lPvjcZshgrtGHwuhm19lnxQ926mc9t9cQf56ZARklXeYchYk6fIqC7XAt8URp7+VHO9NrUu9Rat/TsYSvJIzuUpiHRu7W/mfhllO676mOs5MTPstEIHq87x1QMVpnjhiJiYdZPMt8kSx2EVg+D8VgrebAHaV5rIRkrDyFLEyA5t3cwCjF57bOIxjtxKuGLMKFpSaBpdkztwUH6d104O7kCoDzxfIK81pLgv47NXZWiT14YBhUJhcg8S2Nkn9zZwWkOJnVlnV39UtaDgeWohuSTjyyOLT8girIJ6xd4cXsbaFmaX4HTgAtH9k/opBWhjn3nNtYqTwUBE/9HeHHjXFN6n/mBS3J9azcUT1ZjUiCXWluxUN0Vb7RQCGQv8OHBnCVW4cKaKX0c8GJoy63QdS8/0k9FfN918SkiQhXvFgGa4AlU4LnUdomoAukR05ujSPWhU+tiwY2n5NTWOpDGtEJ1STuRdYlgp6XHL17xEk+n4m+toyYHt","associated_data":"transaction","nonce":"G4LcckuzL8Vv"}}

[2025-09-10 08:50:37.393] : 算法: AEAD_AES_256_GCM(需为AEAD_AES_256_GCM)

[2025-09-10 08:50:37.393] : 原始Nonce: G4LcckuzL8Vv(长度:12字符)

[2025-09-10 08:50:37.393] : 原始Associated Data: transaction

[2025-09-10 08:50:37.393] : 原始类型: transaction

[2025-09-10 08:50:37.393] : Ciphertext长度: 600

[2025-09-10 08:50:37.393] : APIv3密钥长度: 32(正确应为32)

[2025-09-10 08:50:37.393] : Nonce长度: 12(正确应为12)

[2025-09-10 08:50:37.393] : 关联数据长度: 11

[2025-09-10 08:50:37.393] : 密文长度: 450

[2025-09-10 08:50:37.393] : Nonce内容: 47-34-4C-63-63-6B-75-7A-4C-38-56-76

[2025-09-10 08:50:37.393] : 关联数据内容: transaction

[2025-09-10 08:50:37.393] : 密文部分长度: 434

[2025-09-10 08:50:37.393] : Tag长度: 16(正确应为16)

[2025-09-10 08:50:37.393] : Tag内容: 29-E9-71-CB-D7-BC-44-93-E9-F8-9B-EB-68-C9-81-ED

[2025-09-10 08:50:37.424] [ERROR] Cnso.Wechat.Payment.Services.WechatPayV3NotifyService: AES-GCM解密失败(MAC校验不通过): mac check in GCM failed

异常: mac check in GCM failed

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

2 个回答

  • Memory (私信不回复)
    Memory (私信不回复)
    2025-09-10

    先校验一下你的 key 对不对https://tools.aifuwu.net/wechat-pay-tools/apiv3-key-validator

    2025-09-10
    有用
    回复 2
    • tina✨🌺
      tina✨🌺
      2025-09-10
      ❤谢谢你回复,刚用工具校验key是对的,可以解密成功。
      2025-09-10
      回复
    • tina✨🌺
      tina✨🌺
      2025-09-10
      已经修改代码解密成功了
      2025-09-10
      回复
  • 智能回答 智能回答 本次回答由AI生成
    2025-09-10
    有用
    回复
登录 后发表内容