Signature v3

Last updated: 2019-08-18 22:54:59

Tencent Cloud API authenticates each access request, i.e. each request needs to include signature information (Signature) in the common request parameters to verify the identity of the requester. The Signature is generated by the security credentials which include SecretId and SecretKey. If you don't have the security credentials yet, please go to the Cloud API Key page to apply; otherwise, you cannot call the Cloud API.

Applying for Security Credentials

Before using the Cloud API for the first time, go to the Cloud API Key page to apply for security credentials. Security credentials include SecretId and SecretKey:

  • SecretId is used to identify the API caller.
  • SecretKey is used to encrypt the signature string and verify it on the server.
  • You must keep your security credentials private and avoid disclosure.

You can apply for the security credentials in the following steps:

  1. Log in to Tencent Cloud Management Center Console.
  2. Go to the Cloud API Key page
  3. On the Cloud API Key page, click New to create a pair of SecretId/SecretKey

Note: A developer account can have up to two pairs of SecretId/SecretKey.

TC3-HMAC-SHA256 Signature Method

Note: For the GET method, only the protocol format of Content-Type: application/x-www-form-urlencoded is supported. For the POST method, two protocol formats of Content-Type: application/json and Content-Type: multipart/form-data are supported. The JSON format is supported by all business APIs by default. The multipart format is supported only by certain business APIs (in this case, the APIs cannot be called using the JSON format. For details, see the specific business API documentation).

The following uses querying the list of CVM instances in the Guangzhou region as an example to describe the steps of signature splicing. Only two parameters of the instance list querying API are used: Limit and Offset, which are called using the GET method.

Assume that the SecretId and SecretKey are: AKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE and Gu5t9xGARNpq86cd98joQYCN3EXAMPLE

1. Splicing the CanonicalRequest String

The CanonicalRequest string is spliced in the following format:

CanonicalRequest =
    HTTPRequestMethod + '\n' +
    CanonicalURI + '\n' +
    CanonicalQueryString + '\n' +
    CanonicalHeaders + '\n' +
    SignedHeaders + '\n' +
    HashedRequestPayload
  • HTTPRequestMethod: HTTP request method (GET or POST); GET is used in this example;
  • CanonicalURI: URI parameter, which is always a forward slash (/) in API 3.0;
  • CanonicalQueryString: The query string in the URL that initiates the HTTP request. For a POST request, it is fixed as an empty string; for a GET request, it is the string content after the question mark (?) in the URL. The value in this example is: Limit=10&Offset=0. Note: CanonicalQueryString needs to be URL encoded.
  • CanonicalHeaders: Information of the headers involving in the signature, including at least two headers of host and content-type. Custom headers can be added to participate in the signature process to improve the uniqueness and security of the request. Splicing rules: 1) The header keys and values should be uniformly converted to lowercase with the leading and trailing spaces removed, so they are spliced in the format of key:value\n format; 2) if there are multiple headers, they should be sorted in the lexicographical order of the header keys (lowercase). In this example: content-type:application/x-www-form-urlencoded\nhost:cvm.tencentcloudapi.com\n
  • SignedHeaders: Information of the headers involving in the signature, indicating which headers of the request participate in the signature process (they must correspond to the headers in CanonicalHeaders one-to-one). content-type and host are required headers. Splicing rules: 1) The header keys should be uniformly converted to lowercase; 2) if there are multiple headers, they should be sorted in the lexicographical order of the header keys (lowercase) and separated by semicolons (;). In this example: content-type;host
  • HashedRequestPayload: Hash value of the request body, calculated as Lowercase(HexEncode(Hash.SHA256(RequestPayload))) by SHA256 hashing the entire body payload of the HTTP request, performing hexadecimal encoding and finally converting the encoded string to lowercase letters. Note: For a GET request, RequestPayload is fixed to an empty string; for a POST request, RequestPayload is the body payload of the HTTP request.

According to the rules above, the CanonicalRequest string obtained in the example is as follows (for a clear display, the \n line break is replaced by adding a new line):

GET
/
Limit=10&Offset=0
content-type:application/x-www-form-urlencoded
host:cvm.api.tencentyun.com

content-type;host
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

2. Splicing the String to Be Signed

The string to be signed is spliced in the following format:

StringToSign =
    Algorithm + \n +
    RequestTimestamp + \n +
    CredentialScope + \n +
    HashedCanonicalRequest
  • Algorithm: Signature algorithm, currently fixed as TC3-HMAC-SHA256;
  • RequestTimestamp: Timestamp of the request, i.e., the value of the X-TC-Timestamp in the request header; 1539084154 as shown in the example above;
  • CredentialScope: Scope of the credential in the format of Date/service/tc3_request, including the date, requested service and termination string (tc3_request). Date is a date in UTC time, whose value should match the UTC date converted by the common parameter X-TC-Timestamp; service is the product name, which should match the domain name of the product called, such as cvm. As shown in the example request above, the value is 2018-10-09/cvm/tc3_request;
  • HashedCanonicalRequest: Hash value of the CanonicalRequest string spliced in the steps above, calculated as Lowercase(HexEncode(Hash.SHA256(CanonicalRequest))).

