回调被改金额,第一反应是把MD5换成RSA。先等等。你被伪造大概率不是MD5的锅,而是商户密钥泄露或者参数拼接不一致。还有人觉得RSA一定更安全,可一旦把公钥配置错,RSA验签反而更难排。搜「支付回调sign签名MD5和RSA区别」的人,真正想要的不是算法比较,是“我到底该改哪一行配置”。

表面区别:摘要和签名根本不是一回事

MD5不管加不加key,本质是生成一串128位摘要。它只能回答“这串参数有没有被改过”,不能回答“这串参数是谁发给我的”。支付通道里的MD5 sign,实际是把排序后的参数拼上商户密钥再算MD5。商户密钥双方共享,知道key的人都能算。RSA则不同,平台用私钥对参数摘要做签名,商户拿公钥验证,能确认这条回调确实来自持有私钥的平台。两者在回调里承担的信任模型完全不一样。

按常见易支付对接教程,sign_type=MD5时,sign本身、sign_type都不会参与待签名字符串。空值参数通常也不参与,金额保留两位小数。别看这些细节小,实际验签不过基本都是它们。

MD5回调验签:差一位都不行

MD5验签失败的原因很少是md5函数算错,而是你拼出来的待签名字符串和通道文档规定的不一致。如果你也遇到,先查参数排序和sign字段剔除,再看金额格式与编码,多半是待签名字符串拼接顺序和通道文档不一致。人民币金额最典型的坑是通道文档要求“元”为单位,你的程序传了“分”,或者反过来。

有些商户把“按参数名ASCII排序”理解成按字节排序,结果参数名里出现大写字母时顺序全错。还有中文参数需要先按UTF-8 URL编码,而你的语言默认用的是RFC1738,空格变成加号而不是%20,签名也算不对。这类问题在宝塔安装易支付报错处理时经常被误判成环境问题,其实异步通知已经正常返回,只是sign对不上。

一个常见的MD5签名拼接长这样:

$str = $sortedQuery . '&key=' . $merchantKey;
$sign = strtoupper(md5($str));

把sign从待签名参数里拿掉、把空值剔除、按文档排序,这三点做到后,大部分“MD5验签失败”就直接消失。如果回调都收不到,那问题不在这里,可以看《支付回调收不到?按这五步排查,十分钟搞定》。

RSA回调签名:安全边界在密钥管理,不在密钥长度

很多人以为RSA回调一定比MD5安全,其实RSA验签失败或被伪造,最常见的不是算法被攻破,而是公钥配错、证书过期、调试日志把公钥或签名串截断。RSA常用的回调签名算法是SHA256withRSA,平台用私钥对通知原文做签名,商户用平台公钥做验证。

在PHP里验签往往就是几行:

$pubKey = openssl_pkey_get_public($platformPublicKey);
$result = openssl_verify($payload, base64_decode($sign), $pubKey, OPENSSL_ALGO_SHA256);

这里的$payload就是签名原串,必须和平台签名时使用的原串完全一致。有些网关是把HTTP body原文拿去做签名,有些是把通知参数排序拼接后做签名,这两种搞错,验签结果直接不通过。

关键差异在“能不能验来源”

MD5加key仍然是共享密钥方案,平台和商户两边都知道同一个key。key一旦在代码仓库、接口日志、报错回显里泄露,第三方就能直接伪造回调通知、篡改金额。这正是 MD5 和 RSA 最本质的区别:MD5 只能验「有没有被改」,RSA 能验「是谁发的」。所以选型时别盯着密钥长度看,先想清楚你的回调场景到底需要「验完整性」还是「验来源」。对多数中小商户来说,MD5 加 key 配合 IP 白名单、回调重试和金额二次复核,已经足够;只有当回调涉及大额资金、且你无法保证密钥绝对不泄露时,才值得上 RSA。把这一层想明白,比纠结用哪个算法重要得多。