签名过程
客户端
- 从原始请求中提取关键请求信息,构造签名字符串;
- 利用加密算法和AppSecret对签名字符串进行加密处理,得到签名;
- 将签名相关的头部信息加入到原始请求中,发送请求;
服务端
- 从接收到的请求中提取关键请求信息,得到一个用来签名的签名串;
- 从接收到的请求中读取APPKey,通过APPKey查询到对应的APPSecret;
- 使用加密算法和APPSecret对关键请求信息签名串进行加密处理,得到签名;
- 从接收到的请求中读取客户端签名,对比服务器端签名和客户端签名的一致性。
签名生成公式
签名的计算公式为signature = HMAC-SHAx-HEX(secret_key, signing_string),从公式可以看出,想要获得签名需要得到 secret_key 和 signing_string 两个参数。其中 secret_key 为对应应用所配置的,signing_string 的计算公式为 signing_string = HTTP Method + \n + HTTP URI + \n + canonical_query_string + \n + access_key + \n + Date + \n + signed_headers_string。如果 signing_string 中的某一项不存在,也需要使用一个空字符串代替。
字段解释
- HTTP Method:指 HTTP 协议中定义的 GET、PUT、POST 等请求方法,必须使用全大写的形式。
- HTTP URI:要求必须以“/”开头,不以“/”开头的需要补充上,空路径为“/”。
- Date:请求头中的 Date ( GMT 格式 )。
- canonical_query_string:是对于 URL 中的 query( query 即 URL 中 ? 后面的 key1=valve1&key2=valve2 字符串)进行编码后的结果。
- signed_headers_string:是从请求头中获取客户端指定的字段,并按顺序拼接字符串的结果。
其中canonical_query_string 编码步骤如下:
提取URL 中的 query 项,即 URL 中 ? 后面的 key1=valve1&key2=valve2 字符串。
将query 根据&分隔符拆开成若干项,每一项是 key=value 或者只有 key 的形式。
当该项只有key 时,转换公式为 url_encode(key) + "=" 的形式。
当该项是key=value 的形式时,转换公式为 url_encode(key) + "=" + url_encode(value) 的形式。这里 value 可以是空字符串。
将每一项转换后,以key 按照字典顺序( ASCII 码由小到大)排序,并使用 & 符号连接起来,生成相应的 canonical_query_string 。
签名字符串拼接示例
以下面请求为例:
$ curl -i http://127.0.0.1:9080/index.html?name=james&age=36
根据签名生成公式生成的signing_string 为:
"GET
/index.html
age=36&name=james
"
注意最后一个请求头也需要+ \n。
生成签名
使用Python 来生成签名 SIGNATURE:
import base64
import hashlib
import hmac
secret = bytes('my-secret-key', 'utf-8')
message = bytes("""GET
/index.html
age=36&name=james
""", 'utf-8')
hash = hmac.new(secret, message, hashlib.sha256)
# to lowercase base64
print(base64.b64encode(hash.digest()))
不同语言的签名生成样例
以下提供一个更为简单的脚本帮助用户快速得到请求时使用的签名header
Python3
执行样例:python3 app_token_gen.py uri method ak sk query
# app_token_gen.py
import sys
import base64
import hashlib
import hmac
inputs_num = len(sys.argv)
if inputs_num < 5:
print("请依次传入uri、method、ak、sk、query(可选)的值,如python3 app_token_gen.py uri method ak sk query")
sys.exit(1)
uri = sys.argv[1]
method = sys.argv[2]
ak = sys.argv[3]
sk = sys.argv[4]
query_param = ''
if inputs_num >=6:
query_param = sys.argv[5]
if ak is None or sk is None:
print("请依次传入uri、method、ak、sk、query(可选)的值,如python3 app_token_gen.py uri method ak sk query")
sys.exit(1)
secret = bytes(sk, 'utf-8')
signature_message_template = '''%s
%s
%s
%s
'''
signature_message = signature_message_template % (method, uri, query_param, ak)
message = bytes(signature_message, 'utf-8')
hash = hmac.new(secret, message, hashlib.sha256)
# to lowercase base64
signature_code = base64.b64encode(hash.digest())
signature_code_str = str(signature_code)
headers_template ='''-H "X-HMAC-ALGORITHM: hmac-sha256" -H "X-HMAC-ACCESS-KEY: %s" -H "X-HMAC-SIGNATURE: %s"'''
signature_code_len = len(signature_code_str)
start = 2
end = signature_code_len-1
signature = signature_code_str[start:end]
hmac_headers = headers_template % (ak, signature)
# print some info
print("待签名的字符串信息:" + signature_message)
print("原始encode后的编码值:" + signature_code_str)
print("使用的hmac header:" + hmac_headers)
Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.xml.bind.DatatypeConverter;
class Main {
public static void main(String[] args) {
try {
String secret = "the shared secret key here";
String message = "this is signature string";
Mac hasher = Mac.getInstance("HmacSHA256");
hasher.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256"));
byte[] hash = hasher.doFinal(message.getBytes());
// to lowercase hexits
DatatypeConverter.printHexBinary(hash);
// to base64
DatatypeConverter.printBase64Binary(hash);
}
catch (NoSuchAlgorithmException e) {}
catch (InvalidKeyException e) {}
}
}
GO
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
)
func main() {
secret := []byte("the shared secret key here")
message := []byte("this is signature string")
hash := hmac.New(sha256.New, secret)
hash.Write(message)
// to lowercase hexits
hex.EncodeToString(hash.Sum(nil))
// to base64
base64.StdEncoding.EncodeToString(hash.Sum(nil))
}
Ruby
require 'base64'
require 'openssl'
secret = 'the shared secret key here'
message = 'this is signature string'
# to lowercase hexits
OpenSSL::HMAC.hexdigest('sha256', secret, message)
# to base64
Base64.encode64(OpenSSL::HMAC.digest('sha256', secret, message))
NodeJS
var crypto = require('crypto');
var secret = 'the shared secret key here';
var message = 'this is signature string';
var hash = crypto.createHmac('sha256', secret).update(message);
// to lowercase hexits
hash.digest('hex');
// to base64
hash.digest('base64');
PHP
<?php
$secret = 'the shared secret key here';
$message = 'this is signature string';
// to lowercase hexits
hash_hmac('sha256', $message, $secret);
// to base64
base64_encode(hash_hmac('sha256', $message, $secret, true));
Lua
local hmac = require("resty.hmac")
local secret = 'the shared secret key here'
local message = 'this is signature string'
local digest = hmac:new(secret, hmac.ALGOS.SHA256):final(message)
--to lowercase hexits
ngx.say(digest)
--to base64
ngx.say(ngx.encode_base64(digest))
Shell
SECRET="the shared secret key here"
MESSAGE="this is signature string"
# to lowercase hexits
echo -e $MESSAGE | openssl dgst -sha256 -hmac $SECRET
# to base64
echo -e $MESSAGE | openssl dgst -sha256 -hmac $SECRET -binary | base64