Note:

  1. Date has to be calculated from the timestamp "X-TC-Timestamp" and the time zone is UTC+0. If you add the system's local time zone information (such as UTC+8), calls can succeed in the daytime and night but will definitely fail at 00:00. For example, if the timestamp is 1551113065 and the time in UTC+8 is 2019-02-26 00:44:25, the UTC+0 date in the calculated Date value should be 2019-02-25 instead of 2019-02-26.
  2. Timestamp must be the current system time, and it should be ensured that the system time and standard time are synced; if the difference is over five minutes, the call will definitely fail. If the time difference exists for a long time, it may cause the requests to definitely fail after running for a period of time (with a signature expiration error returned).

According to the rules above, the string to be signed obtained in the example is as follows (for a clear display, the \n line break is replaced by adding a new line):

TC3-HMAC-SHA256
1539084154
2018-10-09/cvm/tc3_request
91c9c192c14460df6c1ffc69e34e6c5e90708de2a6d282cccf957dbf1aa7f3a7

3. Calculating the Signature

1) Calculate the derived signature key with the following pseudocode:

SecretKey = "Gu5t9xGARNpq86cd98joQYCN3EXAMPLE"
SecretDate = HMAC_SHA256("TC3" + SecretKey, Date)
SecretService = HMAC_SHA256(SecretDate, Service)
SecretSigning = HMAC_SHA256(SecretService, "tc3_request")
  • SecretKey: The original SecretKey;
  • Date: The Date field information in Credential; 2018-10-09 as shown in the example above;
  • Service: The Service field information in Credential; cvm as shown in the example above;

2) Calculate the signature with the following pseudocode:

Signature = HexEncode(HMAC_SHA256(SecretSigning, StringToSign))
  • SecretSigning: The derived signature key calculated above;
  • StringToSign: The string to be signed calculated in step 2;

4. Splicing the Authorization

The Authorization is spliced in the following format:

Authorization =
    Algorithm + ' ' +
    'Credential=' + SecretId + '/' + CredentialScope + ', ' +
    'SignedHeaders=' + SignedHeaders + ', '
    'Signature=' + Signature
  • Algorithm: Signature algorithm, fixed as TC3-HMAC-SHA256;
  • SecretId: The SecretId in the key pair;
  • CredentialScope: Scope of the credential (see above);
  • SignedHeaders: Information of the headers involving in the signature (see above);
  • Signature: Signature value;

According to the rules above, the value obtained in the example is:

TC3-HMAC-SHA256 Credential=AKIDEXAMPLE/Date/service/tc3_request, SignedHeaders=content-type;host, Signature=5da7a33f6993f0614b047e5df4582db9e9bf4672ba50567dba16c6ccf174c474

The final complete call information is as follows:

https://cvm.tencentcloudapi.com/?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.tencentcloudapi.com
X-TC-Action: DescribeInstances
X-TC-Version: 2017-03-12
X-TC-Timestamp: 1539084154
X-TC-Region: ap-guangzhou

5. Signature Demo

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 TencentCloudAPITC3Demo {
    private final static String CHARSET = "UTF-8";
    private final static String ENDPOINT = "cvm.tencentcloudapi.com";
    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.tencentcloudapi.com";
        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");
        // Pay attention to the time zone; otherwise, errors may occur
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
        String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));

        // ************* Step 1: Splice the CanonicalRequest string *************
        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);

        // ************* Step 2: Splice the string to be signed *************
        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);

        // ************* Step 3: Calculate the signature *************
        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);

        // ************* Step 4: Splice the 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

# Key parameters
secret_id = "AKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE"
secret_key = "Gu5t9xGARNpq86cd98joQYCN3EXAMPLE"

service = "cvm"
host = "cvm.tencentcloudapi.com"
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}

# ************* Step 1: Splice the CanonicalRequest string *************
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)

# ************* Step 2: Splice the string to be signed *************
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)

# ************* Step 3: Calculate the signature *************
# Calculate the signature summary function
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)

# ************* Step 4: Splice the Authorization *************
authorization = (algorithm + " " +
                 "Credential=" + secret_id + "/" + credential_scope + ", " +
                 "SignedHeaders=" + signed_headers + ", " +
                 "Signature=" + signature)
print(authorization)

# Add the common parameters to the request header
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,
}

Signature Failure

The following error codes for signature failure exist based on the actual conditions. Please cope with the errors accordingly.

Error code Error description
AuthFailure.SignatureExpire Signature expired
AuthFailure.SecretIdNotFound Key does not exist
AuthFailure.SignatureFailure Signature error
AuthFailure.TokenFailure Token error
AuthFailure.InvalidSecretId Invalid key (not Cloud API key type)