做微信商圈, 支付即积分,当用户使用微信支付后,微信会把支付信息回调到业务系统,业务系统按照预订的规则对会员进行积分,但就是一个微信商圈回调信息, 进行解密却报错Tag mismatch. 解密微信报错Tag mismath的报错信息如下.
Connected to the target VM, address: '127.0.0.1:50803', transport: 'socket'
Exception in thread "main" javax.crypto.AEADBadTagException: Tag mismatch!
at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:620)
at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1116)
at com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1053)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at javax.crypto.Cipher.doFinal(Cipher.java:2168)
at com.example.demo.index.AesUtil.decryptToString(AesUtil.java:38)
at com.example.demo.index.TestWechatHuangting.main(TestWechatHuangting.java:49)
Disconnected from the target VM, address: '127.0.0.1:50803', transport: 'socket'
翻遍全网, 给出了让人以为一定可行的几个方法. 首先是微信官方给的解决方法
官方:
官方给大家发了文档
参考这个排查文档,您这边仔细检查下:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay7_2.shtml
官方的声音简直是掷地有声:
使用Java解密时,抛出异常AEADBadTagException: Tag mismatch!
加密使用的AES-GCM包含了Galois Message Authentication Code (GMAC)的消息认证机制。解密时会对数据的完整性进行校验。出现tag mismatch异常,表示解密时的消息认证失败。通常有三种可能:
- 使用了错误的API v3密钥,如使用了其他商户号的密钥,或者使用了APIv2的APIKey。
- 密文不正确。请检查提交解密的密文和收到的密文。注意报文中的密文经过了Base64编码。
- 解密时接口遗漏传入附加数据(associated_data)
所以, 我们按图索骥, 三种可能一一排查. 果然......., 都被我们排查掉了.
上代码:
public static void main(String[] args) throws GeneralSecurityException, IOException {
/*
*
* 这一段是刚才回调回来的
*
* { "summary" : "支付成功",
"create_time" : "2022-03-23T16:16:50+08:00",
"resource" :{ "ciphertext" : "/w/WqKvC7S1+OLqAIWld/IGqFkHnT5gTZjW3+6BfArL6k47DYB+As/ObssxdT3qI16KX4JfXx7O35l4BId9JryoUyD1B+cLVJkiT/Rg5Dsk0P1IVzAX1hlx/sIM+B1HWx3xj4U8hNhHd8Ckitv+Hes7CNw2ysA/QJ0bLgG3d1DTwV1THpuVB5x/lPHdeAi/CAs0WHoyqaXOEVaD+4G3/87T22wUz9BEQbi5+XNd8idOGADyDzRU1iKChMTPanSvfBiDFyXNZjhsHmmdmgfpIDE4jTn6cO3ZbrW1dGkUmBbGjucJbIBPXCCLdDmhgJ4eZwg0h5W93vvcdjJnakx872HlkKNhRdOnLSfkSgbHOf55woqZyB9cnT0s1P0bemCknyDDT",
"nonce" : "eciTiCgUlhiP",
"associated_data" : "mall_transaction",
"original_type" : "mall_transaction",
"algorithm" : "AEAD_AES_256_GCM" },
"resource_type" : "encrypt-resource",
"event_type" : "MALL_TRANSACTION.SUCCESS",
"id" : "2507da91-0b81-551a-912c-e2dcf8bf8ba1"}
*
*
*
*
* */
//某商场广场
//密钥
String key = "api3key密钥";
AesUtil aesUtil = new AesUtil(key.getBytes(StandardCharsets.UTF_8));
String associatedData = "mall_transaction";
String nonce = "eciTiCgUlhiP";
String ciphertext = "/w/WqKvC7S1+OLqAIWld/IGqFkHnT5gTZjW3+6BfArL6k47DYB+As/ObssxdT3qI16KX4JfXx7O35l4BId9JryoUyD1B+cLVJkiT/Rg5Dsk0P1IVzAX1hlx/sIM+B1HWx3xj4U8hNhHd8Ckitv+Hes7CNw2ysA/QJ0bLgG3d1DTwV1THpuVB5x/lPHdeAi/CAs0WHoyqaXOEVaD+4G3/87T22wUz9BEQbi5+XNd8idOGADyDzRU1iKChMTPanSvfBiDFyXNZjhsHmmdmgfpIDE4jTn6cO3ZbrW1dGkUmBbGjucJbIBPXCCLdDmhgJ4eZwg0h5W93vvcdjJnakx872HlkKNhRdOnLSfkSgbHOf55woqZyB9cnT0s1P0bemCknyDDT";
String salesjson = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
System.out.println(salesjson);
}
就是这个feel, 我们的代码一点问题都没有, 上面只是提取出来的demo, aesUtil, 就是用的官方给的,我的怀疑精神顿时起来了, 难道我遇到的难得一见面的官方出错?
package com.example.demo.index;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class AesUtil {
static final int KEY_LENGTH_BYTE = 32;
static final int TAG_LENGTH_BIT = 128;
private final byte[] aesKey;
public AesUtil(byte[] key) {
if (key.length != KEY_LENGTH_BYTE) {
throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
}
this.aesKey = key;
}
public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)
throws GeneralSecurityException, IOException {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData);
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
}
最后检查, AESKEY没错, 字符集也没错, 数据也没少,究竟是什么原因?
我把这个demo, 用另一个商场的生产环境的回调报文,以及那个商场的aeskey进行解密, 结果,一点问题都没有,用户的支付信息一览无余.
{"mchid":"157保密9751","merchant_name":"保密 MALL","shop_name":"保密扒","shop_number":"11保密N2","openid":"oLb更要保密xv3bWQ","appid":"wx1d保密b","time_end":"2022-03-23T15:12:05+08:00","amount":3500,"transaction_id":"420000保密4356"}
Disconnected from the target VM, address: '127.0.0.1:52254', transport: 'socket'
所以, 最后, 我们把锅扔给了腾讯的微信技术小兄弟.但是并没有得到想要的回复.直到我们的队友喊出来那一句"我们着急上线,就卡在这个问题上了". 终于有了回响.
原来, 这个商场的微信商圈, 之前是启用的服务商模式, AesKey的密钥, 是在原服务商那边配置的. 所以, 再怎么修改自己小程序的密钥, 也不能解密, 因为, 加密的时候, 使用的是原服务商的密钥. 那个服务商是广州的某家公司.
最后终于真相大白,还是在密钥问题上.微信商圈回调解密报错Tag mismath的这个问题得意解决.
做微信商圈, 支付即积分,当用户使用微信支付后,微信会把支付信息回调到业务系统,业务系统按照预订的规则对会员进行积分,但就是一个微信商圈回调信息, 进行解密却报错Tag mismatch.
遇到Idea中was cached in the local repository, resolution will not be reattempted until的报错,通过了几种方式,也没能解决,最后终于处理好了.