GoLang实现与PHP等效的Shopify HMAC校验不匹配问题排查
问题原因
你的Go实现存在三个核心问题,按优先级排序如下:
低级语法错误
Go语言中仅支持用单引号包裹单个字符(rune类型),多字符字符串必须用双引号包裹。你代码中创建密钥的写法[]byte('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')本身就会编译失败,首先要修正为[]byte("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")。逻辑错误:重复序列化JSON
这是导致校验失败的最核心原因:你将请求数据反序列化为Go结构体后,又重新用json.Marshal序列化,此时得到的字节流和PHP侧json_encode的结果大概率不一致,原因包括:
- Go标准库
json.Marshal默认会对<、>、&等HTML特殊字符做Unicode转义,而PHP的json_encode默认不开启转义 - 不同语言对浮点数格式化、空值处理、字段排序的规则存在差异,都会导致序列化后的内容不一致
正确做法是直接读取请求的原始Body字节流计算HMAC,不要做任何序列化/反序列化操作,避免两边序列化规则差异导致的输入不一致。
- 无用逻辑冗余
Shopify返回的X-Shopify-Hmac-Sha256请求头是base64编码的结果,和你PHP侧的逻辑完全一致,hex编码的校验分支完全无用,可以直接删除。
修正后代码示例
import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "io" "bytes" "net/http" ) // 校验逻辑 func verifyShopifyHMAC(r *http.Request) (bool, error) { // 读取原始请求Body rawBody, err := io.ReadAll(r.Body) if err != nil { return false, err } // 回填Body,避免后续业务逻辑读不到请求数据 r.Body = io.NopCloser(bytes.NewBuffer(rawBody)) // 计算HMAC secret := []byte("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") h := hmac.New(sha256.New, secret) h.Write(rawBody) calculatedHmac := base64.StdEncoding.EncodeToString(h.Sum(nil)) // 校验对比,使用hmac.Equal避免时序攻击 requestHmac := r.Header.Get("X-Shopify-Hmac-Sha256") return hmac.Equal([]byte(calculatedHmac), []byte(requestHmac)), nil }
如果确实需要使用反序列化后的input结构体重新序列化计算,需要禁用Go JSON序列化的HTML转义,对齐PHP的默认行为:
var buf bytes.Buffer enc := json.NewEncoder(&buf) enc.SetEscapeHTML(false) // 禁用HTML转义 if err := enc.Encode(input); err != nil { // 错误处理 } // 去掉Encode默认追加的换行符 inputData := bytes.TrimSuffix(buf.Bytes(), []byte("\n"))
内容的提问来源于stack exchange,提问作者user14041681
相关产品推荐
相关产品推荐

