You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言HMAC SHA256 Webhook签名验证异常排查求助

Go语言HMAC SHA256 Webhook签名验证异常排查求助

大家好,最近在把Node.js里跑的好好的Webhook签名验证逻辑迁移到Go,但死活生成不对正确的HMAC SHA256签名,已经排查掉了“错误把secret转成hex再传入HMAC”这个坑,但还是不行,想请大家帮忙看看还有哪里可能出问题!

先贴出两边的代码对比:

正常工作的Node.js代码

import crypto from "crypto";
import express from "express";

export const computeWebhookSignature = ({
    requestBody,
    secret,
    timestamp,
}: {
    requestBody: any;
    secret: string;
    timestamp: number;
}): string => {
    // 核心逻辑:拼接时间戳和JSON序列化后的请求体,再计算HMAC
    const payload = `${timestamp}.${JSON.stringify(requestBody)}`;
    const hmac = crypto.createHmac("sha256", secret);
    hmac.update(payload);
    return hmac.digest("hex");
};

// Webhook接收示例
const app = express();
app.use(express.json());

app.post("/webhook", (req, res) => {
    const signatureHeader = req.headers["x-webhook-signature"];
    const timestamp = parseInt(req.headers["x-webhook-timestamp"] as string);
    
    const computedSignature = computeWebhookSignature({
        requestBody: req.body,
        secret: process.env.WEBHOOK_SECRET!,
        timestamp,
    });

    if (computedSignature === signatureHeader) {
        res.status(200).send("签名验证通过");
    } else {
        res.status(403).send("签名无效");
    }
});

验证失败的Go代码

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
)

func computeWebhookSignature(requestBody interface{}, secret string, timestamp int64) string {
	// 这里已经修正了之前把secret转hex的错误
	bodyBytes, _ := json.Marshal(requestBody)
	payload := fmt.Sprintf("%d.%s", timestamp, bodyBytes)
	h := hmac.New(sha256.New, []byte(secret))
	h.Write([]byte(payload))
	return hex.EncodeToString(h.Sum(nil))
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
	signatureHeader := r.Header.Get("X-Webhook-Signature")
	timestampStr := r.Header.Get("X-Webhook-Timestamp")
	timestamp, _ := strconv.ParseInt(timestampStr, 10, 64)

	var requestBody interface{}
	json.NewDecoder(r.Body).Decode(&requestBody)

	computedSignature := computeWebhookSignature(requestBody, "my-webhook-secret", timestamp)

	if computedSignature == signatureHeader {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("签名验证通过"))
	} else {
		w.WriteHeader(http.StatusForbidden)
		w.Write([]byte("签名无效"))
		fmt.Printf("计算的签名:%s,收到的签名:%s\n", computedSignature, signatureHeader)
	}
}

func main() {
	http.HandleFunc("/webhook", webhookHandler)
	http.ListenAndServe(":8080", nil)
}

我现在怀疑几个方向,但不确定:

  • JSON序列化差异:Node.js的JSON.stringify()和Go的json.Marshal()处理对象的细节不一样,比如字段顺序、是否保留空格、空值的处理,会不会导致拼接的payload字符串不一样?
  • 请求体原始字节的读取:Node.js里express.json()拿到的req.body是解析后的对象,但Go里用json.NewDecoder读取后,有没有可能丢失了原始请求体的某些字节细节?比如有没有可能应该直接读取原始字节而不是先解析?
  • 时间戳的字符串格式:Node.js里timestamp是number转字符串,Go里是int64转字符串,会不会有格式差异?比如有没有可能时间戳的位数不对?
  • HMAC的输入编码:有没有可能在拼接payload时,字符串转字节的编码不一致?比如Node.js默认是UTF-8,Go里是不是也是用的UTF-8?

有没有大佬遇到过类似的问题,或者能帮我指出代码里的其他潜在问题?感激不尽!

备注:内容来源于stack exchange,提问作者Robert Seares

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.17 08:54:42