开宗明义,米扑科技在使用腾讯云的API接口签名中,按照官方示例开发PHP、Python的接口,经常会提示签名错误
123456789 | { "Response": { "Error": { "Code": "InvalidParameter.SignatureFailure", "Message": "The provided credentials could not be validated. Please check your signature is correct." }, "RequestId": "1ee6ae98-a971-ad9f-4ecc-abcd69ea1234" }} |
---|
本文原文,请参见米扑博客:
Python 和 PHP 对腾讯云签名 hmac_sha256 算法实现
经过多次尝试探究,发现原因有二:
1)腾讯云官方示例不严谨,没有urlencode() 或 urllib.quote() 编码导致提示签名错误
2)腾讯官方只提供了PHP示例,没有提供Python示例,两者签名函数有一些细节
直接给出干货,下面示例是 米扑科技 封装好的腾讯云签名函数,以飨读者。
腾讯云签名:https://cloud.tencent.com/document/api/377/4214
阿里云签名:https://help.aliyun.com/document_detail/35735.html
米扑的官网:https://mimvp.com
PHP 签名示例
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081 | /** * 签名并获取URL结果,json格式返回 * * 1. 查询弹性IP列表, DescribeAddresses * 2. 解绑弹性IP, DisassociateAddress * 3. 释放弹性IP, ReleaseAddresses * 4. 公网IP转弹性IP, TransformAddress * * @param string $req_action : DescribeAddresses, DisassociateAddress, ReleaseAddresses, TransformAddress * @param string $params : 以 & 开头, 如 &xxx=yyy */function qcloud_eip_sign($req_action='DescribeAddresses', $req_region='ap-beijing', $req_extra_params='', $retry_NUM=3) { global $QCloud_SecretId; global $QCloud_SecretKey; // $req_action='DescribeAddresses'// $req_region = 'ap-beijing'; // ap-guangzhou $req_method = 'GET'; // GET POST $req_api = 'eip.api.qcloud.com/v2/index.php'; $req_version = '2017-03-12'; $req_timestamp = strtotime(date('YmdHis')); // 1402992826 $req_nonce = rand(1000, 1000000); // 随机正整数 $req_secretid = $QCloud_SecretId; // 密钥ID,用作参数 $req_secretkey = $QCloud_SecretKey; // 密钥key,用作加密 $req_signature_method = 'HmacSHA256'; // HmacSHA1(默认), HmacSHA256 $req_signature = ''; // $req_uri = "https://eip.api.qcloud.com/v2/index.php?Action=DescribeAddresses// &Version=2017-03-12// &AddressIds.1=eip-hxlqja90// &Region=ap-beijing// &Timestamp=1402992826// &Nonce=345122// &Signature=pStJagaKsV2QdkJnBQWYBDByZ9YPBsOi// &SecretId=AKIDpY8cxBD2GLGK9sT0LaqIczGLFxTsoDF6 // 请求方法 + 请求主机 +请求路径 + ? + 请求字符串 $req_params = sprintf("Action=%s&Region=%s&Version=%s&Timestamp=%s&Nonce=%s&SecretId=%s&SignatureMethod=%s%s", $req_action, $req_region, $req_version, $req_timestamp, $req_nonce, $req_secretid, $req_signature_method, $req_extra_params); $req_params_array = explode("&", $req_params); sort($req_params_array); // 以value排序,value值为 Action=DescribeAddresses 、 Region=ap-beijing $req_params2 = implode("&", $req_params_array); $req_uri = sprintf("%s%s?%s", $req_method, $req_api, $req_params2); $req_signature = urlencode(base64_encode(hash_hmac('sha256', $req_uri, $req_secretkey, true))); // urlencode(xxx) $req_url = sprintf("https://%s?%s&Signature=%s", $req_api, $req_params2, $req_signature); $res = curl_url($req_url); $retry_idx = 0; while(empty($res) && $retry_idx < $retry_NUM) { $retry_idx += 1; $res = curl_url($req_url); } if(!empty($res)) { $resJson = json_decode($res, true); $resJson = $resJson['Response']; echo sprintf("<br><br> +++++ action : %s <br><br> resJson: ", $req_action); print_r($resJson); return $resJson; } else { return null; }} $req_action_query = 'DescribeAddresses'; // 查询弹性IP$req_action_unbind = 'DisassociateAddress'; // 解绑弹性IP$req_action_release = 'ReleaseAddresses'; // 释放弹性IP$req_action_transform = 'TransformAddress'; // 公网IP转弹性IP $req_region = 'ap-guangzhou';$req_extra_params = ''; // 1. 查询弹性IP列表$resJson = qcloud_eip_sign($req_action_query, $req_region);var_dump($resJson); |
---|
运行结果:
1234567891011121314151617181920 | req_url: https://eip.api.qcloud.com/v2/index.php?Action=DescribeAddresses&Nonce=585269&Region=ap-guangzhou&SecretId=AKIDSmAAAA2DABCDpTkBBBBMLMFwY0HM1234&SignatureMethod=HmacSHA256&Timestamp=1520429723&Version=2017-03-12&Signature=8U6i3BKBWYWoit3t1egIE9ZC%2BdWtI46QuHLc%2FbhaWWg%3D array (size=3) 'TotalCount' => int 1 'AddressSet' => array (size=1) 0 => array (size=11) 'AddressId' => string 'eip-qy123abc' (length=12) 'AddressName' => null 'AddressIp' => string '111.230.123.234' (length=15) 'AddressStatus' => string 'BIND' (length=4) 'InstanceId' => string 'ins-fabc1234' (length=12) 'NetworkInterfaceId' => null 'PrivateAddressIp' => string '10.104.245.26' (length=14) 'IsArrears' => boolean false 'IsBlocked' => boolean false 'IsEipDirectConnection' => boolean false 'CreatedTime' => string '2018-03-07T12:46:26Z' (length=20) 'RequestId' => string 'ad28067e-d1f9-4c47-932e-6bba1d123456' (length=36) |
---|
代码说明:
1)函数抽象封装签名方法,方便管理维护,减少开发工作量
2)参数按照升序排列 explode(xxx) —> sort($req_params_array) —> implode(xxx)
3)签名方法,需要添加 urlencode,否则经常提示签名错误,原因是未urlencode会有一些 空格、加号(+)、等号(=)等特殊字符
$req_signature = urlencode(base64_encode(hash_hmac('sha256', $req_uri, $req_secretkey, true))); // urlencode(xxx)
Python 签名示例
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879 | #!/usr/bin/env python# -*- coding:utf-8 -*-## mimvp.com# 2018-01-08 import time, datetime, os, jsonimport urllib, urllib2import hashlib, base64, hmac, randomimport loggingimport logging.handlers import sysreload(sys)sys.setdefaultencoding('utf-8') ## 腾讯云API接口签名def qcloud_eip_sign(req_action='DescribeAddresses', req_region='ap-beijing', req_extra_params='', retry_NUM=3): req_method = 'GET' # GET POST req_api = 'eip.api.qcloud.com/v2/index.php' req_version = '2017-03-12' req_timestamp = int(time.time()) # 1520422452 req_nonce = random.randint(1000, 1000000) # 随机正整数 req_secretid = QCLOUD_SecretId # 密钥ID,用作参数 req_secretkey = QCLOUD_SecretKey # 密钥key,用作加密 req_signature_method = 'HmacSHA256' # HmacSHA1(默认), HmacSHA256 req_signature = '' # req_uri = "https://eip.api.qcloud.com/v2/index.php?Action=DescribeAddresses# &Version=2017-03-12# &AddressIds.1=eip-hxlqja90# &Region=ap-beijing# &Timestamp=1402992826# &Nonce=345122# &Signature=pStJagaKsV2QdkJnBQWYBDByZ9YPBsOi# &SecretId=AKIDpY8cxBD2GLGK9sT0LaqIczGLFxTsoDF6 # 请求方法 + 请求主机 +请求路径 + ? + 请求字符串 req_params = "Action=%s&Region=%s&Version=%s&Timestamp=%s&Nonce=%s&SecretId=%s&SignatureMethod=%s%s" % (req_action, req_region, req_version, req_timestamp, req_nonce, req_secretid, req_signature_method, req_extra_params) req_params_array = req_params.split('&') req_params_array = sorted(req_params_array) # 以value排序,value值为 Action=DescribeAddresses 、 Region=ap-beijing req_params2 = '&'.join(req_params_array); req_uri = "%s%s?%s" % (req_method, req_api, req_params2) req_signature = urllib.quote( base64.b64encode(hmac.new(req_secretkey, req_uri, digestmod=hashlib.sha256).digest()) ) # urllib.quote(xxx) req_url = "https://%s?%s&Signature=%s" % (req_api, req_params2, req_signature) logger.info('qcloud_eip_sign() - req_url: %s' % (req_url)) res = spider_url(req_url) retry_idx = 0; while not res and retry_idx < retry_NUM: retry_idx += 1 res = spider_url(req_url) if res : resJson = json.loads(res) resJson = resJson['Response'] print "<br><br> +++++ action : %s <br><br> resJson: " % (req_action,) return resJson else: return None; if __name__ == "__main__": req_action_query = 'DescribeAddresses' # 查询弹性IP req_action_unbind = 'DisassociateAddress' # 解绑弹性IP req_action_release = 'ReleaseAddresses' # 释放弹性IP req_action_transform = 'TransformAddress' # 公网IP转弹性IP req_region='ap-guangzhou' req_extra_params = ''; # 1. 查询弹性IP列表 resJson = qcloud_eip_sign(req_action_query, req_region) print json.dumps(resJson) |
---|
运行结果:
1234567891011121314151617181920212223 | req_url: https://eip.api.qcloud.com/v2/index.php?Action=DescribeAddresses&Nonce=383782&Region=ap-guangzhou&SecretId=AKIDSmAAAA2DABCDpTkBBBBMLMFwY0HM1234&SignatureMethod=HmacSHA256&Timestamp=1520430569&Version=2017-03-12&Signature=Tsgwx2GV/%2BopDlHiMUg3H/rpIbQ5jPfe9tW3w9Slom4%3D { "Response": { "TotalCount": 1, "AddressSet": [ { "AddressId": "eip-qy123abc", "AddressName": null, "AddressIp": "111.230.123.234", "AddressStatus": "BIND", "InstanceId": "ins-fabc1234", "NetworkInterfaceId": null, "PrivateAddressIp": "10.104.245.26", "IsArrears": false, "IsBlocked": false, "IsEipDirectConnection": false, "CreatedTime": "2018-03-07T12:46:26Z" } ], "RequestId": "c2ab3f7f-9796-4ade-afb1-6bba1d123456" }} |
---|
代码说明:
1)Python改写PHP代码,有一些细节,如 int(time.time())、random.randint(1000, 1000000)、sorted(req_params_array)等
2)参数按照升序排列 xxx.split('&') —> sort($req_params_array) —> '&'.join(xxx)
3)签名方法,需要添加 urllib.quote、base64.b64encode(xxx)、digest() 等,否则经常提示签名错误
req_signature = urllib.quote( base64.b64encode(hmac.new(req_secretkey, req_uri, digestmod=hashlib.sha256).digest()) ) # urllib.quote(xxx)
Python 代码里,特别要注意 hmac 签名 sha256 后获取的是 digest(),而不是 hexdigest() 这里错了会一直提示签名错误!
总结之PHP和Python的对应关系
1) PHP 签名
1234567 | // sha1$hmac_sha1_str = base64_encode(hash_hmac("sha1", $data, $secret_access_key)); // HMAC-SHA1加密$signature = urlencode($hmac_sha1_str); // 编码URL // sha256$hmac_sha256_str = base64_encode(hash_hmac("sha256", $data, $secret_access_key)); // HMAC-SHA256加密$signature = urlencode($hmac_sha256_str); // 编码URL |
---|
2)Python 签名
1234567891011121314 | import urllib, base54, hashlib, hmac # sha1hmac_sha1_str = base64.b64encode( hmac.new(secret_access_key, data, digestmod=hashlib.sha1).digest() )signature = urllib.quote(hmac_sha1_str) # sha256hmac_sha256_str = base64.b64encode( hmac.new(secret_access_key, data, digestmod=hashlib.sha256).digest() )signature = urllib.quote(hmac_sha256_str) # sha256hmac_sha256_str = base64.b64encode( hmac.new(secret_access_key, data, digestmod=hashlib.sha256).hexdigest() ) # 16进制,错误signature = urllib.quote(hmac_sha256_str) |
---|