PAYATHON 2026

更换公钥证书后支付接口报签名异常,提示长度不正确

支付老李

结论

这个报错并不表示私钥”超过了允许长度”。1589 和 1624 通常只是私钥文本的字符数,不能代表 RSA 密钥位数。

从长度变化来看,更换证书后使用的私钥可能从 PKCS#1 格式变成了 PKCS#8 格式,也可能是程序误把证书或公钥文件当作私钥加载。原有签名代码仍按旧格式解析,所以抛出了”长度不正确”的错误。

仅凭 privateKeySize=1624 无法完全确定具体格式,但可以确认一点:不能根据字符串长度判断私钥是否合法。排查时应重点检查密钥格式、密钥算法,以及证书和私钥是否配套。

为什么以前 1589 可以使用

同一把 2048 位 RSA 私钥可以用不同的编码格式保存。常见格式包括:

-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----

这是 PKCS#1 格式。

-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----

这是未加密的 PKCS#8 格式。

-----BEGIN ENCRYPTED PRIVATE KEY-----
...
-----END ENCRYPTED PRIVATE KEY-----

这是加密的 PKCS#8 格式。

这些格式使用的 ASN.1 结构不同,转换成 Base64 后,字符数自然会有差异。换行符、文件头尾、证书链内容和换行风格也会影响字符串长度。因此,不能通过 1589 和 1624 判断密钥是 1024 位还是 2048 位,也不能据此判断它是否符合支付接口要求。

旧私钥能够正常使用,通常是因为它的格式符合原有 SDK 或解析代码的预期。更换后的私钥可能仍然是合法的 RSA 私钥,只是封装格式变了。

排查步骤

1. 检查实际传入的文件

先查看文件头,确认程序读取的是私钥,而不是公钥或证书:

-----BEGIN RSA PRIVATE KEY-----   # PKCS#1 私钥
-----BEGIN PRIVATE KEY-----       # PKCS#8 私钥
-----BEGIN CERTIFICATE-----       # X.509 证书,不是私钥
-----BEGIN PUBLIC KEY-----        # 公钥,不是私钥

日志中出现 privateKeySize=1624,不等于传入内容一定是私钥。有些程序只是把配置字符串的长度记录为 privateKeySize。

2. 使用 OpenSSL 检查私钥

openssl pkey -in merchant_private.pem -check -noout

如果私钥受密码保护,需要按提示输入密码。若程序或 SDK 不支持加密私钥,应按照安全规范将其转换成支持的格式,不要直接去掉密码后随意存放。

检查证书中的公钥信息:

openssl x509 -in merchant_cert.pem -text -noout

3. 确认证书与私钥配套

对于 RSA 证书,可以分别计算 modulus 的摘要:

openssl x509 -in merchant_cert.pem -noout -modulus | openssl sha256
openssl rsa -in merchant_private.pem -noout -modulus | openssl sha256

两条命令的输出应当一致。如果不一致,说明证书和私钥不属于同一对密钥。即使私钥可以成功解析,支付平台也会验签失败。

4. 按 SDK 要求转换格式

将 PKCS#1 私钥转换成未加密的 PKCS#8:

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

将 PKCS#8 私钥转换成传统 PKCS#1:

openssl rsa \
  -in private_key_pkcs8.pem \
  -traditional \
  -out rsa_private_key_pkcs1.pem

应该使用哪种格式,要以当前支付 SDK 的私钥加载说明为准,不能根据转换后的字符数来选择。

Java 加载 PKCS#8 私钥示例

Java 标准的 PKCS8EncodedKeySpec 接收 PKCS#8 DER 数据:

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.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

public class RsaSigner {

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

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

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

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

    public static String sign(String content, PrivateKey privateKey)
            throws Exception {
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(content.getBytes(StandardCharsets.UTF_8));

        return Base64.getEncoder().encodeToString(signature.sign());
    }
}

如果文件头是:

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

就不能直接传给 PKCS8EncodedKeySpec。需要先将私钥转换成 PKCS#8,或者改用支持解析 PKCS#1 的库。只删除 BEGIN RSA PRIVATE KEY 头尾,并不能把 PKCS#1 转换成 PKCS#8。

还需要检查的配置

如果格式正确但仍然报错,还要逐项检查:

  • 更换证书时,是否同时生成了新的密钥对。
  • 程序是否仍在读取旧私钥,或者读错了配置项。
  • 新证书是否为 RSA 证书。如果改成 EC 等算法,KeyFactory.getInstance("RSA") 将无法使用。
  • 签名算法是否符合接口要求,例如 SHA256withRSA,不能直接沿用旧算法。
  • 私钥内容是否在配置中心、环境变量或数据库中被截断。
  • PEM 内容中的 \n 是否被保存成了两个普通字符。
  • 是否混淆了平台公钥证书、商户公钥证书和商户私钥。
  • 签名原文的字符集、字段顺序或换行方式是否发生变化。

正确的处理方式不是把 1624 个字符裁剪成 1589 个字符,而是确认新私钥的实际格式,按照支付 SDK 的要求解析或转换,并验证它与新证书是否属于同一密钥对。

备注:内容仅供参考。