V2(RSA签名)
用 RSA 签名时按本页的方法签名。请求带 sign_type=RSA 按 RSA 验签,否则按 MD5 验签。
签名步骤
- 按「V1(MD5签名)」的前三步得到待签名串,规则完全相同。
- 用你的商户私钥对待签名串的 UTF-8 字节做 SHA256WithRSA 签名(RSASSA-PKCS1-v1_5 填充,SHA-256 摘要)。
- 把签名结果做标准 Base64 编码(带
=补位,不换行),作为sign。 - 请求里必须带
sign_type=RSA。签名方式为「RSA签名」的商户不传sign_type或传了别的值,都会返回「签名校验失败」。
提示
Base64 里有 +、/、=,sign 作为参数发送时要做 URL 编码,否则 + 会被当成空格。用表单提交或 http_build_query 这类方法拼参数会自动编码。
签名方式
签名方式在平台后台设置,有两个选项:
| 选项 | 说明 |
|---|---|
| MD5+RSA混合 | 默认,MD5 与 RSA 签名都认 |
| RSA签名 | 只认 RSA,用 MD5 签名的请求返回「签名校验失败」 |
密钥
每个商户都有一对由平台分配的 RSA 密钥(商户私钥、商户公钥),不用自己生成,在商户中心「API信息」查看、复制;「重新生成」RSA 密钥对之后,旧的立即失效。商户私钥只保存在你的服务器上,不要发给任何人。
商户中心给的私钥、公钥是不带头尾的 Base64;用作 PEM 文件时,私钥加上 -----BEGIN PRIVATE KEY----- / -----END PRIVATE KEY-----(PKCS#8),公钥加上 -----BEGIN PUBLIC KEY----- / -----END PUBLIC KEY-----,中间按 64 个字符换行。
通知验签
用 RSA 签名下的单,平台发出的支付结果通知用平台私钥签名,sign_type 为 RSA。你用平台公钥,对同样规则得到的待签名串验签。平台公钥在商户中心「API信息」查看。
PHP 示例
<?php
// 待签名串:规则与 MD5 签名相同
function zpay_sign_content(array $params): string
{
unset($params['sign'], $params['sign_type']);
$params = array_filter($params, function ($value) {
return $value !== '' && $value !== null && !is_array($value);
});
ksort($params);
$pairs = [];
foreach ($params as $name => $value) {
$pairs[] = $name . '=' . $value;
}
return implode('&', $pairs);
}
// 请求签名:用你的商户私钥
function zpay_rsa_sign(array $params, string $merchantPrivateKeyPem): string
{
openssl_sign(zpay_sign_content($params), $signature, $merchantPrivateKeyPem, OPENSSL_ALGO_SHA256);
return base64_encode($signature);
}
// 通知验签:用平台公钥
function zpay_rsa_verify(array $params, string $platformPublicKeyPem): bool
{
$signature = base64_decode($params['sign'] ?? '');
return openssl_verify(zpay_sign_content($params), $signature, $platformPublicKeyPem, OPENSSL_ALGO_SHA256) === 1;
}
// $params 为要发送的参数,写法见「V1(MD5签名)」的示例
$params['sign'] = zpay_rsa_sign($params, file_get_contents('merchant_private.pem'));
$params['sign_type'] = 'RSA';Python 示例
使用 cryptography 库(pip install cryptography):
import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
def sign_content(params: dict) -> str:
pairs = sorted(
(name, str(value))
for name, value in params.items()
if name not in ("sign", "sign_type")
and value is not None
and value != ""
and not isinstance(value, (list, tuple, dict))
)
return "&".join(f"{name}={value}" for name, value in pairs)
# 请求签名:用你的商户私钥
def rsa_sign(params: dict, merchant_private_key_pem: bytes) -> str:
key = serialization.load_pem_private_key(merchant_private_key_pem, password=None)
signature = key.sign(sign_content(params).encode("utf-8"), padding.PKCS1v15(), hashes.SHA256())
return base64.b64encode(signature).decode("ascii")
# 通知验签:用平台公钥
def rsa_verify(params: dict, platform_public_key_pem: bytes) -> bool:
key = serialization.load_pem_public_key(platform_public_key_pem)
try:
key.verify(
base64.b64decode(params.get("sign", "")),
sign_content(params).encode("utf-8"),
padding.PKCS1v15(),
hashes.SHA256(),
)
return True
except Exception:
return False