PAYATHON 2026

支付宝自生成RSA2密钥调用alipay.trade.app.pay报错

支付老李

结论

问题的根因基本可以确定:应用私钥的封装格式与当前 Python SDK 使用的 rsa 库不兼容。

报错发生在:

rsa.PrivateKey.load_pkcs1(private_key, format='PEM')

这里明确要求传入 PKCS#1 格式的 RSA 私钥,PEM 头尾通常为:

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

如果密钥生成工具导出的是 PKCS#8 私钥:

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

load_pkcs1() 会按 PKCS#1 结构解析 PKCS#8 数据,因此出现:

TypeError: int() argument must be a string, a bytes-like object or a number, not 'Sequence'

后面的 UnboundLocalError 是连带错误。签名失败后,sign 没有被赋值,但 SDK 仍然执行了 common_params[P_SIGN] = sign。这不是支付接口返回的业务错误。

先确认私钥格式

查看私钥文件的第一行和最后一行,不要把私钥内容粘贴到公开环境。

PKCS#1:

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

PKCS#8:

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

加密私钥还可能显示为:

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

当前代码没有提供私钥密码,也就无法直接使用加密私钥。

不要只把 BEGIN PRIVATE KEY 手工改成 BEGIN RSA PRIVATE KEY。两种格式内部的 ASN.1 结构不同,必须实际转换。

将 PKCS#8 转换为 PKCS#1

可以使用 OpenSSL 转换:

openssl pkcs8 \
  -in app_private_key_pkcs8.pem \
  -traditional \
  -nocrypt \
  -out app_private_key_pkcs1.pem

也可以使用:

openssl pkey \
  -in app_private_key_pkcs8.pem \
  -traditional \
  -out app_private_key_pkcs1.pem

转换完成后,确认文件头是:

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

然后检查密钥能否正常解析:

openssl rsa -in app_private_key_pkcs1.pem -check -noout

正常情况下会输出类似内容:

RSA key ok

不要直接覆盖原文件。先保留原始密钥,确认转换结果无误后再修改配置。

直接生成兼容的 RSA2 密钥

RSA2 是使用 RSA 密钥配合 SHA-256 签名,并不是一种单独的 PEM 封装格式。通常应使用 2048 位或更高强度的 RSA 密钥,具体要求以支付宝开放平台当前配置页面为准。

在支持 -traditional 的 OpenSSL 版本中,可以生成 PKCS#1 私钥:

openssl genrsa -traditional -out app_private_key.pem 2048

再从应用私钥导出对应的公钥:

openssl rsa \
  -in app_private_key.pem \
  -pubout \
  -out app_public_key.pem

生成后:

  • app_private_key.pem 配置到服务端 SDK,不能上传或泄露;
  • 将 app_public_key.pem 对应的公钥内容上传到支付宝开放平台;
  • 平台返回或展示的“支付宝公钥”用于验签,不能误填为应用公钥;
  • 平台上的应用公钥必须与服务端应用私钥属于同一密钥对。

如果使用支付宝提供的密钥工具,应选择符合接入语言和 SDK 要求的密钥格式。不同工具版本的导出选项可能不同,最终仍要根据 PEM 文件头和实际解析结果判断。

Python SDK 配置示例

当前调用链使用 load_pkcs1(..., format='PEM'),配置中应传入完整的 PKCS#1 PEM 文本:

with open("app_private_key_pkcs1.pem", "r", encoding="utf-8") as file:
    app_private_key = file.read()

with open("alipay_public_key.pem", "r", encoding="utf-8") as file:
    alipay_public_key = file.read()

config.app_private_key = app_private_key
config.alipay_public_key = alipay_public_key
config.sign_type = "RSA2"

私钥内容必须完整保留头尾和换行:

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

调用支付宝 SDK 前,可以先单独验证私钥:

import rsa

with open("app_private_key_pkcs1.pem", "rb") as file:
    private_key_data = file.read()

private_key = rsa.PrivateKey.load_pkcs1(
    private_key_data,
    format="PEM"
)

print(private_key.n.bit_length())

如果这段代码仍然报错,问题就在密钥文件本身,而不是 alipay.trade.app.pay 的请求参数。

密钥保存在环境变量时的处理

部署到云函数或容器后,一个常见问题是环境变量中的换行变成了字面量 \n。此时需要恢复换行:

import os

app_private_key = os.environ["ALIPAY_APP_PRIVATE_KEY"].replace("\\n", "\n")

如果环境变量中只保存了 Base64 正文,则需要补上 PEM 头尾:

import os

key_body = os.environ["ALIPAY_APP_PRIVATE_KEY_BODY"].strip()

app_private_key = (
    "-----BEGIN RSA PRIVATE KEY-----\n"
    + key_body
    + "\n-----END RSA PRIVATE KEY-----"
)

只有确认正文确实是 PKCS#1 数据时,才能这样处理。补充头尾无法把 PKCS#8 转换成 PKCS#1。

修正 UnboundLocalError

第二个异常表明,SDK 在签名异常后仍然引用了尚未初始化的 sign。不建议仅通过预先设置 sign = None 掩盖问题,因为缺少合法签名的请求同样无法调用支付宝接口。

更合理的做法是让原始签名异常直接向上抛出,例如:

try:
    sign = sign_with_rsa2(
        self.__config.app_private_key,
        sign_content,
        self.__config.charset
    )
except Exception as exc:
    raise RuntimeError("支付宝请求签名失败,请检查应用私钥格式") from exc

common_params[P_SIGN] = sign

如果项目使用的是旧版或自行复制修改过的支付宝 SDK,也可以考虑升级到与当前 Python 环境兼容的官方 SDK 版本。但无论是否升级,配置给 SDK 的私钥格式都必须符合其签名实现要求。

还需要检查的配置

私钥能够成功加载后,如果支付宝返回验签失败或签名错误,再依次检查:

  1. sign_type 是否为 RSA2。
  2. 应用私钥是否与开放平台上传的应用公钥匹配。
  3. alipay_public_key 是否确实为支付宝公钥,而不是应用公钥。
  4. 是否混用了证书模式和公钥模式的配置。
  5. PEM 文本是否缺少头尾或换行,或者包含多余的引号和缩进。
  6. 读取配置时是否错误地进行了二次 Base64 解码。
  7. 更换应用公钥后,是否使用了与当前应用及环境对应的新密钥。
  8. 是否混用了沙箱环境和正式环境的 app_id、网关或密钥。

当前堆栈中的第一个异常发生在本地生成签名的阶段,请求还没有正常发送到 alipay.trade.app.pay。应先将应用私钥转换或重新生成为 SDK 能够读取的 PKCS#1 PEM 格式,再检查支付宝侧的公钥配置。

备注:内容仅供参考。