Skip to main content

签名算法

1. 描述#

API网关将验证每个API请求的标识,服务器还将验证调用参数是否有效。因此,每个HTTP请求都必须包含签名信息。具有无效签名的请求将被拒绝。

API网关通过分配给应用程序的令牌来验证请求的标识。该令牌用于在HTTP请求和服务器生成签名字符串,必须严格保密。

如果您手动编写HTTP请求(而不是使用官方的SDK),则需要理解以下签名算法。

生成签名的过程如下:

  1. 根据ASCII表中的参数名称对所有请求参数(包括系统和应用程序参数,但除了“签名”和具有字节数组类型的参数)进行排序。例如:
Before sort: foo=1, bar=2, foo_bar=3, foobar=4
After sort: bar=2, foo=1, foo_bar=3, foobar=4
  1. 将排序的参数及其值连接到字符串中。例如:
bar2foo1foo_bar3foobar4
  1. 在连接的字符串的前面添加API名称。例如,添加API名称“/test/API”:
/test/apibar2foo1foo_bar3foobar4
  1. 用UTF-8格式对连接的字符串进行编码,并通过签名算法进行摘要(使用HMAC_SHA256)。例如:
hmac_sha256(/test/apibar2foo1foo_bar3foobar4)
  1. 将摘要转换为十六进制格式。例如:
hex("helloworld".getBytes("utf-8")) = "68656C6C6F776F726C64"

2. 示例#

Please find below some samples on how to caculate signature based on token(secret), API, and parameters.

请在查看下面有关基于令牌(密钥)、API和参数计算签名的示例。

2.1. PYTHON#

def sign(secret,api, parameters):
#===========================================================================
# @param secret
# @param parameters
#===========================================================================
sort_dict = sorted(parameters)
parameters_str = "%s%s" % (api,
str().join('%s%s' % (key, parameters[key]) for key in sort_dict))
h = hmac.new(secret.encode(encoding="utf-8"), parameters_str.encode(encoding="utf-8"), digestmod=hashlib.sha256)
return h.hexdigest().upper()

2.2. JAVA#

/**
* Sign the API request with body.
*/
public static String signApiRequest(Map<String, String> params, String body, String appSecret, String signMethod, String apiName) throws IOException {
// first: sort all text parameters
String[] keys = params.keySet().toArray(new String[0]);
Arrays.sort(keys);
// second: connect all text parameters with key and value
StringBuilder query = new StringBuilder();
query.append(apiName);
for (String key : keys) {
String value = params.get(key);
if (areNotEmpty(key, value)) {
query.append(key).append(value);
}
}
// third:put the body to the end
if (body != null) {
query.append(body);
}
// next : sign the whole request
byte[] bytes = null;
if(signMethod.equals(Constants.SIGN_METHOD_HMAC)) {
bytes = encryptWithHmac(query.toString(), appSecret);
} else if(signMethod.equals(Constants.SIGN_METHOD_SHA256)) {
bytes = encryptHMACSHA256(query.toString(), appSecret);
}
// finally : transfer sign result from binary to upper hex string
return byte2hex(bytes);
}
private static byte[] encryptHMACSHA256(String data, String secret) throws IOException {
byte[] bytes = null;
try {
SecretKey secretKey = new SecretKeySpec(secret.getBytes(Constants.CHARSET_UTF8), Constants.SIGN_METHOD_HMAC_SHA256);
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
bytes = mac.doFinal(data.getBytes(Constants.CHARSET_UTF8));
} catch (GeneralSecurityException gse) {
throw new IOException(gse.toString());
}
return bytes;
}
/**
* Transfer binary array to HEX string.
*/
public static String byte2hex(byte[] bytes) {
StringBuilder sign = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex.toUpperCase());
}
return sign.toString();
}

2.3. C##

public static string SignRequest(IDictionary<string, string> parameters, string body, string appSecret, string signMethod, string apiName)
{
// first : sort all key with asci order
IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters, StringComparer.Ordinal);
// second : contact all params with key order
StringBuilder query = new StringBuilder();
query.Append(apiName);
foreach (KeyValuePair<string, string> kv in sortedParams)
{
if (!string.IsNullOrEmpty(kv.Key) && !string.IsNullOrEmpty(kv.Value))
{
query.Append(kv.Key).Append(kv.Value);
}
}
// third : add body to last
if (!string.IsNullOrEmpty(body))
{
query.Append(body);
}
// next : sign the string
byte[] bytes = null;
if (signMethod.Equals(Constants.SIGN_METHOD_SHA256))
{
HMACSHA256 sha256 = new HMACSHA256(Encoding.UTF8.GetBytes(appSecret));
bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(query.ToString()));
}
// finally : transfer binary byte to hex string
StringBuilder result = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
result.Append(bytes[i].ToString("X2"));
}
return result.ToString();
}

有关其他编程语言中的签名示例代码,请参考官方SDK的源代码。

3. 如何获得令牌#

您可以通过登录到API网关来找到用于生成签名的令牌(密钥)。

picture 2

在上面的示例中,186d6c953c90f39c2973e6dd2e110d4057194996ef08fb4b3338180517b509c7 是令牌。

4. 如何调试签名错误?#

在沙箱中有两中调试方法。

4.1. DO_NOT_CHECK#

您可以通过将签名算法设置为DO_NOT_CHECK来禁用具有测试目的的签名算法。 picture 1

4.2. Echo Signature Mode#

info

如果Echo Signature Mode打开,所有API将处于回波模式,每个API调用的响应将只使用有效签名和HMAC_SHA256的输入字符串。

在网关的集成->配置菜单下,您可以打开或关闭此签名计算模式。

当签名计算模式打开时,网关将使用发送到网关的任何请求来计算签名,并且在哈希之前的计算签名和原始字符串将返回给客户端,以协助客户端验证签名算法的实现。

下面是打开签名计算模式后的返回值的一个示例。

{
"reference": "3D8E509148E6AF7C8DC4F6DC6C090026AAC78681F887B7B29D7DC011744B5330",
"locked": false,
"error_message": "The correct signature is in the reference field, raw data before sign in note field for your debug purpose.",
"cleared": false,
"error_code": "DEBUG",
"note": "/api/v1/redirect/orders/1621348784.4028008providerKshertimestampvalue2",
"signature": "8A6D48E38B99BED18C8A0C538B078D179A91E68FDA1413B80738272DE8624EEB",
"force_clear": false
}

In the note field there is the original concatenated string before the HMAC-SHA256 operation, and there is the calculated hash value in the reference field. 在note字段中,有在HMAC-SHA256操作之前的原始连接字符串,并且在参考字段中有计算出的哈希值。

5. 参考设计#

- https://github.com/ksher-solutions/payment_sdk_nodejs/blob/main/src/utils.js - https://github.com/ksher-solutions/payment_sdk_python/blob/main/ksherpay/order.py