> For the complete documentation index, see [llms.txt](https://asiabill.gitbook.io/api-explorer/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://asiabill.gitbook.io/api-explorer/fu-lu/shu-ju-qian-ming-guo-cheng.md).

# 数据签名过程

## 1，**准备**

* 涉及接口：适用本文档（API Explorer）的所有接口
* 申请签名key：商户需要申请签名key，用于数据签名和结果验签，签名key请妥善保管勿对外泄露
* 签名与验证：不建议在web前端存储和进行数据签名验签，推荐在服务端进行签名和响应结果的验签
* 签名计算：<https://www.asiabill.com/developers/sign/check.html>

## 2，通用签名规则

### 数据定义

**header参数：**&#x6307;httpRequest header里的参数，如[Request Header](/api-explorer/overview.md#qing-qiu-tou-request-header) 或 [Response Header](/api-explorer/overview.md#xiang-ying-tou-response-header)

**path参数：**&#x6307; API 文档，URL中带有占位符的为path参数；在接口文档描述中，该参数在path栏位

> **例如**：<https://host/V2022-03/payment\\_methods/><mark style="color:red;">{customerPaymentMethodId}</mark>&#x20;
>
> 该URL中 <mark style="color:red;">customerPaymentMethodId</mark> 带有占位符
>
> <https://host/V2022-03/payment\\_methods/pm\\_1526760521989763072>
>
> 此时pm\_1526760521989763072为参数

**query参数：**&#x6307;直接跟在请求地址后方的key=value数据（GET方式表单提交、url后追加的参数如：url?key1=val1\&key2=val2）

**body参数：**&#x68;ttpRequest body中的json对象

### 通用签名步骤

1，将header参数：gateway-no request-id request-time 字段按ascii排序，取其非空值累加，得到数据H

2，将path参数按照参数名的ascii排序，取其值累加得到数据P

3，将query参数按照参数名的ascii排序，取其值累加得到数据Q

4，将body中的数据转为字符串（json对象转为字符串），此为数据B

{% hint style="warning" %}
注意：webhook 请求头中的header参数包含：gateway-no request-id request-time <mark style="color:red;">version</mark>，比较其他接口多一个 <mark style="color:red;">version</mark> 字段，则数据H的获取方式：

将header参数：gateway-no request-id request-time <mark style="color:red;">version</mark> 字段按ascii排序，取其非空值累加，得到数据H
{% endhint %}

{% tabs %}
{% tab title="javaScript" %}

```
//bodyJson为接口发送在body中的json对象数据，转为字符串，即为上述的数据B
JSON.stringify(bodyJson);
```

{% endtab %}

{% tab title="java" %}

```
//jackson
new ObjectMapper().writeValueAsString(bodyJson)
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
此步骤特别注意：上述代码为转换字符串示意，一定要确保发送的bodyJson（http发送时一般会将json对象序列化）与转换后的字符串一致
{% endhint %}

5，以上四项，如数据项存在内容（字符长度大于0），则使用点号连接，得到待签名数据H.P.Q.B

6，采用商户key，对待签名数据进行HMAC-SHA256签名：HMAC-SHA256(H.P.Q.B , key)，得到签名结果16进制字符串signValue(不区分大小写)

7，将signValue设置到HttpRequest header的sign字段中，发送该请求

## 4，对请求数据签名

```javascript
# post请求示例
curl -X POST "https://api.asiabill.com/V2022-03/refund" 
-H 'Content-Type: application/json' 
-H 'request-id:123456'
-H 'request-time:1646648307486'
-H 'gateway-no:1000001'
-d '{"refundReason":"test refund","tradeNo":"2021212123123123"}'
```

对于以上请求，生成签名过程如下：

> 1，得到header参数的ascii顺序值的字符串H：10000011234561646648307486
>
> 2，path参数没有，略过该步骤
>
> 3，query参数没有，略过该步骤
>
> 4，body的json内容转字符串为{"refundReason":"test refund","tradeNo":"2021212123123123"}
>
> 5，上述各项非空数据，点号连接：10000011234561646648307486.{"refundReason":"test refund","tradeNo":"2021212123123123"}
>
> 6，使用商户key，比如12345678，进行hmac-sha256签名，得到签名结果：
>
> 8eb28572747479aedf3cbc4b59a70b5be180841a527449149ef52d480e12951b
>
> 7，将上述签名结果，放入header的sign-info字段中即可

## 5，对结果数据签名（接口响应，webHook通知）

发送请求，服务端处理后，会在http响应header中原样返回gateway-no response-id response-time version 等信息，同时使用上述签名规则，对响应header和body进行签名，签名结果放在响应header的sign-info字段。客户端收到结果后，推荐对结果数据进行签名验证

**签名验证步骤**

1. 读取请求响应header，解析得到gateway-no request-id request-time字段的值，并按照上述签名顺序将存在的值累加，得到字符串H
2. 读取响应body，得到json字符串B
3. 使用点号连接，得到待签名数据H.B
4. 使用商户key，进行hmac-sha256签名得到结果checkSignValue
5. 比较响应header中的sign的值是否与4中的checkSignValue相等，如不相等则数据异常，或验证流程未匹配上

{% hint style="warning" %}
注意：webhook 请求头中的header参数包含：gateway-no request-id request-time <mark style="color:red;">version</mark>，比较其他接口多一个 <mark style="color:red;">version</mark> 字段，则数据H的获取方式：

将header参数：gateway-no request-id request-time <mark style="color:red;">version</mark> 字段按ascii排序，取其非空值累加，得到数据H
{% endhint %}

## 6，签名代码示例

{% tabs %}
{% tab title="vue.js" %}

```
// 生成签名，HmacSha256后转为16进制字符串，javascript类似
// 引入crypto-js
import CryptoJS from 'crypto-js/crypto-js'
// 按照签名数据规则拼接数据
let sign_info = "";
// 使用key进行签名
let hash = CryptoJS.HmacSHA256(sign_info, key);
```

{% endtab %}

{% tab title="java" %}

```
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import java.nio.charset.StandardCharsets;

/**
 * 生成签名，HmacSha256后转为16进制字符串
 */
class HmacSha256Util {
	public static void main(String[] args) throws Exception {
		String key = "12345678";
		//由请求参数按规则拼接而来，这里是示例数据
		String signInfo = "1220000145508010711647341103179.{\"refundReason\":\"test refund\",\"tradeNo\":\"2021212123123123\"}";
		//密钥
		SecretKey secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
		// 实例化Mac
		Mac mac = Mac.getInstance(secretKey.getAlgorithm());
		//初始化mac
		mac.init(secretKey);
		//执行消息摘要
		byte[] digest = mac.doFinal(signInfo.getBytes(StandardCharsets.UTF_8));
		//转为十六进制的字符串
		String signValue =  new HexBinaryAdapter().marshal(digest);
		//得到签名值：7981DD89443E82C2CC0596702A86AA0FC03C77EA5818DF5BB6EE9B03BD465656
		//校验时不区分大小写
		System.out.println(signValue);
	}
}

```

{% endtab %}

{% tab title="php" %}

```
// 由请求参数按规则拼接而来，这里是示例数据
$sign_str = '1220000145508010711647341103179.{\"refundReason\":\"test refund\",\"tradeNo\":\"2021212123123123\"}';

// 密钥
$sign_key = '12345678';

// HmacSha256后转为16进制字符串
hash_hmac('sha256', $sign_str, $sign_key);
```

{% endtab %}
{% endtabs %}
