PAYATHON 2026

.NET Core 使用 AlipaySignature 验签返回 false

支付老李

结论

RSACheckV1 返回 false,通常并非 .NET Core 本身有问题,而是验签所用的公钥不正确,或者传入验签方法的回调参数与支付宝原始通知不一致。

常见原因有两个:

  1. 使用了”应用公钥”,而不是支付宝平台提供的”支付宝公钥”。
  2. 回调参数经过实体映射、重新编码、裁剪或修改,导致待验签字符串发生变化。

排查步骤

1. 确认使用的是支付宝公钥

XCList[0].alipay_public_key 中必须保存支付宝开放平台为当前应用、当前密钥模式提供的”支付宝公钥”,不能误用以下内容:

  • 应用公钥
  • 应用私钥
  • 支付宝公钥证书对应的其他字符串
  • 沙箱环境中另一应用的公钥
  • 更换密钥前使用的旧公钥

如果应用采用证书模式,应调用 SDK 对应的证书验签方法,不能直接把证书内容作为普通公钥传给该方法。

调用中的最后一个参数是 false,通常表示第二个参数传入的是公钥字符串,而非公钥文件路径。其具体含义仍需以当前安装的支付宝 .NET SDK 版本为准。

2. 直接使用回调表单参数验签

不要先将通知反序列化为业务实体,再通过实体重新生成字典。实体可能遗漏字段,也可能改变空值、金额格式、时间格式或特殊字符。

在 ASP.NET Core 中,可以直接使用表单数据构造验签参数:

[HttpPost]
public async Task<IActionResult> AlipayNotify()
{
    if (!Request.HasFormContentType)
    {
        return Content("failure");
    }

    IFormCollection form = await Request.ReadFormAsync();

    var parameters = form.ToDictionary(
        item => item.Key,
        item => item.Value.ToString(),
        StringComparer.Ordinal
    );

    string alipayPublicKey = XCList[0].alipay_public_key;

    bool verified = AlipaySignature.RSACheckV1(
        new Dictionary<string, string>(parameters),
        alipayPublicKey,
        "utf-8",
        "RSA2",
        false
    );

    if (!verified)
    {
        return Content("failure");
    }

    // 验签成功后,再校验 app_id、seller_id、订单号和金额等业务字段。
    // 业务处理必须支持幂等,避免重复通知造成重复入账。

    return Content("success");
}

代码中额外创建了一份 Dictionary 副本,目的是防止某些 SDK 实现在验签时移除 sign、sign_type 等字段,进而影响后续业务处理。

3. 不要修改回调参数

验签前应保留支付宝通知参数的原始值,尤其不要进行以下操作:

  • 再次对参数执行 URL 编码或解码
  • 对字符串调用 Trim()
  • 将金额转换为 decimal 后再转回字符串
  • 改变时间格式
  • 将 + 错误地转换为空格
  • 删除看似无用的通知字段
  • 将空字符串改为 null
  • 使用 JSON 请求体代替实际的表单参数

ASP.NET Core 的 Request.Form 已经完成表单解析,通常可以直接使用其中的值,无需再次调用 UrlDecode。

4. 检查字符集和签名方式

传入的字符集和签名方式必须与支付宝通知中的设置一致:

"utf-8"
"RSA2"

RSA2 对应 SHA-256 签名。如果实际通知中的 sign_type 不是 RSA2,或者应用仍配置为其他签名方式,验签同样会失败。

可以从回调参数中读取签名类型:

string signType = parameters.TryGetValue("sign_type", out var value)
    ? value
    : "RSA2";

再使用实际的签名类型进行验签:

bool verified = AlipaySignature.RSACheckV1(
    new Dictionary<string, string>(parameters),
    alipayPublicKey,
    "utf-8",
    signType,
    false
);

缺少 sign_type 时是否默认使用 RSA2,需要根据应用的实际配置决定。

5. 检查公钥存储格式

如果公钥保存在数据库或配置文件中,应检查以下问题:

  • 是否带有多余的引号或空格
  • 换行符是否被保存为字面量 \n
  • 是否复制了其他环境的公钥
  • 更换密钥后是否仍在读取缓存中的旧值
  • 当前 SDK 要求纯 Base64 公钥内容还是 PEM 格式

例如,配置系统可能将换行符保存为字面量,此时需要按照实际格式还原:

string alipayPublicKey = XCList[0].alipay_public_key
    .Replace("\\r", "\r")
    .Replace("\\n", "\n")
    .Trim();

不要直接删除 -----BEGIN PUBLIC KEY----- 等内容。不同版本的 SDK 接受的公钥格式可能不同,应以当前版本的方法说明和实际配置为准。

建议记录的诊断信息

验签失败时,可以记录字段名、签名方式、应用编号和公钥指纹,但不要将完整公钥、私钥或完整支付通知写入公开日志。

可重点记录以下信息:

_logger.LogWarning(
    "Alipay signature verification failed. app_id={AppId}, sign_type={SignType}, fields={Fields}",
    parameters.GetValueOrDefault("app_id"),
    parameters.GetValueOrDefault("sign_type"),
    string.Join(",", parameters.Keys.OrderBy(x => x))
);

如果已经确认公钥正确,应优先比较”服务器实际收到的表单参数”与”传入 RSACheckV1 的字典”,检查中间环节是否丢失了字段或改写了参数值。

验签成功后仍需校验业务字段

签名通过只能说明通知内容来自对应密钥的持有方,并且内容没有被篡改。随后还要校验以下业务字段:

  • app_id 是否属于当前应用
  • seller_id 是否为预期收款方
  • out_trade_no 是否存在
  • total_amount 是否与订单金额一致
  • trade_status 是否属于需要处理的状态
  • 订单是否已经处理过

只有验签和业务校验均通过,并且订单处理成功后,才能向支付宝返回以下纯文本:

success

否则返回:

failure

响应内容不能是 JSON、HTML,也不能附带其他说明。

备注:内容仅供参考。