Python 和 PHP 对腾讯云签名 hmac_sha256 算法实现
开宗明义,米扑科技在使用腾讯云的API接口签名中,按照官方示例开发PHP、Python的接口,经常会提示签名错误
{ "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" } }
经过多次尝试探究,发现原因有二:
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 签名示例
/** * 签名并获取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);
运行结果:
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 签名示例
#!/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 "<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)
运行结果:
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 签名
// 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 签名
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)
Python hashlib、hmac模块
Python中的用于加密的函数位于hashlib,hmac模块中,都是内置模块,直接导入即可使用
hashlib模块实现了md5,sha1,sha224,sha256,sha384,sha512等算法,可以通过hashlib.algorithms_available查看
hmac模块实现了hmac算法,需要一个key来进行加密
hashlib用法如下:
#导入hashlib模块
>>> import hashlib
#python可用的加密函数
>>> hashlib.algorithms_available
{'sha384', 'DSA', 'SHA224', 'sha1','sha224', 'SHA384', 'ripemd160', 'MD5', 'whirlpool', 'SHA', 'MD4', 'SHA512','ecdsa-with-SHA1', 'dsaWithSHA', 'md5', 'sha256', 'DSA-SHA', 'SHA1', 'RIPEMD160','sha', 'md4', 'SHA256', 'dsaEncryption', 'sha512'}
#python在所有平台上都可以使用的函数,也就是比较稳定的函数
>>> hashlib.algorithms_guaranteed
{'md5', 'sha256', 'sha384', 'sha1','sha224', 'sha512'}
#创建一个加密函数对象
>>> m = hashlib.md5()
>>> m.update(b'python isinteresting')
>>> m.hexdigest()
'f00243cac6d9aa2d320ed5603061483b'
>>> mySha1 = hashlib.sha1()
>>> mySha1.update(b'python is interesting')
>>> mySha1.hexdigest()
'6ad9d2ccb5fe1d5324092bdac233b4ee49d71cb8'
#如果有中文的话,使用gb2312编码
>>> myMd5 = hashlib.md5('python 真好玩'.encode('gb2312'))
>>> myMd5.hexdigest()
'6c0f33c5f4b96f1aa771bf432ba53002'
hmac用法如下:
>>> import hmac
>>> myhmac = hmac.new(b'mykey')
>>> myhmac.update(b'mimvp_data')
>>> myhmac.hexdigest()
'399146c97e1a58c74877976a460d06c7'
>>> myhmac.digest()
'9\x91F\xc9~\x1aX\xc7Hw\x97jF\r\x06\xc7'
hmac用法对比digest和hexdigest
>>> import hashlib
>>> import hmac
>>> hmac.new(b'mykey', b'mimvp_data', digestmod=hashlib.sha256).hexdigest()
'fbb30b728fd1463ea1e00d4fb3fadf3b1cb5735168a4393ed6b7c29a8352aa3b'
>>> hmac.new(b'mykey', b'mimvp_data', digestmod=hashlib.sha256).digest()
'\xfb\xb3\x0br\x8f\xd1F>\xa1\xe0\rO\xb3\xfa\xdf;\x1c\xb5sQh\xa49>\xd6\xb7\xc2\x9a\x83R\xaa;'
各种语言版本的 HMAC-SHA256的base64加密
语言包含: Javascript ,PHP,Java,Groovy,C#,Objective C,Go,Ruby,Python,Perl,Dart,Swift,Rust,Powershell
参考原文:Examples of creating base64 hashes using HMAC SHA256 in different languages
Javascript HMAC SHA256
Run the code online with this jsfiddle. Dependent upon an open source js library called http://code.google.com/p/crypto-js/.
<script src="http://crypto-js.googlecode.com/svn/tags/3.0.2/build/rollups/hmac-sha256.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.0.2/build/components/enc-base64-min.js"></script>
<script>
var hash = CryptoJS.HmacSHA256("Message", "secret");
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
document.write(hashInBase64);
</script>
PHP HMAC SHA256
PHP has built in methods for hash_hmac (PHP 5) and base64_encode (PHP 4, PHP 5) resulting in no outside dependencies. Say what you want about PHP but they have the cleanest code for this example.
$s = hash_hmac('sha256', 'Message', 'secret', true);
echo base64_encode($s);
Java HMAC SHA256
Dependent on Apache Commons Codec to encode in base64.
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class ApiSecurityExample {
public static void main(String[] args) {
try {
String secret = "secret";
String message = "Message";
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
sha256_HMAC.init(secret_key);
String hash = Base64.encodeBase64String(sha256_HMAC.doFinal(message.getBytes()));
System.out.println(hash);
}
catch (Exception e){
System.out.println("Error");
}
}
}
Groovy HMAC SHA256
It is mostly java code but there are some slight differences. Adapted from Dev Takeout - Groovy HMAC/SHA256 representation.
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
def hmac_sha256(String secretKey, String data) {
try {
Mac mac = Mac.getInstance("HmacSHA256")
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256")
mac.init(secretKeySpec)
byte[] digest = mac.doFinal(data.getBytes())
return digest
} catch (InvalidKeyException e) {
throw new RuntimeException("Invalid key exception while converting to HMac SHA256")
}
}
def hash = hmac_sha256("secret", "Message")
encodedData = hash.encodeBase64().toString()
log.info(encodedData)
C# HMAC SHA256
using System.Security.Cryptography;
namespace Test
{
public class MyHmac
{
private string CreateToken(string message, string secret)
{
secret = secret ?? "";
var encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(secret);
byte[] messageBytes = encoding.GetBytes(message);
using (var hmacsha256 = new HMACSHA256(keyByte))
{
byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
return Convert.ToBase64String(hashmessage);
}
}
}
}
Objective C and Cocoa HMAC SHA256
Most of the code required was for converting to bae64 and working the NSString and NSData data types.
#import "AppDelegate.h"
#import <CommonCrypto/CommonHMAC.h>
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSString* key = @"secret";
NSString* data = @"Message";
const char *cKey = [key cStringUsingEncoding:NSASCIIStringEncoding];
const char *cData = [data cStringUsingEncoding:NSASCIIStringEncoding];
unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
NSData *hash = [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];
NSLog(@"%@", hash);
NSString* s = [AppDelegate base64forData:hash];
NSLog(s);
}
+ (NSString*)base64forData:(NSData*)theData {
const uint8_t* input = (const uint8_t*)[theData bytes];
NSInteger length = [theData length];
static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
uint8_t* output = (uint8_t*)data.mutableBytes;
NSInteger i;
for (i=0; i < length; i += 3) {
NSInteger value = 0;
NSInteger j;
for (j = i; j < (i + 3); j++) {
value <<= 8;
if (j < length) { value |= (0xFF & input[j]); } } NSInteger theIndex = (i / 3) * 4; output[theIndex + 0] = table[(value >> 18) & 0x3F];
output[theIndex + 1] = table[(value >> 12) & 0x3F];
output[theIndex + 2] = (i + 1) < length ? table[(value >> 6) & 0x3F] : '=';
output[theIndex + 3] = (i + 2) < length ? table[(value >> 0) & 0x3F] : '=';
}
return [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; }
@end
Go programming language - Golang HMAC SHA256
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
)
func ComputeHmac256(message string, secret string) string {
key := []byte(secret)
h := hmac.New(sha256.New, key)
h.Write([]byte(message))
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
func main() {
fmt.Println(ComputeHmac256("Message", "secret"))
}
Ruby HMAC SHA256
require 'openssl'
require "base64"
hash = OpenSSL::HMAC.digest('sha256', "secret", "Message")
puts Base64.encode64(hash)
Python (2.7) HMAC SHA256
import hashlib
import hmac
import base64
message = bytes("Message").encode('utf-8')
secret = bytes("secret").encode('utf-8')
signature = base64.b64encode(hmac.new(secret, message, digestmod=hashlib.sha256).digest())
print(signature)
Tested with Python 2.7.6. Also, be sure not to name your python demo script the same as one of the imported libraries.
Perl HMAC SHA256
See Digest::SHA documentation. By convention, the Digest modules do not pad their Base64 output. To fix this you can test the length of the hash and append equal signs "=" until it is the length is a multiple of 4. We will use a modulus function below.
use Digest::SHA qw(hmac_sha256_base64);
$digest = hmac_sha256_base64("Message", "secret");
# digest is currently: qnR8UCqJggD55PohusaBNviGoOJ67HC6Btry4qXLVZc
# Fix padding of Base64 digests
while (length($digest) % 4) {
$digest .= '=';
}
print $digest;
# digest is now: qnR8UCqJggD55PohusaBNviGoOJ67HC6Btry4qXLVZc=
Dart HMAC SHA256
Dependent upon the Dart crypto package.
import 'dart:html';
import 'dart:convert';
import 'package:crypto/crypto.dart';
void main() {
String secret = 'secret';
String message = 'Message';
List<int> secretBytes = UTF8.encode('secret');
List<int> messageBytes = UTF8.encode('Message');
var hmac = new HMAC(new SHA256(), secretBytes);
hmac.add(messageBytes);
var digest = hmac.close();
var hash = CryptoUtils.bytesToBase64(digest);
// output to html page
querySelector('#hash').text = hash;
// hash => qnR8UCqJggD55PohusaBNviGoOJ67HC6Btry4qXLVZc=
}
Swift HMAC SHA256
I have not verified but see this stackOverflow post
Rust
Take a look at the alco/rust-digest repository for Rust (lang) guidance. I have not verified yet.
Powershell (Windows) HMAC SHA256
Mostly wrapping of .NET libraries but useful to see it in powershell's befuddling syntax. See code as gist
$message = 'Message'
$secret = 'secret'
$hmacsha = New-Object System.Security.Cryptography.HMACSHA256
$hmacsha.key = [Text.Encoding]::ASCII.GetBytes($secret)
$signature = $hmacsha.ComputeHash([Text.Encoding]::ASCII.GetBytes($message))
$signature = [Convert]::ToBase64String($signature)
echo $signature
# Do we get the expected signature?
echo ($signature -eq 'qnR8UCqJggD55PohusaBNviGoOJ67HC6Btry4qXLVZc=')
参考推荐:
PHP、Python、Java、C#、Javascript 运用AES加密解密
Python 常用加密算法 base64, md5, sha1
版权所有: 本文系米扑博客原创、转载、摘录,或修订后发表,最后更新于 2019-06-13 07:49:46
侵权处理: 本个人博客,不盈利,若侵犯了您的作品权,请联系博主删除,莫恶意,索钱财,感谢!