接口签名v3
最近更新时间: 2022-05-10 11:14:34
tcecloud API 会对每个访问请求进行身份验证,即每个请求都需要在公共请求参数中包含签名信息(Signature)以验证请求者身份。 签名信息由安全凭证生成,安全凭证包括 SecretId 和 SecretKey;若用户还没有安全凭证,请前往云API密钥页面申请,否则无法调用云API接口。
1. 申请安全凭证
在第一次使用云API之前,请前往云API密钥页面申请安全凭证。 安全凭证包括 SecretId 和 SecretKey:
- SecretId 用于标识 API 调用者身份
- SecretKey 用于加密签名字符串和服务器端验证签名字符串的密钥。
- 用户必须严格保管安全凭证,避免泄露。
申请安全凭证的具体步骤如下:
- 登录tcecloud管理中心控制台。
- 前往云API密钥的控制台页面
- 在云API密钥页面,点击【新建】即可以创建一对SecretId/SecretKey
注意:开发商帐号最多可以拥有两对 SecretId / SecretKey。
2. TC3-HMAC-SHA256 签名方法
注意:对于GET方法,只支持 Content-Type: application/x-www-form-urlencoded 协议格式。对于POST方法,目前支持 Content-Type: application/json 以及 Content-Type: multipart/form-data 两种协议格式,json 格式默认所有业务接口均支持,multipart 格式只有特定业务接口支持,此时该接口不能使用 json 格式调用,参考具体业务接口文档说明。
下面以云服务器查询广州区实例列表作为例子,分步骤介绍签名的计算过程。我们仅用到了查询实例列表的两个参数:Limit 和 Offset,使用 GET 方法调用。
假设用户的 SecretId 和 SecretKey 分别是:AKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE 和 Gu5t9xGARNpq86cd98joQYCN3EXAMPLE
2.1. 拼接规范请求串
按如下格式拼接规范请求串(CanonicalRequest):
CanonicalRequest =
HTTPRequestMethod + '\n' +
CanonicalURI + '\n' +
CanonicalQueryString + '\n' +
CanonicalHeaders + '\n' +
SignedHeaders + '\n' +
HashedRequestPayload
- HTTPRequestMethod:HTTP 请求方法(GET、POST ),本示例中为 GET;
- CanonicalURI:URI 参数,API 3.0 固定为正斜杠(/) ;
- CanonicalQueryString:发起 HTTP 请求 URL 中的查询字符串,对于 POST 请求,固定为空字符串,对于 GET 请求,则为 URL 中问号(?)后面的字符串内容,本示例取值为:Limit=10&Offset=0。注意:CanonicalQueryString 需要经过 URL 编码。
- CanonicalHeaders:参与签名的头部信息,至少包含 host 和 content-type 两个头部,也可加入自定义的头部参与签名以提高自身请求的唯一性和安全性。拼接规则:1)头部 key 和 value 统一转成小写,并去掉首尾空格,按照 key:value\n 格式拼接;2)多个头部,按照头部 key(小写)的字典排序进行拼接。此例中为:
content-type:application/x-www-form-urlencoded\nhost:cvm.fincloud.tencent.cn\n
- SignedHeaders:参与签名的头部信息,说明此次请求有哪些头部参与了签名,和 CanonicalHeaders 包含的头部内容是一一对应的。content-type 和 host 为必选头部。拼接规则:1)头部 key 统一转成小写;2)多个头部 key(小写)按照字典排序进行拼接,并且以分号(;)分隔。此例中为:
content-type;host
- HashedRequestPayload:请求正文的哈希值,计算方法为 Lowercase(HexEncode(Hash.SHA256(RequestPayload))),对 HTTP 请求整个正文 payload 做 SHA256 哈希,然后十六进制编码,最后编码串转换成小写字母。注意:对于 GET 请求,RequestPayload 固定为空字符串,对于 POST 请求,RequestPayload 即为 HTTP 请求正文 payload。
根据以上规则,示例中得到的规范请求串如下(为了展示清晰,\n 换行符通过另起打印新的一行替代):
GET
/
Limit=10&Offset=0
content-type:application/x-www-form-urlencoded
host:cvm.fincloud.tencent.cn
content-type;host
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
2.2. 拼接待签名字符串
按如下格式拼接待签名字符串:
StringToSign =
Algorithm + \n +
RequestTimestamp + \n +
CredentialScope + \n +
HashedCanonicalRequest
- Algorithm:签名算法,目前固定为 TC3-HMAC-SHA256;
- RequestTimestamp:请求时间戳,即请求头部的 X-TC-Timestamp 取值,如上示例请求为 1539084154;
- CredentialScope:凭证范围,格式为 Date/service/tc3_request,包含日期、所请求的服务和终止字符串(tc3_request)。Date 为 UTC 标准时间的日期,取值需要和公共参数 X-TC-Timestamp 换算的 UTC 标准时间日期一致;service 为产品名,必须与调用的产品域名一致,例如 cvm。如上示例请求,取值为 2018-10-09/cvm/tc3_request;
- HashedCanonicalRequest:前述步骤拼接所得规范请求串的哈希值,计算方法为 Lowercase(HexEncode(Hash.SHA256(CanonicalRequest)))。
注意:
- Date 必须从时间戳 X-TC-Timestamp 计算得到,且时区为 UTC+0。如果加入系统本地时区信息,例如东八区,将导致白天和晚上调用成功,但是凌晨时调用必定失败。假设时间戳为 1551113065,在东八区的时间是 2019-02-26 00:44:25,但是计算得到的 Date 取 UTC+0 的日期应为 2019-02-25,而不是 2019-02-26。
- Timestamp 必须是当前系统时间,且需确保系统时间和标准时间是同步的,如果相差超过五分钟则必定失败。如果长时间不和标准时间同步,可能导致运行一段时间后,请求必定失败(返回签名过期错误)。
根据以上规则,示例中得到的待签名字符串如下(为了展示清晰,\n 换行符通过另起打印新的一行替代):
TC3-HMAC-SHA256
1539084154
2018-10-09/cvm/tc3_request
91c9c192c14460df6c1ffc69e34e6c5e90708de2a6d282cccf957dbf1aa7f3a7
2.3. 计算签名
1)计算派生签名密钥,伪代码如下
SecretKey = "Gu5t9xGARNpq86cd98joQYCN3EXAMPLE"
SecretDate = HMAC_SHA256("TC3" + SecretKey, Date)
SecretService = HMAC_SHA256(SecretDate, Service)
SecretSigning = HMAC_SHA256(SecretService, "tc3_request")
- SecretKey:原始的 SecretKey;
- Date:即 Credential 中的 Date 字段信息,如上示例,为2018-10-09;
- Service:即 Credential 中的 Service 字段信息,如上示例,为 cvm;
2)计算签名,伪代码如下
Signature = HexEncode(HMAC_SHA256(SecretSigning, StringToSign))
- SecretSigning:即以上计算得到的派生签名密钥;
- StringToSign:即步骤2计算得到的待签名字符串;
2.4. 拼接 Authorization
按如下格式拼接 Authorization:
Authorization =
Algorithm + ' ' +
'Credential=' + SecretId + '/' + CredentialScope + ', ' +
'SignedHeaders=' + SignedHeaders + ', '
'Signature=' + Signature
- Algorithm:签名方法,固定为 TC3-HMAC-SHA256;
- SecretId:密钥对中的 SecretId;
- CredentialScope:见上文,凭证范围;
- SignedHeaders:见上文,参与签名的头部信息;
- Signature:签名值
根据以上规则,示例中得到的值为:
TC3-HMAC-SHA256 Credential=AKIDEXAMPLE/Date/service/tc3_request, SignedHeaders=content-type;host, Signature=5da7a33f6993f0614b047e5df4582db9e9bf4672ba50567dba16c6ccf174c474
最终完整的调用信息如下:
https://cvm.fincloud.tencent.cn/?Limit=10&Offset=0
Authorization: TC3-HMAC-SHA256 Credential=AKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE/2018-10-09/cvm/tc3_request, SignedHeaders=content-type;host, Signature=5da7a33f6993f0614b047e5df4582db9e9bf4672ba50567dba16c6ccf174c474
Content-Type: application/x-www-form-urlencoded
Host: cvm.fincloud.tencent.cn
X-TC-Action: DescribeInstances
X-TC-Version: 2017-03-12
X-TC-Timestamp: 1539084154
X-TC-Region: ap-guangzhou
3. 签名失败
根据实际情况,存在以下签名失败的错误码,请根据实际情况处理
错误代码 | 错误描述 |
---|---|
AuthFailure.SignatureExpire | 签名过期 |
AuthFailure.SecretIdNotFound | 密钥不存在 |
AuthFailure.SignatureFailure | 签名错误 |
AuthFailure.TokenFailure | token 错误 |
AuthFailure.InvalidSecretId | 密钥非法(不是云 API 密钥类型) |
4. 签名演示
Java
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HttpsURLConnection;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.codec.digest.DigestUtils;
public class TceCloudAPITC3Demo {
private final static String CHARSET = "UTF-8";
private final static String ENDPOINT = "cvm.fincloud.tencent.cn";
private final static String PATH = "/";
private final static String SECRET_ID = "AKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE";
private final static String SECRET_KEY = "Gu5t9xGARNpq86cd98joQYCN3EXAMPLE";
private final static String CT_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded";
private final static String CT_JSON = "application/json";
private final static String CT_FORM_DATA = "multipart/form-data";
public static byte[] sign256(byte[] key, String msg) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
mac.init(secretKeySpec);
return mac.doFinal(msg.getBytes(CHARSET));
}
public static void main(String[] args) throws Exception {
String service = "cvm";
String host = "cvm.fincloud.tencent.cn";
String region = "ap-guangzhou";
String action = "DescribeInstances";
String version = "2017-03-12";
String algorithm = "TC3-HMAC-SHA256";
String timestamp = "1539084154";
//String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 注意时区,否则容易出错
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));
// ************* 步骤 1:拼接规范请求串 *************
String httpRequestMethod = "GET";
String canonicalUri = "/";
String canonicalQueryString = "Limit=10&Offset=0";
String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n" + "host:" + host + "\n";
String signedHeaders = "content-type;host";
String hashedRequestPayload = DigestUtils.sha256Hex("");
String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n"
+ canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload;
System.out.println(canonicalRequest);
// ************* 步骤 2:拼接待签名字符串 *************
String credentialScope = date + "/" + service + "/" + "tc3_request";
String hashedCanonicalRequest = DigestUtils.sha256Hex(canonicalRequest.getBytes(CHARSET));
String stringToSign = algorithm + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
System.out.println(stringToSign);
// ************* 步骤 3:计算签名 *************
byte[] secretDate = sign256(("TC3" + SECRET_KEY).getBytes(CHARSET), date);
byte[] secretService = sign256(secretDate, service);
byte[] secretSigning = sign256(secretService, "tc3_request");
String signature = DatatypeConverter.printHexBinary(sign256(secretSigning, stringToSign)).toLowerCase();
System.out.println(signature);
// ************* 步骤 4:拼接 Authorization *************
String authorization = algorithm + " " + "Credential=" + SECRET_ID + "/" + credentialScope + ", "
+ "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
System.out.println(authorization);
TreeMap<String, String> headers = new TreeMap<String, String>();
headers.put("Authorization", authorization);
headers.put("Host", host);
headers.put("Content-Type", CT_X_WWW_FORM_URLENCODED);
headers.put("X-TC-Action", action);
headers.put("X-TC-Timestamp", timestamp);
headers.put("X-TC-Version", version);
headers.put("X-TC-Region", region);
}
}
Python
# -*- coding: utf-8 -*-
import hashlib, hmac, json, os, sys, time
from datetime import datetime
# 密钥参数
secret_id = "AKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE"
secret_key = "Gu5t9xGARNpq86cd98joQYCN3EXAMPLE"
service = "cvm"
host = "cvm.fincloud.tencent.cn"
endpoint = "https://" + host
region = "ap-guangzhou"
action = "DescribeInstances"
version = "2017-03-12"
algorithm = "TC3-HMAC-SHA256"
timestamp = 1539084154
date = datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d")
params = {"Limit": 10, "Offset": 0}
# ************* 步骤 1:拼接规范请求串 *************
http_request_method = "GET"
canonical_uri = "/"
canonical_querystring = "Limit=10&Offset=0"
ct = "x-www-form-urlencoded"
payload = ""
if http_request_method == "POST":
canonical_querystring = ""
ct = "json"
payload = json.dumps(params)
canonical_headers = "content-type:application/%s\nhost:%s\n" % (ct, host)
signed_headers = "content-type;host"
hashed_request_payload = hashlib.sha256(payload.encode("utf-8")).hexdigest()
canonical_request = (http_request_method + "\n" +
canonical_uri + "\n" +
canonical_querystring + "\n" +
canonical_headers + "\n" +
signed_headers + "\n" +
hashed_request_payload)
print(canonical_request)
# ************* 步骤 2:拼接待签名字符串 *************
credential_scope = date + "/" + service + "/" + "tc3_request"
hashed_canonical_request = hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()
string_to_sign = (algorithm + "\n" +
str(timestamp) + "\n" +
credential_scope + "\n" +
hashed_canonical_request)
print(string_to_sign)
# ************* 步骤 3:计算签名 *************
# 计算签名摘要函数
def sign(key, msg):
return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
secret_date = sign(("TC3" + secret_key).encode("utf-8"), date)
secret_service = sign(secret_date, service)
secret_signing = sign(secret_service, "tc3_request")
signature = hmac.new(secret_signing, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
print(signature)
# ************* 步骤 4:拼接 Authorization *************
authorization = (algorithm + " " +
"Credential=" + secret_id + "/" + credential_scope + ", " +
"SignedHeaders=" + signed_headers + ", " +
"Signature=" + signature)
print(authorization)
# 公共参数添加到请求头部
headers = {
"Authorization": authorization,
"Host": host,
"Content-Type": "application/%s" % ct,
"X-TC-Action": action,
"X-TC-Timestamp": str(timestamp),
"X-TC-Version": version,
"X-TC-Region": region,
}