核心内容摘要
WarcraftHelper:让经典魔兽争霸III焕发新生的优化插件全解析
搭建查券公众号后台微信 XML 消息加解密与 AES 容错机制深度踩坑记录大家好我是 微赚淘客系统
0 的研发者省赚客在接入微信公众号“消息加解密”模式安全模式时我们遭遇了大量因AES/CBC/PKCS7Padding实现差异、Base64 编码不一致、XML 特殊字符转义等问题导致的验签失败。
本文复盘真实生产环境中的典型坑点并给出基于 JDK 原生加密库的完整容错实现。
微信加解密流程回顾用户发送消息 → 微信服务器使用AES 加密CBC 模式 PKCS7 填充推送至开发者服务器携带msg_signature、timestamp、nonce、encrypt_type开发者需用token、timestamp、nonce、msg_encrypt生成 SHA1 签名验证签名一致后解密 msg_encrypt得到原始 XML回复消息也需加密并返回。
关键参数EncodingAESKey43 字节 Base64 字符串实际为 32 字节 AES 密钥 1 字节随机填充AppID用于验证解密内容完整性。
JDK 原生 AES 解密实现含 PKCS7Java 默认不支持 PKCS7但PKCS5 与 PKCS7 在 16 字节块下等价可直接使用packagejuwatech.cn.wx.crypto;importjavax.crypto.Cipher;importjavax.crypto.spec.IvParameterSpec;importjavax.crypto.spec.SecretKeySpec;importjava.nio.charset.StandardCharsets;importjava.util.Arrays;publicclassWxAesDecryptor{publicstaticStringdecrypt(StringencryptedMsg,StringencodingAesKey,StringappId){byte[]aesKeyBase
getDecoder().decode(encodingAesKey);// 补齐 Base64 paddingif(aesKey.length!
{thrownewIllegalArgumentException(AES key must be 32 bytes);}byte[]encryptedDataBase
getDecoder().decode(encryptedMsg);SecretKeySpeckeySpecnewSecretKeySpec(aesKey,AES);IvParameterSpecivnewIvParameterSpec(Arrays.copyOfRange(aesKey,0,
);try{CiphercipherCipher.getInstance(AES/CBC/NoPadding);// 注意不能用 PKCS5Paddingcipher.init(Cipher.DECRYPT_MODE,keySpec,iv);byte[]decryptedcipher.doFinal(encryptedData);// 手动去除 PKCS7 填充intpaddecrypted[decrypted.length-1]0xFF;if(pad1||pad
{thrownewRuntimeException(Invalid PKCS7 padding);}decryptedArrays.copyOfRange(decrypted,0,decrypted.length-pad);// 提取明文结构[16B random][xmlLen(4B)][xml][appId]intxmlLenbytesToIntBigEndian(decrypted,
;StringxmlContentnewString(decrypted,20,xmlLen,StandardCharsets.UTF_
;StringextractedAppIdnewString(decrypted,20xmlLen,decrypted.length-20-xmlLen,StandardCharsets.UTF_
;if(!extractedAppId.equals(appId)){thrownewRuntimeException(AppID mismatch: expectedappId, gotextractedAppId);}returnxmlContent;}catch(Exceptione){thrownewRuntimeException(AES decrypt failed,e);}}privatestaticintbytesToIntBigEndian(byte[]src,intoffset){return((src[offset]0xFF)
|((src[offset1]0xFF)
|((src[offset2]0xFF)
|(src[offset3]0xFF);}}踩坑点 1Cipher.getInstance(AES/CBC/PKCS5Padding)会导致解密后多出 16 字节乱码因为微信使用的是NoPadding 手动 PKCS7必须手动去填充。
Base64 编码兼容性处理微信官方 SDK 使用Apache Commons Codec的 Base64而 JDK 的Base
getDecoder()对缺失 padding敏感。
踩坑点 2EncodingAESKey 是 43 字节字符串无直接解码会抛IllegalArgumentException。
解决方案自动补全 paddingprivatestaticStringensureBase64Padding(Stringinput){intmodinput.length()%4;if(mod
returninput;returninput.substring(mod);}调用时byte[]aesKeyBase
getDecoder().decode(ensureBase64Padding(encodingAesKey));
XML 特殊字符转义容错用户输入可能包含,,等字符微信加密前会进行 XML 转义但部分第三方工具未转义导致解密后解析失败。
踩坑点 3解密得到的 XML 包含未转义的DOM 解析报错The entity name must immediately follow the in the entity reference。
容错方案预处理非法字符publicstaticStringsanitizeXml(Stringxml){returnxml.replace(,amp;).replace(,lt;).replace(,gt;).replace(\,quot;).replace(,apos;);}但注意仅在确定原始内容未转义时使用否则会双重转义。
更安全的做法是捕获解析异常后重试Documentdoc;try{docDocumentBuilderFactory.newInstance().newDocumentBuilder().parse(newInputSource(newStringReader(xml)));}catch(SAXParseExceptione){// 尝试修复StringfixedXmlxml.replaceAll((?!(amp|lt|gt|quot|apos);),amp;);docDocumentBuilderFactory.newInstance().newDocumentBuilder().parse(newInputSource(newStringReader(fixedXml)));}
签名验证容错微信计算msg_signature的顺序为sha1(sort(token, timestamp, nonce, msg_encrypt))。
踩坑点 4msg_encrypt是 Base64 字符串但部分开发者误传原始字节数组的 Hex 或 URL 编码。
正确实现publicstaticbooleanverifySignature(Stringtoken,Stringtimestamp,Stringnonce,StringmsgEncrypt,Stringsignature){String[]arrnewString[]{token,timestamp,nonce,msgEncrypt};Arrays.sort(arr);StringcontentString.join(,arr);StringcalcSigDigestUtils.sha1Hex(content);returncalcSig.equals(signature);}其中msgEncrypt必须是微信 POST 中的Encrypt标签内的原始 Base64 字符串含换行需 trim。
完整 Controller 示例RestControllerpublicclassWxMessageController{PostMapping(/wx/callback)publicStringhandleWxMessage(RequestParamStringsignature,RequestParamStringtimestamp,RequestParamStringnonce,RequestParamStringencrypt_type,RequestBodyStringrequestBody){if(!aes.equals(encrypt_type)){thrownewIllegalArgumentException(Only aes supported);}//
提取 Encrypt 内容StringencryptedMsgextractTagValue(requestBody,Encrypt);//
验签if(!WxSignatureUtil.verifySignature(your_token,timestamp,nonce,encryptedMsg,signature)){thrownewSecurityException(Invalid signature);}//
解密StringxmlWxAesDecryptor.decrypt(encryptedMsg,your_encoding_aes_key,your_appid);//
处理业务如查券StringreplyCouponService.handle(xml);//
加密回复略对称流程returnbuildEncryptedResponse(reply);}privateStringextractTagValue(Stringxml,StringtagName){intstartxml.indexOf(tagName)tagName.length()2;intendxml.indexOf(/tagName);returnxml.substring(start,end).trim();}}通过上述容错机制公众号消息解密成功率从 82% 提升至
9
98%。
本文著作权归 微赚淘客系统