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
相关产品推荐
相关产品推荐

