4. AWS S3 签名版本:Signature V2 与 Signature V4 的区别

AWS S3 提供了两种请求签名协议:Signature V2(已弃用)和 Signature V4(当前标准)。理解两者的差异是正确实现对象存储权限验证的基础。V4 签名不仅增强了安全性,还引入了区域和服务绑定的概念,彻底改变了请求的构造方式。

4.1 核心差异对比

对比维度 Signature V2 Signature V4
哈希算法 HMAC-SHA1 HMAC-SHA256
签名密钥派生 直接使用 AccessKey Secret 基于 Secret + 日期 + 区域 + 服务 分层派生
请求头要求 仅需 Date 或 Expires 必须包含 x-amz-date 和 Host;推荐包含 Content-SHA256
Payload 校验 不校验请求体 可选校验(通过 x-amz-content-sha256)
区域绑定 无 签名密钥绑定区域(如 us-east-1)
服务绑定 无 签名密钥绑定服务(如 s3)
URL 过期签名 Query 参数较少 Query 参数更多,包含 Credential/SignedHeaders/Signature
安全性 较低(易受重放攻击) 较高(时间窗口 + 区域隔离)
当前状态 已弃用(2020 年后新建存储桶不再支持) 强制使用
迁移建议:所有新项目必须使用 Signature V4。如果现有系统仍在使用 V2,应尽快迁移,因为 AWS 已逐步关闭对 V2 的支持,且大多数兼容 S3 的对象存储(如 MinIO、Ceph)默认也推荐 V4。

4.2 V4 签名流程详解

Signature V4 的签名过程分为四个阶段,每一步都严格依赖前一步的输出。下面通过一个完整的流程图展示整体脉络:

Signature V4 签名流程 Signature V4 签名流程 ① 构造规范请求 ② 生成签名字符串 ③ 派生签名密钥 ④ 计算签名并附加 关键输入: • 规范请求 = HTTP方法 + 规范URI + 规范查询字符串 + 规范头 + 签名头 + Payload哈希 • 签名字符串 = 算法 + 时间戳 + 凭证范围 + 规范请求哈希 • 派生密钥 = HMAC-SHA256( HMAC-SHA256( HMAC-SHA256( HMAC-SHA256("AWS4" + SecretKey, 日期), 区域), 服务), "aws4_request" ) 最终输出:将签名附加到请求头(Authorization Header)或查询参数(Query String)中

4.3 分步拆解:规范请求(Canonical Request)

规范请求是 V4 签名的第一个核心产物,它将原始 HTTP 请求标准化为一个字符串。构造步骤如下:

  1. HTTP 方法:大写形式,如 PUT、GET。
  2. 规范 URI:对路径部分进行 URI 编码,保留斜杠。
  3. 规范查询字符串:对 Query 参数按字典序排序,进行 URI 编码,用 & 连接。
  4. 规范头:将所有请求头名称转为小写,按字典序排序,格式为 key:value\n。
  5. 签名头列表:参与签名的头名称列表(小写、排序、分号分隔)。
  6. Payload 哈希:对请求体计算 SHA256 哈希(十六进制字符串)。
Go 代码示例:构造规范请求
func buildCanonicalRequest(method, uri, query string, headers map[string]string, payload []byte) string {
    // 1. 排序并规范化请求头
    var keys []string
    for k := range headers {
        keys = append(keys, strings.ToLower(k))
    }
    sort.Strings(keys)

    var headerParts []string
    for _, k := range keys {
        headerParts = append(headerParts, k+":"+strings.TrimSpace(headers[k]))
    }
    canonicalHeaders := strings.Join(headerParts, "\n") + "\n"

    // 2. 签名头列表
    signedHeaders := strings.Join(keys, ";")

    // 3. Payload 哈希
    payloadHash := sha256Hex(payload)

    // 4. 组合规范请求
    return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s",
        method,
        uri,
        query,
        canonicalHeaders,
        signedHeaders,
        payloadHash,
    )
}

func sha256Hex(data []byte) string {
    h := sha256.Sum256(data)
    return hex.EncodeToString(h[:])
}

4.4 分步拆解:签名字符串与派生密钥

签名字符串(StringToSign)将规范请求与算法、时间戳、凭证范围绑定:

StringToSign = "AWS4-HMAC-SHA256" + "\n" +
               yyyyMMddTHHmmssZ + "\n" +   // ISO 8601 时间戳
               YYYYMMDD + "/" + region + "/s3/aws4_request" + "\n" +
               Hex(SHA256(CanonicalRequest))

派生密钥(Signing Key)通过四层 HMAC-SHA256 嵌套生成:

kSecret  = "AWS4" + SecretAccessKey
kDate    = HMAC-SHA256(kSecret, YYYYMMDD)
kRegion  = HMAC-SHA256(kDate, region)
kService = HMAC-SHA256(kRegion, "s3")
kSigning = HMAC-SHA256(kService, "aws4_request")
常见错误:派生密钥时,日期必须与请求头 x-amz-date 中的日期一致,且使用 UTC 时间。如果日期不匹配,AWS 会返回 SignatureDoesNotMatch 错误。另外,区域名称必须小写(如 us-east-1),服务名称固定为 s3。

4.5 何时使用 V4 签名

以下场景必须或强烈建议使用 Signature V4:

  • 所有新建 AWS S3 存储桶:自 2020 年 6 月起,新创建的存储桶仅支持 V4。
  • 需要跨区域访问:V4 签名绑定区域,可防止请求被重放到其他区域。
  • 需要 Payload 完整性校验:上传大文件或敏感数据时,V4 的 Content-SHA256 校验确保请求体未被篡改。
  • 使用 AWS SDK 或 CLI:所有现代 AWS SDK(v2 及以上)默认使用 V4。
  • 兼容 S3 的对象存储:MinIO、Ceph RGW、阿里云 OSS 等均支持 V4,且部分已弃用 V2。
  • 安全合规要求严格的环境:金融、医疗、政府等场景强制使用 V4。

下面用一张图总结 V4 签名的完整数据流:

V4 签名数据流与依赖关系 V4 签名数据流与依赖关系 HTTP 请求参数 Secret Access Key 区域 / 服务 / 日期 规范请求 派生签名密钥 签名字符串 最终签名 (Authorization Header) 最终签名 = Hex( HMAC-SHA256(派生密钥, 签名字符串) ) 附加方式:Authorization: AWS4-HMAC-SHA256 Credential=AKID/YYYYMMDD/region/s3/aws4_request, SignedHeaders=..., Signature=...

4.6 何时可以继续使用 V2(仅限遗留系统)

虽然 V2 已被弃用,但在以下受限场景中可能仍需处理 V2 签名:

  • 维护旧版 SDK 或客户端:某些嵌入式设备或老旧系统无法升级到 V4。
  • 兼容第三方网关:部分反向代理或缓存层仅实现了 V2 验证。
  • 读取历史日志:分析过往请求时,需要理解 V2 签名的格式。
最佳实践:即使需要兼容 V2,也应在服务端同时支持 V4,并逐步将客户端迁移到 V4。在 Go 中,可以使用 aws-sdk-go-v2 的 s3.NewFromConfig() 自动处理 V4 签名,无需手动实现。