跳到主要内容

签名说明

MD5 签名算法即将弃用

2026/03/31 前,MD5 签名算法将被弃用。 请在此日期前迁移至 HMAC-SHA256。届时,使用 MD5 的请求将返回错误。

为保证数据传输的安全,我们对请求参数进行签名验证。

签名算法

我们支持以下两种签名算法:

算法sign_type 参数值推荐程度说明
HMAC-SHA256HMAC-SHA256⭐ 推荐更安全的签名算法
MD5MD5 或不传兼容为向后兼容保留
新商户建议

强烈建议使用 HMAC-SHA256,MD5 在现代密码学标准中已被视为不够安全。

如何选择签名算法

在请求中添加 sign_type 参数即可指定签名算法:

{
"platform_id": "PF0002",
"sign_type": "HMAC-SHA256",
"sign": "..."
}

不传 sign_type 时默认使用 MD5,保持向后兼容。


范例商户信息

以下所有示例使用相同的测试商户信息:

项目
商户 ID (platform_id)PF0002
平台密钥 (platform_key)ThisIsYourSecretKey123

HMAC-SHA256 签名(推荐)

签名规则

  1. 参数整理:将所有参数按参数名 ASCII 码由小到大排序
  2. 排除参数signsign_type、空值参数
  3. 拼接字符串:以 key=value 形式用 & 连接
  4. HMAC-SHA256 签名:使用密钥对字符串进行 HMAC-SHA256 签名
  5. 转小写:将结果转为 64 位小写十六进制字符串
与 MD5 的差异

HMAC-SHA256 直接使用密钥进行签名,不需要将密钥追加到字符串末尾。

陣列參數處理

當請求參數包含陣列(如 last_numbers)時,需將陣列轉換為 JSON 字串格式參與簽名。

範例last_numbers["12345", "67890"] 時:

last_numbers=["12345","67890"]
注意

陣列轉換為 JSON 字串時,不可有多餘空格,元素之間僅以逗號分隔。

代收示例

请求参数

{
"platform_id": "PF0002",
"service_id": "SVC0001",
"payment_cl_id": "DEVPM00014581",
"amount": "50000",
"notify_url": "https://your-domain.com/callback",
"request_time": "1595504136",
"sign_type": "HMAC-SHA256"
}

步骤 1:排序并拼接(排除 sign、sign_type)

amount=50000&notify_url=https://your-domain.com/callback&payment_cl_id=DEVPM00014581&platform_id=PF0002&request_time=1595504136&service_id=SVC0001

步骤 2:HMAC-SHA256 签名

使用密钥 ThisIsYourSecretKey123 签名,得到:

e8a5c3f2d1b4a6e9c7f0d2b5a8e1c4f7d0b3a6e9c2f5d8b1a4e7c0f3d6b9a2e5

代码示例

cURL
# 1. 拼接参数字符串(已排序,排除 sign 和 sign_type)
PARAM_STR="amount=50000&notify_url=https://your-domain.com/callback&payment_cl_id=DEVPM00014581&platform_id=PF0002&request_time=1595504136&service_id=SVC0001"
PLATFORM_KEY="ThisIsYourSecretKey123"

# 2. HMAC-SHA256 签名
SIGN=$(echo -n "$PARAM_STR" | openssl dgst -sha256 -hmac "$PLATFORM_KEY" | awk '{print $2}')
echo $SIGN
Python
import hmac
import hashlib

def generate_sign_hmac_sha256(params: dict, platform_key: str) -> str:
filtered = {k: v for k, v in params.items()
if v and k not in ['sign', 'sign_type']}
sorted_keys = sorted(filtered.keys())
param_str = '&'.join([f'{k}={filtered[k]}' for k in sorted_keys])

return hmac.new(
platform_key.encode('utf-8'),
param_str.encode('utf-8'),
hashlib.sha256
).hexdigest().lower()
Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;

public class SignatureUtil {
public static String generateHmacSha256(Map<String, String> params, String key) {
TreeMap<String, String> filtered = new TreeMap<>();
for (Map.Entry<String, String> e : params.entrySet()) {
if (e.getValue() != null && !e.getValue().isEmpty()
&& !e.getKey().equals("sign") && !e.getKey().equals("sign_type")) {
filtered.put(e.getKey(), e.getValue());
}
}

StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> e : filtered.entrySet()) {
if (sb.length() > 0) sb.append("&");
sb.append(e.getKey()).append("=").append(e.getValue());
}

try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"));
byte[] hash = mac.doFinal(sb.toString().getBytes("UTF-8"));
StringBuilder hex = new StringBuilder();
for (byte b : hash) hex.append(String.format("%02x", b));
return hex.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
PHP
<?php
function generateSignHmacSha256(array $params, string $platformKey): string {
$filtered = array_filter($params, function($v, $k) {
return !empty($v) && !in_array($k, ['sign', 'sign_type']);
}, ARRAY_FILTER_USE_BOTH);

ksort($filtered);
$paramStr = http_build_query($filtered, '', '&');

return strtolower(hash_hmac('sha256', $paramStr, $platformKey));
}
?>
Go
package main

import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"sort"
"strings"
)

