.NET Core 使用 AlipaySignature 验签返回 false
结论
RSACheckV1 返回 false,通常并非 .NET Core 本身有问题,而是验签所用的公钥不正确,或者传入验签方法的回调参数与支付宝原始通知不一致。
常见原因有两个:
- 使用了”应用公钥”,而不是支付宝平台提供的”支付宝公钥”。
- 回调参数经过实体映射、重新编码、裁剪或修改,导致待验签字符串发生变化。
排查步骤
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,也不能附带其他说明。
备注:内容仅供参考。
