关于签名

签名说明

ach-access-sign的请求头:
是对timestamp + method + requestPath + body使用HMAC SHA256方法用SecretKey加密,通过Base-64编码输出而得到的。
其中requestPath中参数,跟body规则一致。list值里面的顺序按照以下类型顺序int - float/double - string - list & object排序,int,double,string同类型之间按字典排序;list & object之间根据在数列红的位置进行排序;里面的object/list 等子结构体以一致的规则进行递归排序。然后放回进行签名。其中去掉null以及’’值,空数列[].空字典{}。

签名串示例

{{“x”: 1, “y”: 2}, 1, 3, 2, -4, 1.1, “xxxxx”, “yyyy”, “jscx”, 0, “sss”,{“z”:2,”x”:1,”a”:””}}
排序后:
{-4,0,1,2,3,1.1,”jscx”,”sss”,”xxxxx”,”yyy”,{“x”: 1, “y”: 2},{“x”: 1, “z”: 2}}

注意事项

  • 需注意原则上,传输中数列的数据排序不应对传输内容有关联。
    如Path以及body中,都有参数,则将Path以及body各自排序,然后按该拼接顺序(timestamp + method + requestPath + body)组合在一起签名
    例子: timestamp = 1538054050234, GET 请求,path=/api/v1/crypto/order?order_no=sdf23?token=ETH, Body 为空,签名内容为
    “1538054050234” + “GET” + “/api/v1/crypto/order?token=ETH?order_no=sdf23
    其中,timestamp的值与ach-access-timestamp请求头相同,为ISO格式,Unix 时间,以毫秒为单位,十三位时间戳。币安/OKX交易所规则,
    如:1538054050231
  • method是请求方法,字母全部大写
    如:GET/POST
  • requestPath是请求接口路径,大小写敏感, 如果URL已/结尾,仍然还需加上
    如:/api/v1/crypto/order
  • body是指请求主体的字符串,如果请求没有主体(通常为GET请求)则body可省略,字典顺序,body内部的结构也是字典顺序,空值不参与签名,任何参数为空时,会被过滤掉,不参与签名。
    例如: ‘1538054051230’ + ‘GET’ + ‘/api/v1/crypto/token/price’ + body
  • secretKey,apiKey 大小写敏感
    使用 HMAC SHA256 使用密钥对于哈希字符串进行签名。
  • 以 Base64 格式对签名进行编码。

示例

import base64
import hmac
import json
import requests
from datetime import datetime
from typing import Dict, Any, List, Union, Type


# 签名
class SignatureUtility:
    def generate_signature(self, secret_key: str, message: str) -> str:
        """Generate the HMAC SHA256 signature for a given message."""
        signature = hmac.new(
            secret_key.encode(), message.encode(), digestmod="sha256"
        ).digest()
        return base64.b64encode(signature).decode()

    def verify_signature(
        self, secret_key: str, message: str, received_signature: str
    ) -> bool:
        """Verify the received signature against the computed one."""
        computed_signature = self.generate_signature(secret_key, message)
        return hmac.compare_digest(computed_signature, received_signature)

    def clean_and_sort_dict(
        self, data: Union[Dict[Any, Union[Dict, Any]], List[Union[Dict, Any]]]
    ) -> Union[Dict[Any, Union[Dict, Any]], List[Union[Dict, Any]]]:
        if isinstance(data, dict):
            sorted_dict = {}
            for key, value in sorted(data.items()):
                if isinstance(value, (dict, list)):
                    value = self.clean_and_sort_dict(value)

                # Checking for non-empty values, including non-empty lists and non-empty dictionaries
                if value or value == 0:
                    sorted_dict[key] = value
            return (
                sorted_dict if sorted_dict else None
            )  # Return None if the dictionary is empty
        elif isinstance(data, list):
            int_list = sorted([item for item in data if isinstance(item, int)])
            float_list = sorted([item for item in data if isinstance(item, float)])
            str_list = sorted([item for item in data if isinstance(item, str)])
            complex_data_types = [
                item for item in data if isinstance(item, (dict, list))
            ]

            sorted_complex_data = [
                self.clean_and_sort_dict(item) for item in complex_data_types
            ]
            sorted_complex_data = [
                item for item in sorted_complex_data if item
            ]  # Filter out None values

            result = int_list + float_list + str_list + sorted_complex_data
            return result if result else None  # Return None if the list is empty





#
def post_request(URL: str, body: Type):
    sign_utility = SignatureUtility()
    query_string = ""
    method = "POST"
    #
    # 请求签名
    body_str = ""
    timestamp = str(int(datetime.utcnow().timestamp() * 1000))
    req_cleaned_body = sign_utility.clean_and_sort_dict(body)
    if req_cleaned_body is not None:
        body_str = json.dumps(
            req_cleaned_body,
            sort_keys=True,
            separators=(",", ":"),
            ensure_ascii=False,
        )
    message = f"{timestamp}{method}{URL}{query_string}{body_str}"
    print(message)
    sign = sign_utility.generate_signature(secret_key=SECRET_KEY, message=message)
    print(sign)
    # POST REQUEST
    headers = {
        "ach-access-key": API_KEY,
        "ach-access-sign": sign,
        "ach-access-timestamp": timestamp,
    }
    response = requests.post(url=HOST + URL, headers=headers, json=body)
    return response.text



API_KEY = "service000-local-apikey"
SECRET_KEY = "service000-local-secretkey"
HOST = "http://127.0.0.1:8072"

#
if __name__ == "__main__":
    url = "/open/api/card/create"
    body = {
        "callbackUrl": "http://baidu.com",
        "cardHolder": {
            "address": {
                "city": "string",
                "country": "string",
                "state": "string",
                "street": "string",
                "zipCode": "string"
            },
            "firstName": "string",
            "lastName": "string"
        },
        "customerId": "user_id_123",
        "deposit": "100",
        "orderNo": "12165456165441",
        "tagNameList": [
            "string"
        ],
        "vid": "vab_069af8a792ad"
    }
    response = post_request(url, body)
    print(f"response: {response}")