func GenerateSignHmacSha256(params map[string]string, platformKey string) string {
var keys []string
for k, v := range params {
if v != "" && k != "sign" && k != "sign_type" {
keys = append(keys, k)
}
}
sort.Strings(keys)

var pairs []string
for _, k := range keys {
pairs = append(pairs, k+"="+params[k])
}
paramStr := strings.Join(pairs, "&")

h := hmac.New(sha256.New, []byte(platformKey))
h.Write([]byte(paramStr))
return strings.ToLower(hex.EncodeToString(h.Sum(nil)))
}
JavaScript
const crypto = require('crypto');

function generateSignHmacSha256(params, platformKey) {
// 过滤并排序
const filtered = Object.entries(params)
.filter(([k, v]) => v && k !== 'sign' && k !== 'sign_type')
.sort(([a], [b]) => a.localeCompare(b));

// 拼接
const paramStr = filtered.map(([k, v]) => `${k}=${v}`).join('&');

// HMAC-SHA256
return crypto
.createHmac('sha256', platformKey)
.update(paramStr)
.digest('hex')
.toLowerCase();
}

MD5 签名(兼容旧版) - 点击展开
已不推荐

MD5 仅为向后兼容保留,新商户请使用 HMAC-SHA256。

签名规则

  1. 参数整理:将所有参数按参数名 ASCII 码由小到大排序
  2. 排除参数signsign_type、空值参数
  3. 拼接字符串:以 key=value 形式用 & 连接
  4. 追加密钥:在末尾追加 &{platform_key}(不带 key= 前缀)
  5. MD5 杂凑:转为 32 位小写字符串

代收示例

请求参数(不传 sign_type 则默认 MD5):

{
"platform_id": "PF0002",
"service_id": "SVC0001",
"payment_cl_id": "DEVPM00014581",
"amount": "50000",
"notify_url": "https://your-domain.com/callback",
"request_time": "1595504136"
}

步骤 1:排序并拼接

amount=50000&notify_url=https://your-domain.com/callback&payment_cl_id=DEVPM00014581&platform_id=PF0002&request_time=1595504136&service_id=SVC0001

步骤 2:追加密钥

amount=50000&notify_url=https://your-domain.com/callback&payment_cl_id=DEVPM00014581&platform_id=PF0002&request_time=1595504136&service_id=SVC0001&ThisIsYourSecretKey123

步骤 3:MD5 杂凑

49be5fa304b5f536c6e2ea89435e211a

代码示例

Python
import hashlib

def generate_sign_md5(params: dict, platform_key: str) -> str:
filtered = {k: v for k, v in params.items()
if v and k not in ['sign', 'sign_type']}
sorted_keys = sorted(filtered.keys())
param_str = '&'.join([f'{k}={filtered[k]}' for k in sorted_keys])
sign_str = f'{param_str}&{platform_key}'
return hashlib.md5(sign_str.encode('utf-8')).hexdigest().lower()
Java
import java.security.MessageDigest;
import java.util.*;

public class SignatureUtil {
public static String generateMd5(Map<String, String> params, String key) {
TreeMap<String, String> filtered = new TreeMap<>();
for (Map.Entry<String, String> e : params.entrySet()) {
if (e.getValue() != null && !e.getValue().isEmpty()
&& !e.getKey().equals("sign") && !e.getKey().equals("sign_type")) {
filtered.put(e.getKey(), e.getValue());
}
}

StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> e : filtered.entrySet()) {
if (sb.length() > 0) sb.append("&");
sb.append(e.getKey()).append("=").append(e.getValue());
}
sb.append("&").append(key);

try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(sb.toString().getBytes("UTF-8"));
StringBuilder hex = new StringBuilder();
for (byte b : digest) hex.append(String.format("%02x", b));
return hex.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

验证回调签名

收到回调通知时,请验证签名以确保数据未被篡改:

  1. 从回调参数中提取 sign_type(如果有)
  2. 使用相同的算法重新计算签名
  3. 比对计算结果与 sign 是否一致
安全建议

务必验证回调签名,防止伪造回调攻击。


常见问题

签名错误 (error_code: 0004)

常见原因:

  1. MD5 密钥追加方式错误 - 应该是 &{platform_key} 而不是 &key={platform_key}
  2. 参数值不一致 - 确保签名时的参数值与实际请求完全一致
  3. 未排除 sign_type - 签名计算时应排除 signsign_type
  4. 编码问题 - 确保使用 UTF-8 编码