在使用 ThinkPHP 官方 WeChat 包進行微信公眾號開發時,普通模式正常,但啟用「安全模式」(加密模式)後消息解密失敗。經排查,官方解密類 WechatCrypt 返回的 XML 字串包含轉義字符(<\/tag>\n),導致 simplexml_load_string() 解析失敗。本文記錄根因分析與修復方案。
simplexml_load_string() 正常解析 XML。simplexml_load_string() 返回 false,無錯誤提示。WechatCrypt.class.php,邏輯一致,唯獨輸出 XML 格式異常。在 WechatCrypt::decrypt() 方法中,經過 Base64 解碼、AES-CBC 解密、PKCS7 去填充、去隨機串、驗證 AppId 後,直接返回 $text。實際輸出內容如下:
<xml>
<ToUserName><![CDATA[gh_249aeb986d99]]><\/ToUserName>\n
<FromUserName><![CDATA[oopVmxHZaeQkDPsRcbpwXKkH-J2Q]]><\/FromUserName>\n
<CreateTime>1448944621<\/CreateTime>\n
<MsgType><![CDATA[text]]><\/MsgType>\n
<Content><![CDATA[\u7ecf\u7406]]><\/Content>\n
<MsgId>6223169761311044588<\/MsgId>\n
<\/xml>
關鍵異常點:
<\/tag> 而非標準 </tag>。\n(兩字符),而非真正換行。simplexml_load_string() 無法識別 <\/tag> 為合法結束標籤,導致解析失敗。
在 WechatCrypt::decrypt() 返回前,添加兩行清洗代碼:
// 明文內容
$text = substr($decrypt, 4, $size);
// 【修復】清洗轉義字符
$text = str_replace('<\/', '</', $text); // 還原結束標籤
$text = str_replace('>\n', '>', $text); // 移除字面 \n
return $text;
清洗後 XML 變為標準格式,simplexml_load_string() 即可正常解析。
base64_decode($encrypt)substr($decrypt, 16)unpack('N', substr($decrypt, 0, 4)) 獲得 XML 長度。substr($decrypt, 4, $xml_len)回覆消息加密(encrypt())流程相反:XML → PKCS7 填充 → 隨機串 + 長度 + XML + AppId → AES 加密 → Base64。若自行構建 XML 回覆,確保輸出標準格式(無轉義斜槓、無字面 \n),避免微信服務端解密失敗。
// 臨時寫入文件檢查解密輸出
file_put_contents('/tmp/wechat_decrypt.xml', $text);
// 然後用瀏覽器或 xmllint 驗證
// xmllint --noout /tmp/wechat_decrypt.xml
| 坑點 | 表現 | 解決 |
|---|---|---|
| XML 轉義字符 | simplexml 解析失敗,無報錯 | str_replace 清洗 <\/ 和 >\n |
| EncodingAESKey 長度 | 解密報錯「非法密文」 | 確保 Base64 解碼後為 32 字節(256-bit) |
| 時間戳/Nonce 驗證失敗 | 簽名不匹配 | 嚴格按微信文檔排序拼接 SHA1 |
| 回覆超時 | 5 秒內未回覆,微信重試 | 異步處理業務,立即返回 success/空串 |
掌握微信加解密的數據流向與常見異常格式,能快速定位「安全模式」集成難題,保障公眾號消息通道穩定。