PAYATHON 2026

证书模式支付回调使用 AlipaySignature.rsaCertCheckV1 验签失败

支付阿杰

结论

在证书模式下,支付回调必须使用支付宝公钥证书验签。应用公钥证书、应用私钥和支付宝根证书都不能用于这一步。

如果证书通过 PEM 文本 content 配置,还要确认当前支付宝 Java SDK 中的 AlipaySignature.rsaCertCheckV1 能否直接接收证书内容。部分版本要求传入的是证书文件路径。此时如果把 PEM 内容作为参数传入,SDK 会将其当作路径处理,导致验签失败。

建议按以下顺序排查:

  1. 确认使用的是支付宝公钥证书。
  2. 根据回调中的 alipay_cert_sn 选择匹配的证书。
  3. 原样获取全部回调参数,不要二次解码或修改参数值。
  4. 按回调声明的字符集和签名类型验签,通常是 UTF-8 和 RSA2。
  5. 如果 rsaCertCheckV1 只接受证书路径,就传入证书文件路径。若业务只能保存证书内容,可以从证书中提取支付宝公钥,再调用 rsaCheckV1。

常见原因

1. 用错证书

证书模式通常涉及四类材料:

  • 应用私钥:用于应用请求签名。
  • 应用公钥证书:用于标识和验证应用公钥。
  • 支付宝公钥证书:用于验证支付宝回调签名。
  • 支付宝根证书:用于校验证书链,不能代替支付宝公钥证书完成业务回调验签。

异步通知由支付宝签发,因此验签时必须使用支付宝公钥证书。

2. 把证书内容传给只接受路径的方法

典型调用如下:

boolean verified = AlipaySignature.rsaCertCheckV1(
    params,
    alipayPublicCertPath,
    charset,
    signType
);

在部分 SDK 版本中,第二个参数要求传入支付宝公钥证书的文件路径,例如:

String alipayPublicCertPath = "/opt/certs/alipayCertPublicKey_RSA2.crt";

如果传入的是下面这样的 PEM 内容:

-----BEGIN CERTIFICATE-----
MIID...
-----END CERTIFICATE-----

SDK 可能会把整段内容当作文件名,因而无法正确加载证书。

方法是否存在其他重载、具体支持哪些参数,应以项目实际使用的 SDK 版本源码或 API 定义为准,不能只看参数名判断。

3. alipay_cert_sn 与证书不匹配

证书模式的回调通常包含:

alipay_cert_sn

该参数用于标识支付宝本次签名使用的公钥证书。证书更新或轮换后,本地保存的旧证书可能无法与这个序列号匹配。

需要检查:

  • 回调中是否包含 alipay_cert_sn。
  • 本地保存的支付宝公钥证书是否仍然有效。
  • SDK 能否根据 alipay_cert_sn 找到匹配的证书。
  • 系统是否只保存了一张旧证书,而支付宝已经切换到新证书。

不要直接把 X.509 证书中显示的十进制序列号当作 alipay_cert_sn。支付宝证书序列号有特定的计算规则,建议使用 SDK 提供的证书工具方法进行计算和比较。

4. 回调参数被修改

验签必须使用支付宝回调中的原始参数。以下操作都可能破坏签名:

  • 对参数值重复执行 URL Decode。
  • 错误地将 + 转换为空格。
  • 重新格式化 JSON、XML 等参数。
  • 删除值为空字符串的参数。
  • 将参数放入对象后再次序列化。
  • 手工拼接待验签字符串。
  • 使用已经读取并转换过编码的请求体。
  • 混入网关、框架或业务代码自行添加的参数。

应直接根据 HTTP 请求参数构造 Map<String, String>。

使用证书文件路径验签

如果当前 SDK 的 rsaCertCheckV1 接受证书路径,可以这样处理:

import com.alipay.api.internal.util.AlipaySignature;

import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

public boolean verifyAlipayNotify(HttpServletRequest request) throws Exception {
    Map<String, String> params = new HashMap<>();

    Map<String, String[]> requestParams = request.getParameterMap();
    for (Map.Entry<String, String[]> entry : requestParams.entrySet()) {
        String[] values = entry.getValue();
        String value = values == null ? "" : String.join(",", values);
        params.put(entry.getKey(), value);
    }

    String alipayPublicCertPath =
        "/opt/certs/alipayCertPublicKey_RSA2.crt";

    String charset = params.getOrDefault("charset", "UTF-8");
    String signType = params.getOrDefault("sign_type", "RSA2");

    return AlipaySignature.rsaCertCheckV1(
        params,
        alipayPublicCertPath,
        charset,
        signType
    );
}

alipayPublicCertPath 中不能填写应用公钥证书的路径。

如果项目使用 javax.servlet,将示例中的导包改为:

import javax.servlet.http.HttpServletRequest;

证书只能以内容形式保存时

如果 SDK 没有提供接收证书内容的 rsaCertCheckV1 重载,可以解析支付宝公钥证书,从中提取公钥,然后调用 rsaCheckV1:

import com.alipay.api.internal.util.AlipaySignature;

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Map;

public boolean verifyWithCertContent(
        Map<String, String> params,
        String alipayPublicCertContent) throws Exception {

    CertificateFactory certificateFactory =
        CertificateFactory.getInstance("X.509");

    X509Certificate certificate;
    try (ByteArrayInputStream input = new ByteArrayInputStream(
            alipayPublicCertContent.getBytes(StandardCharsets.US_ASCII))) {
        certificate = (X509Certificate)
            certificateFactory.generateCertificate(input);
    }

    String alipayPublicKey = Base64.getEncoder()
        .encodeToString(certificate.getPublicKey().getEncoded());

    String charset = params.getOrDefault("charset", "UTF-8");
    String signType = params.getOrDefault("sign_type", "RSA2");

    return AlipaySignature.rsaCheckV1(
        params,
        alipayPublicKey,
        charset,
        signType
    );
}

使用这种方式前,需要确认回调中的 alipay_cert_sn 与该证书匹配。如果系统保存了多张支付宝公钥证书,应先根据 alipay_cert_sn 选出正确的证书,再提取公钥,不能随意固定使用其中一张。

进一步排查

排查时可以记录以下信息,但不要记录应用私钥:

sign_type
charset
alipay_cert_sn
app_id
notify_id
trade_no
验签结果
所选支付宝公钥证书的序列号

还需要重点核对:

  • 回调中的 app_id 是否属于当前应用。
  • 是否混用了沙箱环境和生产环境的证书。
  • 回调使用的是表单参数还是 JSON 请求体。
  • 实际传给验签方法的参数是否包含 sign 和 sign_type。
  • sign_type 是否为 RSA2,代码中是否误写为固定的 RSA。
  • 证书内容是否完整保留 BEGIN CERTIFICATE 和 END CERTIFICATE。
  • PEM 内容中是否混入引号、转义字符 \n 或额外的配置前缀。
  • 支付宝公钥证书是否已经更新。

验签通过后,还要继续校验 app_id、订单号、金额、卖家身份和订单状态。签名正确只能证明通知没有被篡改,不能证明其中的业务数据一定与当前订单一致。

备注:内容仅供参考。