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 密钥
不要在日志中输出完整公钥配置、私钥或回调签名原文。支付宝公钥虽然不是私钥,但生产日志中仍没有必要暴露完整的安全配置。
备注:内容仅供参考。
