HMAC(AK/SK)验证使用详解
固定请求头说明
| 请求头 | 是否必填 | 说明 |
|---|---|---|
| X-GS-API-AK | 是 | 填写AK密钥 |
| X-GS-API-DATE | 是 | 填写发送请求的时间(GMT 格式) |
| X-GS-API-ALGORITHM | 是 | 可选算法"hmac-sha1", "hmac-sha256", "hmac-sha512",建议"hmac-sha256" |
| X-GS-API-SIGNATURE | 是 | 需要组合HTTP Method、URI等生成,详见X-GS-API-SIGNATURE 签名生成公式 |
| X-GS-API-SIGNED-HEADERS | 可选 | headers 中加入X-GS-API-SIGNATURE计算的请求头,注意顺序不能错! |
| X-GS-API-BODY-DIGEST | 可选 | 当网关路由开启 body 做签名校验时必传,详见X-GS-API-BODY-DIGEST 签名生成公式 |
X-GS-API-SIGNATURE 签名生成公式
在使用HMAC(AK/SK)验证鉴权方式时,会涉及到签名。签名的计算公式为 X-GS-API-SIGNATURE = HMAC-SHAx-HEX(SK密钥, signing_string)。
为了生成签名需要两个参数:SK密钥 和 signing_string。其中 SK密钥 由界面自动生成,也可手动修改,signing_string 的计算公式为 signing_string = HTTP Method + \n + HTTP URI + \n + canonical_query_string + \n + access_key + \n + Date + \n + signed_headers_string。以下是对计算公式中各个字段的释义:
- HTTP Method:指 HTTP 协议中定义的 GET、PUT、POST 等请求方法,必须使用全大写的形式。
- HTTP URI:HTTP URI。必须以 / 开头,/ 表示空路径。
- Date:请求头中的日期(GMT 格式)。
- canonical_query_string:对 URL 中的 query(query 即 URL 中 ? 后面的 key1=valve1&key2=valve2 字符串)进行编码后的结果。
- signed_headers_string:从请求头中获取客户端指定的字段,并按顺序拼接字符串的结果。
! 如果 signing_string 中的任意一项不存在,则需要使用一个空字符串代替。 由于签名计算时,会区分大小写字母,在使用时,请规范其参数命名。
生成 canonical_query_string 的算法描述如下:
1、提取 URL 中的 query 项。
2、使用 & 作为分隔符,将 query 拆分成键值对。 - 当该项有 key 时,转换公式为 url_encode(key) + "="。 - 当该项同时有 key 和 value 时,转换公式为 url_encode(key) + "=" + url_encode(value) 。此处 value 可以是空字符串。 - 将每一项转换后,以 key 按照字典顺序(ASCII 码由小到大)排序,并使用 & 符号连接起来,生成相应的 canonical_query_string 。
生成 signed_headers_string 的算法如下:
1.从请求头中获取指定的 headers 加入计算中,指定传输请求头为X-GS-API-SIGNED-HEADERS。
2.从请求头中按顺序取出 SIGNED_HEADERS 指定的 headers,并按顺序用 name:value 方式拼接起来,拼接完后就生成了 signed_headers_string。
HeaderKey1 + ":" + HeaderValue1 + "\n"+ HeaderKey2 + ":" + HeaderValue2 + "\n"+ ... HeaderKeyN + ":" + HeaderValueN + "\n"
X-GS-API-BODY-DIGEST 签名生成公式
如果没有请求 body,你可以将 X-GS-API-ALGORITHM 的值设置为空字符串的 HMAC-SHA。
X-GS-API-ALGORITHM: base64(hmac-sha(
))
签名生成公式过程详解
接下来,我们将以下述请求为例,为你介绍签名生成公式的具体计算过程:
curl -i http://127.0.0.1:9080/index.html?name=james&age=36 \ -H "X-GS-API-SIGNED-HEADERS: User-Agent;x-custom-a" \ -H "x-custom-a: test" \ -H "User-Agent: curl/7.29.0"
1、上文请求默认的 HTTP Method 是 GET,得到 signing_string 为:
"GET"
2、请求的 URI 是 /index.html,根据 HTTP Method + \n + HTTP URI 得到 signing_string 为:
"GET /index.html"
3、URL 中的 query 项是 name=james&age=36,假设 encode_uri_params 为 false,根据 canonical_query_string 的算法,重点是对 key 进行字典排序,得到 age=36&name=james;根据 HTTP Method + \n + HTTP URI + \n + canonical_query_string 得到 signing_string 为:
"GET /index.html age=36&name=james"
4、access_key 是 user-key,根据 HTTP Method + \n + HTTP URI + \n + canonical_query_string + \n + access_key 得到 signing_string 为:
"GET /index.html age=36&name=james user-key"
5、Date 是指 GMT 格式的日期,形如 Tue, 19 Jan 2021 11:33:20 GMT, 根据 HTTP Method + \n + HTTP URI + \n + canonical_query_string + \n + access_key + \n + Date 得到 signing_string 为:
"GET /index.html age=36&name=james user-key Tue, 19 Jan 2021 11:33:20 GMT"
6、signed_headers_string 用来制定参与到签名的 headers,在上面示例中包括 User-Agent: curl/7.29.0 和 x-custom-a: test。 根据 HTTP Method + \n + HTTP URI + \n + canonical_query_string + \n + access_key + \n + Date + \n + signed_headers_string + \n,得到完整的 signing_string 为:
"GET /index.html age=36&name=james user-key Tue, 19 Jan 2021 11:33:20 GMT User-Agent:curl/7.29.0 x-custom-a:test "
通过以下 Python 代码为上述请求生成签名 SIGNATURE:
import base64
import hashlib
import hmac
secret = bytes('my-secret-key', 'utf-8')
message = bytes("""GET
/index.html
age=36&name=james
user-key
Tue, 19 Jan 2021 11:33:20 GMT
User-Agent:curl/7.29.0
x-custom-a:test
""", 'utf-8')
hash = hmac.new(secret, message, hashlib.sha256)
# to lowercase base64
print(base64.b64encode(hash.digest()))
也可以参考文章末尾了解如何使用不同的编程语言生成签名。 SIGNATURE:8XV1GB7Tq23OJcoz6wjqTs4ZLxr9DiLoY4PxzScWGYg= 签名生成后,你可以通过以下示例使用生成的签名发起请求:
curl -i "http://127.0.0.1:9080/index.html?name=james&age=36" \ -H "X-GS-API-SIGNATURE: 8XV1GB7Tq23OJcoz6wjqTs4ZLxr9DiLoY4PxzScWGYg=" \ -H "X-GS-API-ALGORITHM: hmac-sha256" \ -H "X-GS-API-AK": user-key" \ -H "X-GS-API-DATE: Tue, 19 Jan 2021 11:33:20 GMT" \ -H "X-GS-API-SIGNED-HEADERS: User-Agent;x-custom-a" \ -H "x-custom-a: test" \ -H "User-Agent: curl/7.29.0"
HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive Date: Tue, 19 Jan 2021 11:33:20 GMT Server: APISIX/2.2 ......
不同语言样例
Java
《Java版本Demo》,建议其他语言引入相关java语言模块,使用java版本Demo中GsApiSignature.java生成HTTP请求头。
python
import base64
import hashlib
import hmac
secret = bytes('the shared secret key here', 'utf-8')
message = bytes('this is signature string', 'utf-8')
hash = hmac.new(secret, message, hashlib.sha256)
# to lowercase hexits
hash.hexdigest()
# to lowercase base64
base64.b64encode(hash.digest())
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))
}
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');
JavaScript ES6
const secret = 'the shared secret key here';
const message = 'this is signature string';
const getUtf8Bytes = str =>
new Uint8Array(
[...unescape(encodeURIComponent(str))].map(c => c.charCodeAt(0))
);
const secretBytes = getUtf8Bytes(secret);
const messageBytes = getUtf8Bytes(message);
const cryptoKey = await crypto.subtle.importKey(
'raw', secretBytes, { name: 'HMAC', hash: 'SHA-256' },
true, ['sign']
);
const sig = await crypto.subtle.sign('HMAC', cryptoKey, messageBytes);
// to lowercase hexits
[...new Uint8Array(sig)].map(b => b.toString(16).padStart(2, '0')).join('');
// to base64
btoa(String.fromCharCode(...new Uint8Array(sig)));
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))