签名方法

最后更新时间:2020-05-21 14:50:03

    签名方法 v1 简单易用,但是功能和安全性都不如签名方法 v3,推荐使用签名方法 v3。

    首次接触,建议使用 API Explorer 中的“签名串生成”功能,选择签名版本为“API 3.0 签名 v1”,可以生成签名过程进行验证,并提供了部分编程语言的签名示例,也可直接生成 SDK 代码。推荐使用腾讯云 API 配套的 7 种常见的编程语言 SDK,已经封装了签名和请求过程,均已开源,支持 PythonJavaPHPGoNodeJS.NETC++

    腾讯云 API 会对每个访问请求进行身份验证,即每个请求都需要在公共请求参数中包含签名信息(Signature)以验证请求者身份。
    签名信息由安全凭证生成,安全凭证包括 SecretId 和 SecretKey;若用户还没有安全凭证,请前往 云API密钥页面 申请,否则无法调用云 API 接口。

    1. 申请安全凭证

    在第一次使用云 API 之前,请前往 云 API 密钥页面 申请安全凭证。
    安全凭证包括 SecretId 和 SecretKey:

    • SecretId 用于标识 API 调用者身份
    • SecretKey 用于加密签名字符串和服务器端验证签名字符串的密钥。
    • 用户必须严格保管安全凭证,避免泄露。

    申请安全凭证的具体步骤如下:

    1. 登录 腾讯云管理中心控制台
    2. 前往 云 API 密钥 的控制台页面
    3. 云 API 密钥 页面,单击【新建密钥】即可以创建一对 SecretId/SecretKey。

    注意:每个账号最多可以拥有两对 SecretId/SecretKey。

    2. 生成签名串

    有了安全凭证SecretId 和 SecretKey后,就可以生成签名串了。以下是生成签名串的详细过程:

    假设用户的 SecretId 和 SecretKey 分别是:

    • SecretId: AKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE
    • SecretKey: Gu5t9xGARNpq86cd98joQYCN3EXAMPLE

    注意:这里只是示例,请根据用户实际申请的 SecretId 和 SecretKey 进行后续操作!

    以云服务器查看实例列表(DescribeInstances)请求为例,当用户调用这一接口时,其请求参数可能如下:

    参数名称 中文 参数值
    Action 方法名 DescribeInstances
    SecretId 密钥 ID AKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE
    Timestamp 当前时间戳 1465185768
    Nonce 随机正整数 11886
    Region 实例所在区域 ap-guangzhou
    InstanceIds.0 待查询的实例 ID ins-09dx96dg
    Offset 偏移量 0
    Limit 最大允许输出 20
    Version 接口版本号 2017-03-12

    2.1. 对参数排序

    首先对所有请求参数按参数名的字典序( ASCII 码)升序排序。注意:1)只按参数名进行排序,参数值保持对应即可,不参与比大小;2)按 ASCII 码比大小,如 InstanceIds.2 要排在 InstanceIds.12 后面,不是按字母表,也不是按数值。用户可以借助编程语言中的相关排序函数来实现这一功能,如 PHP 中的 ksort 函数。上述示例参数的排序结果如下:

    {
        'Action' : 'DescribeInstances',
        'InstanceIds.0' : 'ins-09dx96dg',
        'Limit' : 20,
        'Nonce' : 11886,
        'Offset' : 0,
        'Region' : 'ap-guangzhou',
        'SecretId' : 'AKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE',
        'Timestamp' : 1465185768,
        'Version': '2017-03-12',
    }

    使用其它程序设计语言开发时,可对上面示例中的参数进行排序,得到的结果一致即可。

    2.2. 拼接请求字符串

    此步骤生成请求字符串。
    将把上一步排序好的请求参数格式化成“参数名称=参数值”的形式,如对 Action 参数,其参数名称为 "Action" ,参数值为 "DescribeInstances" ,因此格式化后就为 Action=DescribeInstances 。
    注意:“参数值”为原始值而非 url 编码后的值。

    然后将格式化后的各个参数用"&"拼接在一起,最终生成的请求字符串为:

    Action=DescribeInstances&InstanceIds.0=ins-09dx96dg&Limit=20&Nonce=11886&Offset=0&Region=ap-guangzhou&SecretId=AKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE&Timestamp=1465185768&Version=2017-03-12

    2.3. 拼接签名原文字符串

    此步骤生成签名原文字符串。
    签名原文字符串由以下几个参数构成:

    1. 请求方法: 支持 POST 和 GET 方式,这里使用 GET 请求,注意方法为全大写。
    2. 请求主机:查看实例列表(DescribeInstances)的请求域名为:cvm.tencentcloudapi.com。实际的请求域名根据接口所属模块的不同而不同,详见各接口说明。
    3. 请求路径: 当前版本云API的请求路径固定为 / 。
    4. 请求字符串: 即上一步生成的请求字符串。

    签名原文串的拼接规则为:请求方法 + 请求主机 +请求路径 + ? + 请求字符串

    示例的拼接结果为:

    GETcvm.tencentcloudapi.com/?Action=DescribeInstances&InstanceIds.0=ins-09dx96dg&Limit=20&Nonce=11886&Offset=0&Region=ap-guangzhou&SecretId=AKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE&Timestamp=1465185768&Version=2017-03-12

    2.4. 生成签名串

    此步骤生成签名串。
    首先使用 HMAC-SHA1 算法对上一步中获得的签名原文字符串进行签名,然后将生成的签名串使用 Base64 进行编码,即可获得最终的签名串。

    具体代码如下,以 PHP 语言为例:

    $secretKey = 'Gu5t9xGARNpq86cd98joQYCN3EXAMPLE';
    $srcStr = 'GETcvm.tencentcloudapi.com/?Action=DescribeInstances&InstanceIds.0=ins-09dx96dg&Limit=20&Nonce=11886&Offset=0&Region=ap-guangzhou&SecretId=AKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE&Timestamp=1465185768&Version=2017-03-12';
    $signStr = base64_encode(hash_hmac('sha1', $srcStr, $secretKey, true));
    echo $signStr;

    最终得到的签名串为:

    EliP9YW3pW28FpsEdkXt/+WcGeI=

    使用其它程序设计语言开发时,可用上面示例中的原文进行签名验证,得到的签名串与例子中的一致即可。

    3. 签名串编码

    生成的签名串并不能直接作为请求参数,需要对其进行 URL 编码。

    如上一步生成的签名串为 EliP9YW3pW28FpsEdkXt/+WcGeI= ,最终得到的签名串请求参数(Signature)为:EliP9YW3pW28FpsEdkXt%2f%2bWcGeI%3d,它将用于生成最终的请求 URL。

    注意:如果用户的请求方法是 GET,或者请求方法为 POST 同时 Content-Type 为 application/x-www-form-urlencoded,则发送请求时所有请求参数的值均需要做 URL 编码,参数键和=符号不需要编码。非 ASCII 字符在 URL 编码前需要先以 UTF-8 进行编码。

    注意:有些编程语言的网络库会自动为所有参数进行 urlencode,在这种情况下,就不需要对签名串进行 URL 编码了,否则两次 URL 编码会导致签名失败。

    注意:其他参数值也需要进行编码,编码采用 RFC 3986。使用 %XY 对特殊字符例如汉字进行百分比编码,其中“X”和“Y”为十六进制字符(0-9 和大写字母 A-F),使用小写将引发错误。

    4. 签名失败

    根据实际情况,存在以下签名失败的错误码,请根据实际情况处理。

    错误代码 错误描述
    AuthFailure.SignatureExpire 签名过期
    AuthFailure.SecretIdNotFound 密钥不存在
    AuthFailure.SignatureFailure 签名错误
    AuthFailure.TokenFailure token 错误
    AuthFailure.InvalidSecretId 密钥非法(不是云 API 密钥类型)

    5. 签名演示

    在实际调用 API 3.0 时,推荐使用配套的腾讯云 SDK 3.0 ,SDK 封装了签名的过程,开发时只关注产品提供的具体接口即可。详细信息参见 SDK 中心。当前支持的编程语言有:

    为了更清楚的解释签名过程,下面以实际编程语言为例,将上述的签名过程具体实现。请求的域名、调用的接口和参数的取值都以上述签名过程为准,代码只为解释签名过程,并不具备通用性,实际开发请尽量使用 SDK 。

    最终输出的 url 可能为:https://cvm.tencentcloudapi.com/?Action=DescribeInstances&InstanceIds.0=ins-09dx96dg&Limit=20&Nonce=11886&Offset=0&Region=ap-guangzhou&SecretId=AKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE&Signature=EliP9YW3pW28FpsEdkXt%2F%2BWcGeI%3D&Timestamp=1465185768&Version=2017-03-12

    注意:由于示例中的密钥是虚构的,时间戳也不是系统当前时间,因此如果将此 url 在浏览器中打开或者用 curl 等命令调用时会返回鉴权错误:签名过期。为了得到一个可以正常返回的 url ,需要修改示例中的 SecretId 和 SecretKey 为真实的密钥,并使用系统当前时间戳作为 Timestamp 。

    注意:在下面的示例中,不同编程语言,甚至同一语言每次执行得到的 url 可能都有所不同,表现为参数的顺序不同,但这并不影响正确性。只要所有参数都在,且签名计算正确即可。

    注意:以下代码仅适用于 API 3.0,不能直接用于其他的签名流程,即使是旧版的 API ,由于存在细节差异也会导致签名计算错误,请以对应的实际文档为准。

    Java

    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    import java.util.Random;
    import java.util.TreeMap;
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import javax.xml.bind.DatatypeConverter;
    
    public class TencentCloudAPIDemo {
        private final static String CHARSET = "UTF-8";
    
        public static String sign(String s, String key, String method) throws Exception {
            Mac mac = Mac.getInstance(method);
            SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(CHARSET), mac.getAlgorithm());
            mac.init(secretKeySpec);
            byte[] hash = mac.doFinal(s.getBytes(CHARSET));
            return DatatypeConverter.printBase64Binary(hash);
        }
    
        public static String getStringToSign(TreeMap<String, Object> params) {
            StringBuilder s2s = new StringBuilder("GETcvm.tencentcloudapi.com/?");
            // 签名时要求对参数进行字典排序,此处用TreeMap保证顺序
            for (String k : params.keySet()) {
                s2s.append(k).append("=").append(params.get(k).toString()).append("&");
            }
            return s2s.toString().substring(0, s2s.length() - 1);
        }
    
        public static String getUrl(TreeMap<String, Object> params) throws UnsupportedEncodingException {
            StringBuilder url = new StringBuilder("https://cvm.tencentcloudapi.com/?");
            // 实际请求的url中对参数顺序没有要求
            for (String k : params.keySet()) {
                // 需要对请求串进行urlencode,由于key都是英文字母,故此处仅对其value进行urlencode
                url.append(k).append("=").append(URLEncoder.encode(params.get(k).toString(), CHARSET)).append("&");
            }
            return url.toString().substring(0, url.length() - 1);
        }
    
        public static void main(String[] args) throws Exception {
            TreeMap<String, Object> params = new TreeMap<String, Object>(); // TreeMap可以自动排序
            // 实际调用时应当使用随机数,例如:params.put("Nonce", new Random().nextInt(java.lang.Integer.MAX_VALUE));
            params.put("Nonce", 11886); // 公共参数
            // 实际调用时应当使用系统当前时间,例如:   params.put("Timestamp", System.currentTimeMillis() / 1000);
            params.put("Timestamp", 1465185768); // 公共参数
            params.put("SecretId", "AKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE"); // 公共参数
            params.put("Action", "DescribeInstances"); // 公共参数
            params.put("Version", "2017-03-12"); // 公共参数
            params.put("Region", "ap-guangzhou"); // 公共参数
            params.put("Limit", 20); // 业务参数
            params.put("Offset", 0); // 业务参数
            params.put("InstanceIds.0", "ins-09dx96dg"); // 业务参数
            params.put("Signature", sign(getStringToSign(params), "Gu5t9xGARNpq86cd98joQYCN3EXAMPLE", "HmacSHA1")); // 公共参数
            System.out.println(getUrl(params));
        }
    }

    Python

    注意:如果是在 Python 2 环境中运行,需要先安装 requests 依赖包: pip install requests

    # -*- coding: utf8 -*-
    import base64
    import hashlib
    import hmac
    import time
    
    import requests
    
    secret_id = "AKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE"
    secret_key = "Gu5t9xGARNpq86cd98joQYCN3EXAMPLE"
    
    def get_string_to_sign(method, endpoint, params):
        s = method + endpoint + "/?"
        query_str = "&".join("%s=%s" % (k, params[k]) for k in sorted(params))
        return s + query_str
    
    def sign_str(key, s, method):
        hmac_str = hmac.new(key.encode("utf8"), s.encode("utf8"), method).digest()
        return base64.b64encode(hmac_str)
    
    if __name__ == '__main__':
        endpoint = "cvm.tencentcloudapi.com"
        data = {
            'Action' : 'DescribeInstances',
            'InstanceIds.0' : 'ins-09dx96dg',
            'Limit' : 20,
            'Nonce' : 11886,
            'Offset' : 0,
            'Region' : 'ap-guangzhou',
            'SecretId' : secret_id,
            'Timestamp' : 1465185768, # int(time.time())
            'Version': '2017-03-12'
        }
        s = get_string_to_sign("GET", endpoint, data)
        data["Signature"] = sign_str(secret_key, s, hashlib.sha1)
        print(data["Signature"])
        # 此处会实际调用,成功后可能产生计费
        # resp = requests.get("https://" + endpoint, params=data)
        # print(resp.url)

    Go

    package main
    
    import (
        "bytes"
        "crypto/hmac"
        "crypto/sha1"
        "encoding/base64"
        "fmt"
        "sort"
    )
    
    func main() {
        secretId := "AKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE"
        secretKey := "Gu5t9xGARNpq86cd98joQYCN3EXAMPLE"
        params := map[string]string{
            "Nonce":         "11886",
            "Timestamp":     "1465185768",
            "Region":        "ap-guangzhou",
            "SecretId":      secretId,
            "Version":       "2017-03-12",
            "Action":        "DescribeInstances",
            "InstanceIds.0": "ins-09dx96dg",
            "Limit":         "20",
            "Offset":        "0",
        }
    
        var buf bytes.Buffer
        buf.WriteString("GET")
        buf.WriteString("cvm.tencentcloudapi.com")
        buf.WriteString("/")
        buf.WriteString("?")
    
        // sort keys by ascii asc order
        keys := make([]string, 0, len(params))
        for k, _ := range params {
            keys = append(keys, k)
        }
        sort.Strings(keys)
    
        for i := range keys {
            k := keys[i]
            buf.WriteString(k)
            buf.WriteString("=")
            buf.WriteString(params[k])
            buf.WriteString("&")
        }
        buf.Truncate(buf.Len() - 1)
    
        hashed := hmac.New(sha1.New, []byte(secretKey))
        hashed.Write(buf.Bytes())
    
        fmt.Println(base64.StdEncoding.EncodeToString(hashed.Sum(nil)))
    }

    PHP

    <?php
    $secretId = "AKIDz8krbsJ5yKBZQpn74WFkmLPx3EXAMPLE";
    $secretKey = "Gu5t9xGARNpq86cd98joQYCN3EXAMPLE";
    $param["Nonce"] = 11886;//rand();
    $param["Timestamp"] = 1465185768;//time();
    $param["Region"] = "ap-guangzhou";
    $param["SecretId"] = $secretId;
    $param["Version"] = "2017-03-12";
    $param["Action"] = "DescribeInstances";
    $param["InstanceIds.0"] = "ins-09dx96dg";
    $param["Limit"] = 20;
    $param["Offset"] = 0;
    
    ksort($param);
    
    $signStr = "GETcvm.tencentcloudapi.com/?";
    foreach ( $param as $key => $value ) {
        $signStr = $signStr . $key . "=" . $value . "&";
    }
    $signStr = substr($signStr, 0, -1);
    
    $signature = base64_encode(hash_hmac("sha1", $signStr, $secretKey, true));
    echo $signature.PHP_EOL;
    // need to install and enable curl extension in php.ini
    // $param["Signature"] = $signature;
    // $url = "https://cvm.tencentcloudapi.com/?".http_build_query($param);
    // echo $url.PHP_EOL;
    // $ch = curl_init();
    // curl_setopt($ch, CURLOPT_URL, $url);
    // $output = curl_exec($ch);
    // curl_close($ch);
    // echo json_decode($output);