开始

V2(RSA签名)

用 RSA 签名时按本页的方法签名。请求带 sign_type=RSA 按 RSA 验签,否则按 MD5 验签。

签名步骤

  1. 按「V1(MD5签名)」的前三步得到待签名串,规则完全相同。
  2. 用你的商户私钥对待签名串的 UTF-8 字节做 SHA256WithRSA 签名(RSASSA-PKCS1-v1_5 填充,SHA-256 摘要)。
  3. 把签名结果做标准 Base64 编码(带 = 补位,不换行),作为 sign。
  4. 请求里必须带 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