PAYATHON 2026

支付宝手机网站支付提示RSA2异常且privateKeySize=0

支付小周

结论

RSA2遭遇异常,请检查私钥格式是否正确 表示支付宝 SDK 生成 RSA2 签名时,没有获得可用的应用私钥。技术参数中的 privateKeySize=0 表明传入签名模块的私钥字符串长度为 0。常见情况包括私钥未配置、读取失败、配置项名称有误,或私钥经过处理后变成了空字符串。

排查时,应先确认私钥是否真正传入,再检查它是否符合当前 SDK 要求的格式。这里需要的是应用私钥,不是支付宝公钥或应用公钥。

常见原因

常见原因有:

  • 私钥配置项为空,或配置没有在当前运行环境中生效。
  • 配置文件路径错误,程序没有读到私钥文件。
  • 环境变量名称写错,或服务启动时没有加载该变量。
  • 将支付宝公钥或应用公钥误填到私钥配置中。
  • Java SDK 要求 PKCS#8 私钥,实际传入的却是 PKCS#1 私钥。
  • 删除 PEM 头尾时使用了错误的字符串或正则表达式,导致整个私钥被清空。
  • 私钥中的换行符被保存为字面量 \n,程序却没有将其转换为真实换行。
  • YAML、Properties、JSON 等配置格式没有正确处理多行内容。
  • 私钥中混入全角空格或不可见字符,或复制时遗漏了部分内容。
  • 代码读取了错误的环境配置,例如测试环境读取了生产配置项。

privateKeySize=0 通常说明程序根本没有读到私钥,而不只是密钥算法不匹配。私钥已经成功传入但格式有误时,其长度通常不会是 0。

排查步骤

1. 确认私钥配置确实存在

不要打印完整私钥,只需检查它是否为空,并记录字符长度:

String privateKey = config.getPrivateKey();

if (privateKey == null || privateKey.trim().isEmpty()) {
    throw new IllegalStateException("支付宝应用私钥未配置或读取失败");
}

System.out.println("privateKeySize=" + privateKey.trim().length());

如果这里仍然输出 privateKeySize=0,应检查配置文件、环境变量、容器密钥挂载和启动参数,暂时不用继续排查 RSA 算法。

2. 确认密钥类型没有填错

手机网站支付通常会用到以下几种密钥:

  • 应用私钥:商户系统生成请求签名时使用。
  • 应用公钥:上传到支付宝开放平台,用于匹配应用私钥。
  • 支付宝公钥:商户系统验证支付宝响应或异步通知签名时使用。

遇到 RSA2 私钥异常时,应确认 SDK 的私钥参数中传入的是应用私钥。支付宝公钥不能用来生成商户请求签名。

3. 检查 PEM 头部

常见的私钥头部如下:

-----BEGIN PRIVATE KEY-----

这通常表示 PKCS#8 私钥。

-----BEGIN RSA PRIVATE KEY-----

这通常表示 PKCS#1 RSA 私钥。

部分 Java 版本或支付宝 Java SDK 的配置方式要求传入 PKCS#8 私钥正文,也就是删除 PEM 头尾和换行后得到的 Base64 字符串。不同语言、SDK 版本和初始化接口的要求并不完全相同,应以当前 SDK 的接口说明为准,不要用同一种方式处理所有私钥。

4. 使用 OpenSSL 验证私钥

如果私钥保存在 PEM 文件中,可以用以下命令检查它能否被正确解析:

openssl pkey -in app_private_key.pem -check

如果命令要求输入密码,该文件可能是加密私钥。支付 SDK 是否支持加密私钥取决于具体版本和使用方式。多数直接接收私钥字符串的接口通常要求使用未加密私钥。

如果现有密钥是 PKCS#1,可以将其转换为未加密的 PKCS#8 格式:

openssl pkcs8 \
  -topk8 \
  -inform PEM \
  -in rsa_private_key.pem \
  -outform PEM \
  -nocrypt \
  -out app_private_key_pkcs8.pem

