# COS POST 签名算法详解与调试

## 🔍 签名计算步骤（已修正）

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

### 步骤 1：生成 KeyTime

```
KeyTime = StartTimestamp;EndTimestamp
```

**示例：**
```
1567064374;1567071574
```

---

### 步骤 2：构造 Policy（JSON 格式）

```json
{
  "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"
```

---

## ✅ 正确的代码实现

```java
// 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. 生成 StringToSign（Policy 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));
```

---

## 🔧 辅助方法

```java
/**
 * 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` 中添加：

```yaml
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：

```xml
<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

❌ **错误：**
```java
String encodedPolicy = Base64.encodeBase64String(jsonPolicy.getBytes());
String stringToSign = sha1(encodedPolicy);  // 错误！
```

✅ **正确：**
```java
String stringToSign = sha1(jsonPolicy);  // 使用 JSON 原文
String encodedPolicy = Base64.encodeBase64String(jsonPolicy.getBytes());
```

---

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

❌ **错误：**
```java
return Base64.encodeBase64String(hmacBytes);  // 错误！
```

✅ **正确：**
```java
return toHexString(hmacBytes);  // 十六进制小写
```

---

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

❌ **错误：**
```json
{
  "conditions": [
    { "bucket": "xxx" }
    // 缺少 q-sign-algorithm, q-ak, q-sign-time
  ]
}
```

✅ **正确：**
```json
{
  "conditions": [
    { "bucket": "xxx" },
    { "q-sign-algorithm": "sha1" },
    { "q-ak": "AKID..." },
    { "q-sign-time": "1567064374;1567071574" }
  ]
}
```

---

## 📚 参考文档

- [腾讯云 COS POST Object 官方文档](https://cloud.tencent.com/document/product/436/14690)
- [COS 请求签名算法](https://cloud.tencent.com/document/product/436/7778)
- [COS 签名工具（在线验证）](https://cloud.tencent.com/document/product/436/30442)

---

## ✅ 验证清单

在部署前，确认以下几点：

- [ ] 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` 字段

**修复后重新编译部署即可！**
