.NET Core 调用支付接口提示 RSA 签名长度不正确
结论
这个异常通常不是因为”生成的签名字符串长度不符合支付平台要求”,而是 .NET 无法按预期格式解析 RSA 私钥。常见原因包括:
- SDK 要求 PKCS#1,传入的却是 PKCS#8,或反之;
- 误把公钥、应用公钥证书或平台公钥当成私钥;
- 私钥的 PEM 头尾、换行或 Base64 内容损坏;
- 使用了加密私钥,但代码没有提供密码;
- 私钥的导入方式与当前 .NET 或支付 SDK 支持的格式不一致。
应用绑定公钥是供支付平台验证签名使用的。客户端调用接口时,仍需使用与该公钥配对的私钥完成签名。
先确认私钥类型
检查私钥文件的首尾标记,不要只根据文件扩展名判断。
PKCS#1 私钥:
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
PKCS#8 未加密私钥:
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
PKCS#8 加密私钥:
-----BEGIN ENCRYPTED PRIVATE KEY-----
...
-----END ENCRYPTED PRIVATE KEY-----
以下内容不是私钥,不能用于生成签名:
-----BEGIN PUBLIC KEY-----
-----BEGIN CERTIFICATE-----
不同支付 SDK 接收私钥的方式并不相同。有些要求完整的 PEM 文本,有些只接受去掉头尾和换行后的 Base64 字符串,还有些需要额外指定 PKCS1 或 PKCS8。具体格式应以当前 SDK 的构造函数或配置项说明为准,不能仅凭文件名判断。
使用 OpenSSL 检查私钥
可以用下面的命令检查 PKCS#1 或未加密 PKCS#8 私钥能否正常解析:
openssl pkey -in private_key.pem -check -noout
查看私钥的基本信息:
openssl pkey -in private_key.pem -text -noout
如果命令提示无法读取密钥、Base64 解码失败或 ASN.1 结构错误,通常说明文件内容、密码或格式有问题。
还可以分别从私钥和已绑定的公钥中提取公钥 DER 数据并计算摘要,以确认二者是否配对:
openssl pkey -in private_key.pem -pubout -outform DER | openssl sha256
openssl pkey -pubin -in public_key.pem -outform DER | openssl sha256
两条命令得到的摘要应当一致。比较前必须统一转换为 DER 格式,不能直接比较两个 PEM 文件的文本。
按 SDK 要求转换格式
将私钥转换为未加密的 PKCS#8:
openssl pkcs8 -topk8 -inform PEM -in private_key.pem \
-outform PEM -nocrypt -out private_key_pkcs8.pem
将私钥转换为传统 PKCS#1:
openssl rsa -in private_key.pem -traditional -out private_key_pkcs1.pem
OpenSSL 版本不同,默认输出格式也可能不同。需要 PKCS#1 时,建议显式添加 -traditional。转换前应备份原始私钥,并确认目标 SDK 允许使用未加密私钥。
在现代 .NET 中加载 PEM 私钥
在支持 RSA.ImportFromPem 的 .NET 版本中,运行库可以根据 PEM 标记识别 PKCS#1 或未加密的 PKCS#8:
using System;
using System.Security.Cryptography;
using System.Text;
string privateKeyPem = """
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
""";
using RSA rsa = RSA.Create();
rsa.ImportFromPem(privateKeyPem);
byte[] data = Encoding.UTF8.GetBytes("待签名内容");
byte[] signature = rsa.SignData(
data,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
string signatureBase64 = Convert.ToBase64String(signature);
Console.WriteLine(signatureBase64);
如果使用的是加密 PKCS#8 私钥,应调用 ImportFromEncryptedPem:
using System.Security.Cryptography;
string encryptedPrivateKeyPem = """
-----BEGIN ENCRYPTED PRIVATE KEY-----
...
-----END ENCRYPTED PRIVATE KEY-----
""";
using RSA rsa = RSA.Create();
rsa.ImportFromEncryptedPem(encryptedPrivateKeyPem, "private-key-password");
如果支付 SDK 只接受纯 Base64 内容,可以先去掉 PEM 标记和空白字符:
using System;
using System.Linq;
using System.Security.Cryptography;
static byte[] ReadPemBody(string pem)
{
string base64 = string.Concat(
pem.Split('\n')
.Select(line => line.Trim())
.Where(line =>
line.Length > 0 &&
!line.StartsWith("-----BEGIN", StringComparison.Ordinal) &&
!line.StartsWith("-----END", StringComparison.Ordinal)));
return Convert.FromBase64String(base64);
}
byte[] keyBytes = ReadPemBody(privateKeyPem);
using RSA rsa = RSA.Create();
// 仅当已经确认 keyBytes 是 PKCS#8 时使用。
rsa.ImportPkcs8PrivateKey(keyBytes, out int bytesRead);
if (bytesRead != keyBytes.Length)
{
throw new CryptographicException("私钥中包含未解析的数据。");
}
如果文件采用 PKCS#1 格式,则应改用:
rsa.ImportRSAPrivateKey(keyBytes, out int bytesRead);
不要用不同的导入方法反复尝试同一份密钥并忽略异常。应先根据 PEM 标记或 OpenSSL 的检查结果确定格式,再选择相应的导入方法。
检查签名配置是否一致
私钥成功加载后,还需要核对以下配置是否符合支付平台的要求:
- 签名算法是
RSA还是RSA2; - 摘要算法是 SHA-1 还是 SHA-256;
- RSA 填充方式是否为
PKCS#1 v1.5; - 待签名字符串的字段顺序、字符编码和换行是否正确;
- 绑定的公钥是否由当前私钥导出;
- SDK 配置中声明的私钥格式是否与实际文件一致。
例如,许多支付场景中的 RSA2 是指 SHA-256 与 RSA PKCS#1 v1.5 的组合,对应以下代码:
rsa.SignData(
data,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
RSA2 不等于 RSA-PSS。接口是否允许使用 PSS,应以对应的规范为准。
常见误区
将换行写成普通字符
下面的字符串包含真正的换行,可以作为 PEM 解析:
string key = "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----";
如果配置系统把 \n 保存成了两个普通字符,需要先确认转义规则,再将其恢复为换行:
key = key.Replace("\\n", "\n");
正常的 PEM 内容不应再被重复进行 Base64 解码。
从环境变量读取时内容被截断
将多行私钥存入环境变量或 JSON 配置时,可能出现换行转义错误、混入引号或丢失空格等问题。排查时可以记录私钥类型、字符数或 SHA-256 摘要,但不要把完整私钥写入日志。
只重新绑定公钥
如果异常出现在本地签名阶段,重新绑定公钥通常解决不了问题。此时应先确认 .NET 能否成功导入私钥。只有本地签名成功,而平台返回验签失败时,才需要重点检查公私钥是否配对,以及签名参数是否一致。
建议按以下顺序排查:确认传入的是私钥,识别 PKCS#1 或 PKCS#8 格式,使用 OpenSSL 验证,按照 SDK 要求转换格式,在本地完成一次签名和验签,最后再调用支付接口。
备注:内容仅供参考。