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

Go与PHP间加密URL的HMAC签名验证问题排查

PHP生成带签名加密URL,Go端验证不匹配的问题分析与解决

核心差异点

  • 1. 密钥与盐的二进制处理不一致
    PHP中对密钥和盐做了十六进制转二进制处理:

    $signatureKeyBin = hex2bin($signatureKey);
    $signatureSaltBin = hex2bin($signatureSalt);
    

    但Go代码中直接将环境变量的字符串转为[]byte,没有做十六进制解码,导致HMAC使用的密钥/盐完全不同。

  • 2. 待签名内容拼接错误
    PHP中生成签名的待签内容是:

    $binarySignature = hash_hmac('sha256', $signatureSaltBin . $encryptedPath, $signatureKeyBin, true);
    

    其中$encryptedPath是"/enc/{$encryptedUrl}",最终签名的是二进制盐 + 字符串路径/enc/xxx。

    但Go的中间件里错误地拼接了带签名的路径:

    encPath := fmt.Sprintf("/%s/enc/%s", signature, encrypted)
    

    这导致待签内容变成了/签名值/enc/加密内容,和PHP的待签内容完全不符。

  • 3. Base64URL解码的潜在问题
    PHP的base64url_encode实现是去掉末尾的=,这和Go的base64.RawURLEncoding完全一致,这部分是没问题的,但要确保解码时用的是同一个标准。

修正后的Go代码

第一步:添加十六进制解码处理

首先需要将环境变量中的十六进制密钥/盐转成二进制:

import (
    "encoding/hex"
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "fmt"
    "os"
    "net/http"
    "github.com/gorilla/mux"
)

func getKeyAndSalt() ([]byte, []byte, error) {
    keyHex := os.Getenv("FILELIST_SECRET_KEY")
    saltHex := os.Getenv("FILELIST_SECRET_SALT")
    
    key, err := hex.DecodeString(keyHex)
    if err != nil {
        return nil, nil, err
    }
    salt, err := hex.DecodeString(saltHex)
    if err != nil {
        return nil, nil, err
    }
    return key, salt, nil
}

第二步:修正待签名内容的拼接

中间件里应该用正确的encryptedPath(即/enc/xxx),而不是带签名的路径:

func SignatureVerificationMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        vars := mux.Vars(r)
        signature := vars["signature"]
        encrypted := vars["encrypted"]

        // 正确的待签路径:和PHP的$encryptedPath完全一致
        encPath := fmt.Sprintf("/enc/%s", encrypted)

        key, salt, err := getKeyAndSalt()
        if err != nil {
            // 替换为你的错误输出逻辑
            fmt.Fprintf(w, "Invalid key/salt: %v", err)
            w.WriteHeader(http.StatusInternalServerError)
            return
        }

        if !verifySignature(signature, encPath, key, salt) {
            // 替换为你的错误输出逻辑
            fmt.Fprint(w, "Invalid signature")
            w.WriteHeader(http.StatusForbidden)
            return
        }

        next.ServeHTTP(w, r)
    })
}

第三步:修正verifySignature函数

传入正确的二进制密钥和盐,确保待签内容拼接逻辑和PHP对齐:

func verifySignature(signature, encryptedPath string, key, salt []byte) bool {
    decodedSignature, err := base64.RawURLEncoding.DecodeString(signature)
    if err != nil {
        fmt.Printf("Invalid signature decode error: %v\n", err)
        return false
    }

    mac := hmac.New(sha256.New, key)
    // 先写入二进制盐,再写入路径字节,和PHP的拼接逻辑一致
    mac.Write(salt)
    mac.Write([]byte(encryptedPath))
    expectedMAC := mac.Sum(nil)

    return hmac.Equal(decodedSignature, expectedMAC)
}

验证要点

  • 确保Go环境变量中的密钥/盐是和PHP完全一致的十六进制字符串,无额外空格或换行
  • 测试时可分别输出PHP和Go中待签内容的十六进制表示,确认两者完全一致
  • 对比两边生成的HMAC二进制值,再对比Base64URL编码后的结果,精准定位差异点

内容的提问来源于stack exchange,提问作者carry0987

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 23:47:35