Qbit签名
Qbit 发起的每个请求都包含一个sign参数,可用于验证来自 Qbit 的请求的真实性。对于每个请求,取出data参数的数据并通过 HMAC-SHA256 哈希函数对其进行处理。
签名流程
签名步骤:
- 待签名参数集合
const params = {
'createTime': '2023-05-31T07:29:46.784Z',
'budgetId': null,
'provider': 'PrepaidCard_493728',
'currency': 'USD',
'qbitCardNoLastFour': '1234',
'id': 'b9ce056b-c1f8-4f19-b014-d7be02a54598',
'status': 'Active',
'useType': '79f22263-a3fe-4347-8a40-2af6bf422839',
'label': 'ce08100b-fca8-4a13-bbfc-c381aeaec5d0',
'balanceId': 'ab43462f-93b3-4540-8601-11d759948ee7',
'cardAddress': {
'country': 'US',
'postalCode': '94402',
'addressLine2': '',
'addressLine1': '20 Barneson ave',
'state': 'California',
'city': 'San Mateo'
},
'accountId': '01eba490-5f9c-48a6-aa2d-7bcfdff0d720',
'token': '0ef85b24-866f-4c03-a7e8-459e3742642b',
'userName': 'test test'
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 将待签名参数集合key依据“字符串首位字符的ASCII码”进行升序排列(排序过程中若出现ASCII码值相同的情况,则依次递增对下一位进行比较)
const keys = Object.keys(params);
keys.sort();
1
2
- 拼接字符串, 空值以空字符串填充
accountId=01eba490-5f9c-48a6-aa2d-7bcfdff0d720&balanceId=ab43462f-93b3-4540-8601-11d759948ee7&budgetId=&cardAddress={"addressLine1":"20 Barneson ave","addressLine2":"","city":"San Mateo","country":"US","postalCode":"94402","state":"California"}&createTime=2023-05-31T07:29:46.784Z¤cy=USD&id=b9ce056b-c1f8-4f19-b014-d7be02a54598&label=ce08100b-fca8-4a13-bbfc-c381aeaec5d0&provider=PrepaidCard_493728&qbitCardNoLastFour=1234&status=Active&token=0ef85b24-866f-4c03-a7e8-459e3742642b&useType=79f22263-a3fe-4347-8a40-2af6bf422839&userName=test test
1
- 用CLIENT_SECRET对拼接后的字符串做hmac-sha256签名,且以16进制编码,得到signature:8287d5539c03918c9de51176162c2bf7065d5a8756b014e3293be1920c20d102
const hmac = crypto.createHmac('sha256', '25d55ad283aa400af464c76d713c07ad');
const sign = hmac.update('拼接后的字符串').digest('hex');
1
2
示例代码
const crypto = require('crypto');
function joinStr(params) {
const keys = Object.keys(params);
keys.sort();
const result = [];
for (const key of keys) {
let val = params[key];
if (val == null) {
val = '';
} else if (typeof val === 'object') {
if (!Array.isArray(val)) {
const fields = Object.keys(val);
fields.sort();
const res = {};
for (const field of fields) {
res[field] = val[field];
}
val = res;
}
val = JSON.stringify(val);
}
result.push(`${key}=${val}`);
}
return result.join('&');
}
const params = {
'createTime': '2023-05-31T07:29:46.784Z',
'budgetId': null,
'provider': 'PrepaidCard_493728',
'currency': 'USD',
'qbitCardNoLastFour': '1234',
'id': 'b9ce056b-c1f8-4f19-b014-d7be02a54598',
'status': 'Active',
'useType': '79f22263-a3fe-4347-8a40-2af6bf422839',
'label': 'ce08100b-fca8-4a13-bbfc-c381aeaec5d0',
'balanceId': 'ab43462f-93b3-4540-8601-11d759948ee7',
'cardAddress': {
'country': 'US',
'postalCode': '94402',
'addressLine2': '',
'addressLine1': '20 Barneson ave',
'state': 'California',
'city': 'San Mateo'
},
'accountId': '01eba490-5f9c-48a6-aa2d-7bcfdff0d720',
'token': '0ef85b24-866f-4c03-a7e8-459e3742642b',
'userName': 'test test'
};
const hmac = crypto.createHmac('sha256', '25d55ad283aa400af464c76d713c07ad');
const sign = hmac.update(joinStr(params)).digest('hex');
console.log(sign);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.qbitnetwork.demo;
import com.alibaba.fastjson.JSON;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.*;
public class Signature {
public static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
Formatter formatter = new Formatter(sb);
for (byte b : bytes) {
formatter.format("%02x", b);
}
return sb.toString();
}
public static byte[] sign(String str, String secret) throws NoSuchAlgorithmException, InvalidKeyException {
Key sk = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
Mac mac = Mac.getInstance(sk.getAlgorithm());
mac.init(sk);
return mac.doFinal(str.getBytes());
}
public static String joinStr(Map<String, Object> data) {
String[] keys = data.keySet().toArray(new String[0]);
Arrays.sort(keys);
StringBuilder sb = new StringBuilder();
for (String key : keys) {
Object val = data.get(key);
if (val == null) {
val = "";
}
if (val instanceof Map<?, ?>) {
val = JSON.toJSONString(new TreeMap<>((Map<?, ?>) val));
}
sb.append(key).append("=").append(val).append("&");
}
String str = sb.toString();
return str.substring(0, str.length() - 1);
}
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
Map<String, String> address = new HashMap<>();
address.put("addressLine1", "20 Barneson ave");
address.put("addressLine2", "");
address.put("city", "San Mateo");
address.put("country", "US");
address.put("postalCode", "94402");
address.put("state", "California");
Map<String, Object> data = new HashMap<>();
data.put("id", "b9ce056b-c1f8-4f19-b014-d7be02a54598");
data.put("accountId", "01eba490-5f9c-48a6-aa2d-7bcfdff0d720");
data.put("token", "0ef85b24-866f-4c03-a7e8-459e3742642b");
data.put("status", "Active");
data.put("currency", "USD");
data.put("provider", "PrepaidCard_493728");
data.put("userName", "test test");
data.put("createTime", "2023-05-31T07:29:46.784Z");
data.put("qbitCardNoLastFour", "1234");
data.put("label", "ce08100b-fca8-4a13-bbfc-c381aeaec5d0");
data.put("useType", "79f22263-a3fe-4347-8a40-2af6bf422839");
data.put("balanceId", "ab43462f-93b3-4540-8601-11d759948ee7");
data.put("budgetId", null);
data.put("cardAddress", address);
String signStr = bytesToHex(sign(joinStr(data), "25d55ad283aa400af464c76d713c07ad"));
System.out.println(signStr);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import json
import hashlib
import hmac
def sign(message, secret):
encryption = hmac.new(bytes(secret, encoding='UTF-8'), bytes(message, encoding='UTF-8'), hashlib.sha256)
return encryption.hexdigest()
def join_str(origin):
keys = list(origin.keys())
keys.sort()
content = []
for key in keys:
val = origin[key]
if val is None:
val = ''
if isinstance(val, dict):
val = json.dumps(val, sort_keys=True, ensure_ascii=False, separators=(',', ':'))
content.append(key + '=' + str(val))
return '&'.join(content)
if __name__ == '__main__':
data = {
'createTime': '2023-05-31T07:29:46.784Z',
'budgetId': None,
'provider': 'PrepaidCard_493728',
'currency': 'USD',
'qbitCardNoLastFour': '1234',
'id': 'b9ce056b-c1f8-4f19-b014-d7be02a54598',
'status': 'Active',
'useType': '79f22263-a3fe-4347-8a40-2af6bf422839',
'label': 'ce08100b-fca8-4a13-bbfc-c381aeaec5d0',
'balanceId': 'ab43462f-93b3-4540-8601-11d759948ee7',
'cardAddress': {
'country': 'US',
'postalCode': '94402',
'addressLine2': '',
'addressLine1': '20 Barneson ave',
'state': 'California',
'city': 'San Mateo'
},
'accountId': '01eba490-5f9c-48a6-aa2d-7bcfdff0d720',
'token': '0ef85b24-866f-4c03-a7e8-459e3742642b',
'userName': 'test test'
}
sign_str = sign(join_str(data), '25d55ad283aa400af464c76d713c07ad')
print(sign_str)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"sort"
"strings"
)
func getKeys(m map[string]any) []string {
j := 0
keys := make([]string, len(m))
for k := range m {
keys[j] = k
j++
}
return keys
}
func joinStr(params map[string]any) string {
keys := getKeys(params)
sort.Strings(keys)
var result []string
for _, key := range keys {
val := params[key]
if val == nil {
val = ""
}
_, ok := val.(map[string]any)
if ok {
bytes, _ := json.Marshal(&val)
val = string(bytes)
fmt.Println(val)
}
result = append(result, fmt.Sprintf("%s=%s", key, fmt.Sprintf("%v", val)))
}
return strings.Join(result, "&")
}
func sign(message string, secret string) string {
key := []byte(secret)
h := hmac.New(sha256.New, key)
h.Write([]byte(message))
return hex.EncodeToString(h.Sum(nil))
}
func main() {
params := map[string]any{
"createTime": "2023-05-31T07:29:46.784Z",
"budgetId": nil,
"provider": "PrepaidCard_493728",
"currency": "USD",
"qbitCardNoLastFour": "1234",
"id": "b9ce056b-c1f8-4f19-b014-d7be02a54598",
"status": "Active",
"useType": "79f22263-a3fe-4347-8a40-2af6bf422839",
"label": "ce08100b-fca8-4a13-bbfc-c381aeaec5d0",
"balanceId": "ab43462f-93b3-4540-8601-11d759948ee7",
"cardAddress": map[string]any{
"country": "US",
"postalCode": "94402",
"addressLine2": "",
"addressLine1": "20 Barneson ave",
"state": "California",
"city": "San Mateo",
},
"accountId": "01eba490-5f9c-48a6-aa2d-7bcfdff0d720",
"token": "0ef85b24-866f-4c03-a7e8-459e3742642b",
"userName": "test test",
}
fmt.Println(sign(joinStr(params), "25d55ad283aa400af464c76d713c07ad"))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77