Python 和 PHP 对腾讯云签名 hmac_sha256 算法实现

开宗明义,米扑科技在使用腾讯云的API接口签名中,按照官方示例开发PHP、Python的接口,经常会提示签名错误

1
2
3
4
5
6
7
8
9
{
   "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 签名示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/**
  * 签名并获取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( "

+++++ action : %s

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 );

 

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 签名示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
# mimvp.com
# 2018-01-08
 
 
import time, datetime, os, json
import urllib, urllib2
import hashlib, base64, hmac, random
import logging
import logging.handlers
 
import sys
reload (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 "

+++++ action : %s

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)

 

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 签名

1
2
3
4
5
6
7
// 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 签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import urllib, base54, hashlib, hmac
 
# sha1
hmac_sha1_str = base64.b64encode( hmac.new(secret_access_key, data, digestmod = hashlib.sha1).digest() )
signature = urllib.quote(hmac_sha1_str)
 
# sha256
hmac_sha256_str = base64.b64encode( hmac.new(secret_access_key, data, digestmod = hashlib.sha256).digest() )
signature = urllib.quote(hmac_sha256_str)
 
 
# sha256
hmac_sha256_str = base64.b64encode( hmac.new(secret_access_key, data, digestmod = hashlib.sha256).hexdigest() )   # 16进制,错误
signature = urllib.quote(hmac_sha256_str)

 


你可能感兴趣的:(Algorithm,Linux/Shell,NetWork)