Files
1818web-hoduan/COS_SIGNATURE_DEBUG.md
Claude Workbench e3e6f1f29d first commit
2026-02-13 18:18:20 +08:00

7.0 KiB
Raw Permalink Blame History

COS POST 签名算法详解与调试

🔍 签名计算步骤(已修正)

根据腾讯云官方文档COS POST Object 的签名计算步骤如下:

步骤 1生成 KeyTime

KeyTime = StartTimestamp;EndTimestamp

示例:

1567064374;1567071574

步骤 2构造 PolicyJSON 格式)

{
  "expiration": "2019-08-29T09:39:34.471Z",
  "conditions": [
    { "bucket": "examplebucket-1250000000" },
    [ "content-length-range", 1, 10485760 ],
    [ "starts-with", "$key", "user_img" ],
    { "q-sign-algorithm": "sha1" },
    { "q-ak": "************************************" },
    { "q-sign-time": "1567064374;1567071574" }
  ]
}

步骤 3生成 SignKey

SignKey = HMAC-SHA1(SecretKey, KeyTime)

注意: 结果是十六进制小写字符串

示例:

SignKey = HMAC-SHA1("your-secret-key", "1567064374;1567071574")
        = "39acc8c9f34ba5b19bce4e965b370cd3f62d2fba"

步骤 4生成 StringToSign

关键: StringToSign 是 Policy JSON 原文的 SHA1 哈希值

StringToSign = SHA1(Policy JSON 原文)

注意:

  • 使用 Policy 的 JSON 原文(未 Base64 编码)
  • 结果是十六进制小写字符串

示例:

Policy JSON: {"expiration":"2019-08-29T09:39:34.471Z","conditions":[...]}
StringToSign = SHA1(Policy JSON)
             = "d5d903b8360468bc81c1311f134989bc8c8b5b89"

步骤 5生成 Signature

Signature = HMAC-SHA1(SignKey, StringToSign)

注意: 结果是十六进制小写字符串

示例:

Signature = HMAC-SHA1("39acc8c9f34ba5b19bce4e965b370cd3f62d2fba", "d5d903b8360468bc81c1311f134989bc8c8b5b89")
          = "7758dc9a832e9d301dca704cacbf9d9f8172abcd"

正确的代码实现

// 1. 生成 KeyTime
long currentSeconds = System.currentTimeMillis() / 1000;
long expireSeconds = currentSeconds + 3600;
String keyTime = currentSeconds + ";" + expireSeconds;

// 2. 构造 Policy
Map<String, Object> policy = new HashMap<>();
policy.put("expiration", generateExpiration(3600L));
List<Object> conditions = new ArrayList<>();
conditions.add(Map.of("bucket", "your-bucket"));
conditions.add(Arrays.asList("content-length-range", 1, 10485760));
conditions.add(Arrays.asList("starts-with", "$key", "user_img"));
conditions.add(Map.of("q-sign-algorithm", "sha1"));
conditions.add(Map.of("q-ak", secretId));
conditions.add(Map.of("q-sign-time", keyTime));
policy.put("conditions", conditions);

String jsonPolicy = mapper.writeValueAsString(policy);

// 3. 生成 SignKey
String signKey = hmacSha1(secretKey, keyTime);

// 4. 生成 StringToSignPolicy JSON 原文的 SHA1
String stringToSign = sha1(jsonPolicy);

// 5. 生成 Signature
String signature = hmacSha1(signKey, stringToSign);

// 6. Base64 编码 Policy用于表单提交
String encodedPolicy = Base64.encodeBase64String(jsonPolicy.getBytes(StandardCharsets.UTF_8));

🔧 辅助方法

/**
 * HMAC-SHA1返回十六进制小写字符串
 */
private String hmacSha1(String key, String data) {
    SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
    Mac mac = Mac.getInstance("HmacSHA1");
    mac.init(secretKeySpec);
    byte[] hmacBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
    return toHexString(hmacBytes);  // 十六进制小写
}

/**
 * SHA1返回十六进制小写字符串
 */
private String sha1(String data) {
    MessageDigest digest = MessageDigest.getInstance("SHA-1");
    byte[] hash = digest.digest(data.getBytes(StandardCharsets.UTF_8));
    return toHexString(hash);  // 十六进制小写
}

/**
 * 字节数组转十六进制小写字符串
 */
private String toHexString(byte[] bytes) {
    StringBuilder sb = new StringBuilder();
    for (byte b : bytes) {
        sb.append(String.format("%02x", b));
    }
    return sb.toString();
}

🧪 调试步骤

1. 启用 DEBUG 日志

application.yml 中添加:

logging:
  level:
    com.dora.service.OssPostSignatureService: DEBUG

2. 查看日志输出

重新请求签名接口后,查看日志:

COS POST signature calculation:
  KeyTime: 1733472660;1733476260
  Policy JSON: {"expiration":"2025-12-06T12:51:00.000Z","conditions":[...]}
  StringToSign (SHA1 of Policy): 93e7e253c53fc6513ce0dc1c0dd34af925ad028f
  SignKey: 39acc8c9f34ba5b19bce4e965b370cd3f62d2fba
  Signature: 7758dc9a832e9d301dca704cacbf9d9f8172abcd

3. 对比 COS 返回的错误

如果 COS 返回签名错误,会显示它计算的 StringToSign

<Error>
  <Code>SignatureDoesNotMatch</Code>
  <Message>The Signature you specified is invalid.</Message>
  <StringToSign>93e7e253c53fc6513ce0dc1c0dd34af925ad028f</StringToSign>
</Error>

对比:

  • 如果 StringToSign 相同,说明 Policy 正确,但 Signature 计算错误
  • 如果 StringToSign 不同,说明 Policy 构造有问题

⚠️ 常见错误

错误 1使用 Base64 编码后的 Policy 计算 StringToSign

错误:

String encodedPolicy = Base64.encodeBase64String(jsonPolicy.getBytes());
String stringToSign = sha1(encodedPolicy);  // 错误!

正确:

String stringToSign = sha1(jsonPolicy);  // 使用 JSON 原文
String encodedPolicy = Base64.encodeBase64String(jsonPolicy.getBytes());

错误 2HMAC-SHA1 返回 Base64 而不是十六进制

错误:

return Base64.encodeBase64String(hmacBytes);  // 错误!

正确:

return toHexString(hmacBytes);  // 十六进制小写

错误 3Policy 中缺少必需的签名条件

错误:

{
  "conditions": [
    { "bucket": "xxx" }
    // 缺少 q-sign-algorithm, q-ak, q-sign-time
  ]
}

正确:

{
  "conditions": [
    { "bucket": "xxx" },
    { "q-sign-algorithm": "sha1" },
    { "q-ak": "AKID..." },
    { "q-sign-time": "1567064374;1567071574" }
  ]
}

📚 参考文档


验证清单

在部署前,确认以下几点:

  • KeyTime 格式正确:StartTimestamp;EndTimestamp
  • Policy 包含所有必需条件
  • StringToSign = SHA1(Policy JSON 原文)
  • SignKey = HMAC-SHA1(SecretKey, KeyTime)
  • Signature = HMAC-SHA1(SignKey, StringToSign)
  • 所有哈希值都是十六进制小写字符串
  • 表单提交时包含所有必需字段

🎯 总结

关键点:

  1. StringToSign 是 Policy JSON 原文的 SHA1不是 Base64 后的
  2. 所有哈希值都是十六进制小写字符串,不是 Base64
  3. Policy 中必须包含 q-sign-algorithm, q-ak, q-sign-time 条件
  4. 表单中必须包含 q-key-time 字段

修复后重新编译部署即可!