VM 模式 API 签名鉴权开发文档 (VM4-HMAC-SHA256)
本接口采用 VM4-HMAC-SHA256 签名算法,用于确保请求的来源合法性、数据完整性及防重放攻击。所有请求必须在 Header 中携带签名信息。
参数 | 值 | 说明 |
Header 前缀 |
| 所有自定义头均以此开头 |
Service Name |
| 签名 Scope 中的服务标识 |
算法标识前缀 |
| Authorization 头的起始标识 |
Scope 后缀 |
| 派生密钥计算的结尾字符 |
Header 键 | 示例值 | 说明 |
x-vm-date |
| 请求发出的 UTC 时间,格式: |
x-vm-content-sha256 |
| 请求体 (Body) 的 SHA256 哈希值(小写十六进制) |
Authorization |
| 完整的签名凭证字符串 |
x-vm-date,如果与服务器时间偏差超过 15分钟,请求将被拒绝按顺序拼接以下字符串(以 \n 分隔):
HTTPMethod: 如 GET, POST。
CanonicalURI: 编码后的路径(如 /api/v1/instances)。若为空则使用 /。
CanonicalQueryString: 参数按 ASCII 码升序排列并 URL 编码后的字符串。若无参数则为空。 CanonicalHeaders: 必须包含 host、x-vm-content-sha256、x-vm-date。格式为:key:value\n(注意 key 必须小写)。
空行: 在 Headers 拼接完成后,必须多加一个换行符。
SignedHeaders: 参与签名的 Header 键名列表,小写并以分号分隔。
PayloadHash: x-vm-content-sha256 的值(即 Body 的 SHA256 十六进制字符串)。
示例
GET
/v1/instances
Limit=10&status=running
host:api.example.com
x-vm-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-vm-date:20260326T113306Z
host;x-vm-content-sha256;x-vm-date
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855将元数据与步骤一生成的规范请求哈希值按以下顺序拼接(以 \n 分隔):
Algorithm: 固定值 VM4-HMAC-SHA256。
RequestDateTime: x-vm-date 的值(例如 20260326T113306Z)。
CredentialScope: 包含日期、区域和服务标识的字符串。格式为:{DateStamp}/{Region}/v3/vm4_request。
DateStamp: 格式为 YYYYMMDD(如 20260326)。
Region: 请求的目标区域ID或名称,非必填(如 1965717130594750464 或 cn-north-1 )。 HashedCanonicalRequest: 将步骤 1中构建的 Canonical Request 整个字符串进行 SHA256 哈希计算,并转换为小写十六进制字符串。
示例
VM4-HMAC-SHA256
20260326T113306Z
20260326/cn-north-1/v3/vm4_request
765e92d7b1d12c8a3e8a4d2f9b8c7a6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a派生密钥是通过对 SecretKey 进行多次哈希计算得到的。请按以下顺序执行 HMAC-SHA256 操作(伪代码逻辑):
kDate: 使用 VM4 + SecretKey 作为 Key,对 DateStamp(YYYYMMDD)进行哈希。 HMAC-SHA256("VM4" + SecretKey, "20260326")
kRegion: 使用上一步得到的 kDate 作为 Key,对 Region(如 cn-north-1)进行哈希。 HMAC-SHA256(kDate, "cn-north-1")
kService: 使用上一步得到的 kRegion 作为 Key,对 ServiceName(固定为 v3)进行哈希。 HMAC-SHA256(kRegion, "v3")
kSigning: 使用上一步得到的 kService 作为 Key,对 Suffix(固定为 vm4_request)进行哈希。 HMAC-SHA256(kService, "vm4_request")
使用您的 SecretKey 依次进行 HMAC-SHA256 计算:
// 注意:VM4 是作为 SecretKey 的前缀使用的
kDate := hmacSha256([]byte("VM4" + secretKey), "20260326")
kRegion := hmacSha256(kDate, "cn-north-1")
kService := hmacSha256(kRegion, "v3")
kSigning := hmacSha256(kService, "vm4_request") // 最终得到的 kSigning 用于步骤 4使用生成的派生密钥(Signing Key)对偏移量和请求元数据进行最后的完整性签名。
Key: 使用 步骤 3 中计算出的最后一个二进制字节数组 kSigning。
Data: 使用 步骤 2 中构建的完整 String To Sign 字符串。
HMAC 计算: 使用 HMAC-SHA256 算法,以 kSigning 为密钥,对 String To Sign 进行哈希计算。
十六进制转换: 将上一步得到的二进制结果转换为 小写十六进制(Lowercase Hex) 字符串。这就是最终的 Signature。
// 使用步骤 3 的结果 kSigning 和步骤 2 的结果 stringToSign
signatureBytes := hmacSha256(kSigning, stringToSign)
signature := hex.EncodeToString(signatureBytes) Authorization: VM4-HMAC-SHA256 Credential=AK_EXAMPLE/20260326/region-1/v3/vm4_request, SignedHeaders=host;x-vm-content-sha256;x-vm-date, Signature=fe5f...以下示例演示了如何在不依赖外部框架的情况下,为 HTTP 请求手动计算并添加 VM 模式签名。
package main
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"net/http"
"time"
)
// HmacSha256 辅助工具
func hmacSha256(key []byte, data string) []byte {
h := hmac.New(sha256.New, key)
h.Write([]byte(data))
return h.Sum(nil)
}
// 计算派生密钥 (Signing Key)
func getVM4SigningKey(secretKey, dateStamp, regionName string) []byte {
kDate := hmacSha256([]byte("VM4"+secretKey), dateStamp)
kRegion := hmacSha256(kDate, regionName)
kService := hmacSha256(kRegion, "v3") // ServiceName 为 v3
kSigning := hmacSha256(kService, "vm4_request")
return kSigning
}
func signVMRequest(req *http.Request, ak, sk, region, payloadHash string) {
now := time.Now().UTC()
amzDate := now.Format("20060102T150405Z")
dateStamp := now.Format("20060102")
// 1. 设置必要 Header
req.Header.Set("x-vm-date", amzDate)
// 计算 Payload Hash (假设无 Body 或已处理)
if payloadHash == "" {
payloadHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
req.Header.Set("x-vm-content-sha256", payloadHash)
// 2. 构建 Canonical Request
signedHeaders := "host;x-vm-content-sha256;x-vm-date"
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s",
req.Method,
req.URL.Path,
req.URL.RawQuery,
"host:"+req.Host,
"x-vm-content-sha256:"+payloadHash,
"x-vm-date:"+amzDate+"\n",
signedHeaders,
payloadHash,
)
// 3. 构建 StringToSign
credentialScope := fmt.Sprintf("%s/%s/v3/vm4_request", dateStamp, region)
hash := sha256.Sum256([]byte(canonicalRequest))
stringToSign := fmt.Sprintf("VM4-HMAC-SHA256\n%s\n%s\n%s",
amzDate,
credentialScope,
hex.EncodeToString(hash[:]),
)
fmt.Println("StringToSign:", stringToSign)
// 4. 计算签名
signingKey := getVM4SigningKey(sk, dateStamp, region)
signature := hex.EncodeToString(hmacSha256(signingKey, stringToSign))
// 5. 设置 Authorization Header
authHeader := fmt.Sprintf("VM4-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s",
ak, credentialScope, signedHeaders, signature)
req.Header.Set("Authorization", authHeader)
}
func main() {
var body = []byte(`{"offset":1,"limit":1}`)
// 计算 Payload Hash
hash := sha256.Sum256(body)
payloadHash := hex.EncodeToString(hash[:])
req, _ := http.NewRequest("GET", "http://127.0.0.1:8080/aaa-2100002392/?delimiter=%2F&encoding-type=url&fetch-owner=true&list-type=2&prefix=", bytes.NewReader(body))
req.Host = "127.0.0.1:8080"
ak := "CUS1****SVTK"
sk := "OPD3****E27G"
region := "la"
signVMRequest(req, ak, sk, region, payloadHash)
fmt.Println("Authorization Header:", req.Header.Get("Authorization"))
}为了确保对接成功,请按以下顺序检查:
路径编码:CanonicalURI 必须以 / 开头。如果路径为空,请使用 /。
参数排序:CanonicalQueryString 中的参数必须按键名的 ASCII 码升序排列。
小写转换:SignedHeaders 中的所有键名必须转换为小写。哈希一致性:x-vm-content-sha256 必须与请求体实际内容的 SHA256 值完全一致。
错误码 | 说明 |
UNAUTHORIZED: Signature mismatch | 签名计算不一致,请检查密钥及 Canonical Request 构建逻辑。 |
UNAUTHORIZED: x-amz-date skew too large | 客户端时间偏差超过 15 分钟。请同步 NTP 时间。 |
UNAUTHORIZED: Missing x-vm-date | 缺少 |