簽名過程
客戶端
- 從原始請求中提取關鍵請求信息,構造簽名字符串;
- 利用加密算法和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 //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