微服务引擎MSE

消费者调用API签名计算

2025-05-27 07:58:29

签名过程

客户端

① 从原始请求中提取关键请求信息,构造签名字符串

② 利用加密算法和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 中的某一项不存在,也需要使用一个空字符串代替。

 

字段解释:

l  HTTP Method:指 HTTP 协议中定义的 GET、PUT、POST 等请求方法,必须使用全大写的形式。

l  HTTP URI:要求必须以“/”开头,不以“/”开头的需要补充上,空路径为“/”。

l  Date:请求头中的 Date ( GMT 格式 )。

l  canonical_query_string:是对于 URL 中的 query( query 即 URL 中 ? 后面的 key1=valve1&key2=valve2 字符串)进行编码后的结果。

l  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

R1PBAGZ3LkCB