微信公众号消息解密需用crypto/aes的cbc模式,iv为16字节全0切片;密文前16字节是随机填充而非iv;encodingaeskey需base64解码得32字节密钥;解密后按pkcs#7去填充;gin路由须get/post同路径且禁用干扰中间件;签名验证需token/timestamp/nonce字典序拼接后sha1,时间偏差不可超5分钟;xml解析应手动处理,注意msgid大小写及createtime为秒级。

微信公众号消息解密必须用 crypto/aes 的 CBC 模式,且 IV 必须是 16 字节空字节切片
微信服务器发来的加密消息(EncryptMsg)是 AES-128-CBC 加密的,密文前 16 字节不是 IV,而是随机填充的 16 字节,真正的 IV 是全 0 的 16 字节切片。很多人误把前 16 字节当 IV,导致解密失败或乱码。
实际操作中需注意:
-
key是EncodingAESKey+ "==" 后 base64 解码得到的 32 字节密钥(不是原始字符串) - 先 base64 解码
EncryptMsg,再截掉开头 16 字节(随机填充),剩下部分才是待解密密文 - 使用
aes.NewCipher创建 cipher 后,必须用cipher.NewCBCDecrypter,且 IV 参数传make([]byte, 16) - 解密后要按 PKCS#7 填充规则去除末尾填充字节,否则 XML 解析会失败
Gin 路由必须同时支持 GET(验证)和 POST(接收消息),且不能有中间件干扰签名验证
微信服务器首次接入时会发 GET 请求校验 echostr,后续所有消息都是 POST。Gin 中必须用同一个路由路径(如 /wechat)处理两种方法,且不能挂载日志、JWT、CORS 等中间件——这些会修改请求体、Header 或响应状态,导致签名比对失败。
典型写法:
router := gin.New()
router.GET("/wechat", wechatVerify)
router.POST("/wechat", wechatReceive)
关键点:
-
wechatVerify中只做三件事:取signature、timestamp、nonce、echostr四个 query 参数,调用checkSignature验证,验证通过直接c.String(200, echostr) -
wechatReceive中禁用c.ShouldBindXML等自动解析,改用c.Request.Body原始读取,因为微信 POST 的 Content-Type 是text/xml,但 Gin 默认不识别 - 若用了
gin.Recovery(),确保它不拦截 400/500 级错误返回,否则微信会重试发送
消息签名验证失败的三个高频原因:参数未排序、hash 算法用错、时间戳偏差超 5 分钟
微信签名是把 token、timestamp、nonce 三者字典序升序拼接后 SHA1,不是简单相加或任意顺序。常见翻车点:
- 用
sort.Strings([]string{nonce, timestamp, token})错了——必须是{token, timestamp, nonce}排序后拼,不是按变量名排 - 用了
sha256.Sum256或md5,微信只认sha1.Sum([]byte(s)) - 服务器本地时间与微信服务器时间偏差 > 300 秒(5 分钟),微信会直接拒绝请求,返回 403;建议用
ntpdate -u time.windows.com或 systemd-timesyncd 校准 - 从
c.Request.URL.Query()取参数时没做url.QueryEscape逆操作,导致中文参数被转义,签名不一致
解密后的 XML 消息体必须手动提取 ToUserName、FromUserName 等字段,不能依赖结构体绑定
Gin 的 c.ShouldBindXML 在解密后直接调用会失败,因为微信原始 XML 的根节点是 xml(非标准命名),且字段名大小写与 Go 结构体 tag 不完全对应(如 MsgId 实际是 MsgID)。更稳妥的方式是用 encoding/xml 手动解析。
示例关键字段提取逻辑:
type WechatMsg struct {
ToUserName string `xml:"ToUserName"`
FromUserName string `xml:"FromUserName"`
CreateTime int64 `xml:"CreateTime"`
MsgType string `xml:"MsgType"`
Content string `xml:"Content"`
MsgID int64 `xml:"MsgId"` // 注意:XML 中是 MsgId,但文档写的是 MsgID
}
var msg WechatMsg
err := xml.Unmarshal(decryptedBytes, &msg)
特别注意:
-
MsgId在 XML 中是小写i,不是大写I,结构体 tag 必须写成xml:"MsgId" -
CreateTime是秒级时间戳(10 位),不是毫秒,直接赋给int64即可 - 如果消息类型是
event,Event字段在 XML 中存在,但普通文本消息没有,结构体里可加Event string `xml:"Event,omitempty"`
加密消息体里还混着一个 4 字节的 uint32(网络字节序)表示原始 XML 长度,以及一个 ToUserName(公众号原始 ID),这两项必须校验一致,否则可能是伪造请求。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











