PAYATHON 2026

alipay-sdk-python 回调验签提示 RSA key format is not supported

支付小周

结论

这段以 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8...IDAQAB 开头和结尾的内容,通常是 Base64 编码的 X.509 SubjectPublicKeyInfo 公钥数据,只有公钥正文,没有 PEM 头尾。

RSA.import_key() 可以加载完整的 PEM 文本,也可以加载由 Base64 解码得到的 DER 字节,但不能直接将裸 Base64 字符串作为 PEM 公钥解析。直接传入时会报错:

RSA key format is not supported

可以将公钥补全为标准 PEM 格式:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8...
-----END PUBLIC KEY-----

也可以先解码裸 Base64 内容,再把得到的 DER 字节传给 RSA.import_key()。

原因分析

下面这种支付宝公钥:

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8...wUnswIDAQAB

并不是完整的 PEM 文件,而是 PEM 中间的 Base64 正文。标准 PEM 公钥通常是:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8...
-----END PUBLIC KEY-----

如果直接执行:

key = RSA.import_key(alipay_public_key)

而 alipay_public_key 只有 MIIB...IDAQAB 这段正文,解析器无法确认它是 PEM、DER,还是其他受支持的密钥编码,因此会报格式不支持。

常见的 PEM 公钥头部有两种:

-----BEGIN PUBLIC KEY-----

表示 X.509 SubjectPublicKeyInfo 格式。

-----BEGIN RSA PUBLIC KEY-----

表示 PKCS#1 RSA 公钥格式。

以 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8... 开头的支付宝公钥通常属于前一种,应使用 BEGIN PUBLIC KEY,不要随意改成 BEGIN RSA PUBLIC KEY。

解决方法一:补全 PEM 头尾

先清除公钥正文中的空格和换行,再按照每行 64 个字符重新排版:

import re
import textwrap
from Crypto.PublicKey import RSA


def normalize_alipay_public_key(public_key: str) -> str:
    public_key = public_key.strip()

    # 已经是完整 PEM 时,只处理配置中可能存在的转义换行
    if "-----BEGIN" in public_key:
        return public_key.replace("\\n", "\n")

    # 清除裸 Base64 中的空白字符
    public_key = re.sub(r"\s+", "", public_key)

    return (
        "-----BEGIN PUBLIC KEY-----\n"
        + "\n".join(textwrap.wrap(public_key, 64))
        + "\n-----END PUBLIC KEY-----"
    )


alipay_public_key = normalize_alipay_public_key(alipay_public_key)
key = RSA.import_key(alipay_public_key)

每行严格保持 64 个字符通常不是解析成功的唯一条件,但按照 PEM 规范排版更稳妥,也更容易发现配置错误。

如果公钥保存在环境变量中,换行经常会被存成字面量 \n,可以先进行替换:

alipay_public_key = alipay_public_key.replace("\\n", "\n")
key = RSA.import_key(alipay_public_key)

解决方法二:将裸 Base64 解码为 DER

RSA.import_key() 也支持二进制 DER 数据:

import base64
import re
from Crypto.PublicKey import RSA

public_key_base64 = re.sub(r"\s+", "", alipay_public_key)
public_key_der = base64.b64decode(public_key_base64, validate=True)

key = RSA.import_key(public_key_der)

这种写法不需要拼接 PEM 头尾。设置 validate=True 后,如果公钥正文包含非法 Base64 字符或数据不完整,程序会更早报错。

兼容多种公钥格式

如果配置中既可能出现完整 PEM,也可能出现裸 Base64,可以统一封装导入逻辑:

import base64
import re
from Crypto.PublicKey import RSA


def import_alipay_public_key(public_key: str):
    value = public_key.strip().replace("\\n", "\n")

    if value.startswith("-----BEGIN"):
        return RSA.import_key(value)

    base64_body = re.sub(r"\s+", "", value)
    der_data = base64.b64decode(base64_body, validate=True)
    return RSA.import_key(der_data)


key = import_alipay_public_key(alipay_public_key)

验签时还要检查这些问题

公钥成功加载只能说明密钥格式正确,不能保证验签一定成功。还需要检查以下内容:

  • 使用支付宝公钥,不要误用应用公钥或应用私钥。
  • 沙箱环境和正式环境应使用各自对应的支付宝公钥。
  • sign_type 必须与签名算法一致。常见的 RSA2 对应 SHA-256 with RSA。
  • 待验签参数应排除 sign 和 sign_type。具体拼接规则应交给 SDK 处理,或严格遵循支付宝接口规则。
  • 不要擅自对回调参数进行 URL 解码、字符替换、JSON 重排或空格清理,否则可能改变原始签名内容。
  • 从配置文件或环境变量读取公钥时,要检查多余引号、全角符号、转义字符以及内容截断等问题。
  • 如果使用支付宝证书模式,应采用与证书模式匹配的 SDK 配置和支付宝公钥证书,不能直接把证书内容当作普通裸公钥替换。

可以通过下面的代码检查加载结果:

key = import_alipay_public_key(alipay_public_key)

print(key.has_private())  # 支付宝公钥应为 False
print(key.size_in_bits()) # 用于检查是否解析出了预期长度的 RSA 密钥

不要在日志中输出完整公钥配置、私钥或回调签名原文。支付宝公钥虽然不是私钥,但生产日志中仍没有必要暴露完整的安全配置。

备注:内容仅供参考。