更换公钥证书后支付接口报签名异常,提示长度不正确
结论
这个报错并不表示私钥”超过了允许长度”。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 的要求解析或转换,并验证它与新证书是否属于同一密钥对。
备注:内容仅供参考。