API 签名验证
本文介绍如何使用 API 签名和验签机制来确保 API 请求的安全性和完整性。
1. 获取 API Key 和 API Secret
参考商户接入文档,获取 API Key 和 API Secret。
2. RESTful 请求签名
2.1 请求标头
所有 RESTful 请求都需要在标头中包含以下参数:
- X-PAY-KEY: 字符串类型的 API Key。
 - X-PAY-SIGN: 使用 HMAC-SHA256 算法生成的哈希值,再使用 Base64 编码后的字符串。
 - X-PAY-TIMESTAMP: 请求的 Unix 时间戳,单位为秒,字符串类型,如 
'1684304935'。为了防止重放攻击,时间戳与服务器时间的差值不能超过 1 分钟。 
信息
注:所有的 POST 请求还需要在标头中包含 Content-Type: application/json 参数,并且保证请求体是有效的 JSON。
2.2 签名生成
X-PAY-SIGN 的请求标头是由 API Secret 和请求的参数生成的。生成签名的步骤如下:
- 将请求的时间戳 
timestamp、请求方法method(GET、POST)、请求路径requestPath(不包含域名)和请求体body拼接成字符串timestamp + method + requestPath + body。 - 使用 HMAC-SHA256 算法,使用 API Secret 作为密钥,对拼接的字符串进行哈希。
 - 将哈希值使用 Base64 编码,得到 X-PAY-SIGN 的值。
 
以下是一个使用 JavaScript 生成签名的示例:
import crypto from 'crypto';
const timestamp = '1684304935';
const sign = crypto.createHmac('sha256', apiSecret)
  .update(timestamp + 'GET' + '/api/mer/conf/list/currency?chainId=101')
  .digest('base64');
其中,
apiSecret是 API Secret;timestamp是请求的 Unix 时间戳,单位为秒,与X-PAY-TIMESTAMP的值相同;GET是请求方法method;/api/mer/conf/list/currency?chainId=101是请求路径requestPath,即 URL 中的pathname+search(对于 GET 请求,包含了查询参数)部分;body是请求体,对于 GET 请求,为空。对于 POST 请求,是请求体的 JSON 字符串,比如{"chainId":101,"description": "some products","isLegalTender": 1,"notifyUrl":"https://some-notify-url.com","outTradeNo":"12345","quoteAmount":"11.22","quoteCurrencySymbol":"USD"}。- 得到的 
sign即为X-PAY-SIGN的值。 
2.3 签名示例
- Java
 - PHP
 
public void getSignature(String requestPath, String body, String method) throws NoSuchAlgorithmException, InvalidKeyException {
    String apiUrl = 'API Request Domain';
    String apiKey = "your-api-key";
    String secret = "your-api-secret";
    // 组装签名内容
    StringBuilder sign = new StringBuilder();
    // 时间戳
    String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
    sign.append(timestamp);
    // 请求方法
    sign.append(method.toUpperCase());
    // 请求路径
    sign.append(requestPath);
    // 请求体为json字符串
    sign.append(body);
    // HMAC SHA256加密
    Mac sha256Hmac = Mac.getInstance("HmacSHA256");
    SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
    sha256Hmac.init(secretKeySpec);
    byte[] hmacBytes = sha256Hmac.doFinal(sign.toString().getBytes(StandardCharsets.UTF_8));
    String apiSign = Base64.getEncoder().encodeToString(hmacBytes);
    // 发送请求
    RestTemplate restTemplate = new RestTemplate();
    // 设置请求头
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    headers.set("X-PAY-KEY", apiKey);
    headers.set("X-PAY-SIGN", apiSign);
    headers.set("X-PAY-TIMESTAMP", timestamp);
    HttpEntity<String> requestEntity = new HttpEntity<>(body, headers);
    // 请求地址
    String url = apiUrl + requestPath;
    // 发送请求并获取响应(此处为post请求)
    ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
}
<?php
class Signature
{
    const API_URL = 'API Request Domain';
    const API_KEY = 'your-api-key';
    const SECRET_KEY = 'your-api-secret';
    public  static  function request($requestPath, $params, $method)
    {
        if (strtoupper($method) == 'GET') {
            $requestPath .= $params ? '?'.http_build_query($params) : '';
            $params = [];
        }
        $url = self::API_URL.$requestPath;
        $body = $params ? json_encode($params, JSON_UNESCAPED_SLASHES) : '';
        $timestamp =time();
        $sign = self::signature($timestamp, $method, $requestPath, $body, self::SECRET_KEY);
        $headers = self::getHeader(self::API_KEY, $sign, $timestamp);
        $ch= curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        if($method == "POST") {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
        }
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER , FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        $return = curl_exec($ch);
        curl_close($ch);
        $return = json_decode($return,true);
        return $return;
    }
    public static function getHeader($apiKey, $sign, $timestamp)
    {
        $headers = array();
        $headers[] = "Content-Type: application/json";
        $headers[] = "X-PAY-KEY: $apiKey";
        $headers[] = "X-PAY-SIGN: $sign";
        $headers[] = "X-PAY-TIMESTAMP: $timestamp";
        $headers[] = "Expect: ";
        return $headers;
    }
    public static function getTimestamp()
    {
        return date("Y-m-d\TH:i:s"). substr((string)microtime(), 1, 4) . 'Z';
    }
    public static function signature($timestamp, $method, $requestPath, $body, $secretKey)
    {
        $message = (string) $timestamp . strtoupper($method) . $requestPath . (string) $body;
        return base64_encode(hash_hmac('sha256', $message, $secretKey, true));
    }
}
3. 请求测试
在每个 API 参考页面,我们都提供了请求试用面板,可以对接口进行测试:

在试用面板中,您必须输入 API Key 和 API Secret,然后填入请求参数,面板会自动生成签名所需的三个请求标头。点击 “SEND API REQUEST” 按钮,即可发送请求并在下方查看响应。
提示
测试时可以选择 BaseURL,以便在不同环境中测试 API。沙盒环境和生产环境是两套独立的系统,使用时请注意区分。您在沙盒环境注册的商户无法在生产环境使用,反之亦然。