转换前应备份原密钥,并确保输出文件不会被提交到代码仓库,也不会暴露给无关人员。

Java 私钥读取示例

下面的代码读取 PKCS#8 PEM 私钥,并将其转换为 Java 可识别的 PrivateKey:

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

public class PrivateKeyLoader {

    public static PrivateKey loadPkcs8PrivateKey(Path path) throws Exception {
        String pem = Files.readString(path, StandardCharsets.UTF_8);

        if (pem == null || pem.trim().isEmpty()) {
            throw new IllegalArgumentException("私钥文件为空");
        }

        String base64Key = pem
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s", "");

        if (base64Key.isEmpty()) {
            throw new IllegalArgumentException("移除 PEM 头尾后私钥内容为空");
        }

        byte[] keyBytes = Base64.getDecoder().decode(base64Key);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);

        return KeyFactory.getInstance("RSA").generatePrivate(keySpec);
    }
}

如果支付宝 SDK 要求传入不带 PEM 头尾的私钥字符串,可以单独提取私钥正文:

String privateKey = Files.readString(
        Path.of("/secure/app_private_key.pem"),
        StandardCharsets.UTF_8
);

privateKey = privateKey
        .replace("-----BEGIN PRIVATE KEY-----", "")
        .replace("-----END PRIVATE KEY-----", "")
        .replaceAll("\\s", "");

if (privateKey.isEmpty()) {
    throw new IllegalStateException("支付宝应用私钥读取结果为空");
}

不要在同时删除 BEGIN PRIVATE KEY 和 BEGIN RSA PRIVATE KEY 后,就认为两种密钥格式已经等价。删除头尾只会改变文本外观,并不能把 PKCS#1 转换成 PKCS#8。格式转换应使用 OpenSSL 等工具完成。

环境变量中的换行处理

通过环境变量传递私钥时,换行可能会被保存成 \ 和 n 两个字符。确认配置方式后,可以进行转换:

String privateKey = System.getenv("ALIPAY_APP_PRIVATE_KEY");

if (privateKey == null || privateKey.isBlank()) {
    throw new IllegalStateException("ALIPAY_APP_PRIVATE_KEY 未配置");
}

privateKey = privateKey.replace("\\n", "\n");

如果 SDK 要求纯 Base64 正文,再删除 PEM 头尾和空白字符:

privateKey = privateKey
        .replace("-----BEGIN PRIVATE KEY-----", "")
        .replace("-----END PRIVATE KEY-----", "")
        .replaceAll("\\s", "");

是否保留换行和 PEM 头尾,要根据所用的 SDK、语言和构造方法确定。

仍然报错时应核对的内容

私钥成功加载且长度不为 0 后,还需要确认:

  • 签名类型设置为 RSA2。
  • 字符集通常配置为 UTF-8。
  • 应用私钥与开放平台配置的应用公钥属于同一密钥对。
  • 测试环境和生产环境没有混用密钥。
  • 私钥内容没有被截断,Base64 内容可以正常解码。
  • SDK 接收的是私钥内容,而不是私钥文件路径。
  • 服务重启后已经加载最新配置。
  • 多节点部署时,每个节点都使用了正确的配置。

可以从私钥导出公钥来核对密钥对,但不要覆盖开放平台上的配置:

openssl pkey \
  -in app_private_key.pem \
  -pubout \
  -out derived_public_key.pem

注意事项

不要在日志、异常信息、截图或工单中输出完整私钥。排查时只记录私钥是否为空、字符长度、格式类型或密钥指纹。

如果私钥曾被提交到公开仓库、发送到群聊,或出现在公开日志中,应将其视为已经泄露,立即重新生成密钥对,并更新开放平台和应用配置。仅删除泄露记录并不能恢复密钥安全。

这类问题应按以下顺序排查:先解决 privateKeySize=0,确认程序确实取得了私钥;再核对应用私钥与应用公钥的关系,以及 PKCS#1、PKCS#8 和 PEM 文本格式。

备注:内容仅供参考。