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));
}
}