PAYATHON 2026

为什么支付网关需要使用 checksum?

支付小周

明确结论

支付网关使用 checksum,通常是为了检查请求中的关键字段在传输过程中有没有被修改,并验证请求是否来自合法商户。

不过,示例中的写法:

$checksum = $merchantid . "|" . $amount . "|" . $customerReferenceNo;
$checksum = hash('sha256', $checksum);

只是对公开字段进行普通的 SHA-256 哈希,没有使用商户密钥,因而无法可靠地验证请求来源,也不能阻止恶意篡改。如果攻击者能够修改 amount,也能根据修改后的数据重新计算 checksum。

这种 checksum 能提供多少安全保障,还要看支付网关是否使用了示例中没有展示的密钥、签名或其他验证机制。

支付网关如何验证 checksum

网关收到请求后,一般会按照约定的字段顺序重新拼接数据:

merchantid|amount|customerReferenceNo

再使用相同的算法计算哈希值:

$expectedChecksum = hash(
    'sha256',
    $merchantid . "|" . $amount . "|" . $customerReferenceNo
);

最后比较计算结果和请求中携带的 checksum:

if (hash_equals($expectedChecksum, $receivedChecksum)) {
    // checksum 匹配,继续处理请求
} else {
    // checksum 不匹配,拒绝请求
}

如果两个值不一致,原因可能包括:

  • 字段在传输过程中发生了变化;
  • 商户拼接字段的顺序有误;
  • 金额格式不一致;
  • 双方处理字符编码或空格的方式不同;
  • 请求被人为修改;
  • 双方使用的算法或密钥不一致。

比较签名或哈希值时,适合使用 hash_equals(),以免普通字符串比较泄露时序信息。

普通 SHA-256 checksum 的局限

SHA-256 是哈希算法,不是加密算法,也不能单独用于身份认证。

假设原始请求是:

merchant001|100.00|ORDER-123

对应的 checksum 为:

$checksum = hash(
    'sha256',
    'merchant001|100.00|ORDER-123'
);

如果攻击者将金额改为 1.00,也可以重新计算 checksum:

$checksum = hash(
    'sha256',
    'merchant001|1.00|ORDER-123'
);

整个计算过程不需要任何秘密信息,所以网关无法仅凭这个 checksum 判断它究竟由商户生成,还是由攻击者重新生成。

因此,单纯对公开参数执行:

hash('sha256', $data);

最多只能检查数据是否一致,不能作为可靠的防篡改或身份认证方案。

更常见的安全方案:HMAC

支付接口通常会给商户分配一个只有商户和网关知道的密钥,并使用 HMAC 生成签名:

<?php

$merchantId = 'merchant001';
$amount = '100.00';
$customerReferenceNo = 'ORDER-123';
$secretKey = 'your-secret-key';

$data = $merchantId . "|" . $amount . "|" . $customerReferenceNo;

$checksum = hash_hmac('sha256', $data, $secretKey);

网关收到请求后,会使用自己保存的商户密钥重新计算签名:

<?php

$data = $merchantId . "|" . $amount . "|" . $customerReferenceNo;

$expectedChecksum = hash_hmac('sha256', $data, $secretKey);

if (!hash_equals($expectedChecksum, $receivedChecksum)) {
    // 签名无效,拒绝请求
}

即使攻击者知道字段内容和 SHA-256 算法,只要不知道 secretKey,就无法为修改后的参数生成有效签名。

有些网关不用 HMAC,而是采用 RSA、ECDSA 等非对称数字签名。具体算法、字段顺序和编码方式必须以网关的接口文档为准,不能自行替换。

集成时应怎么做

先检查文档中是否还规定了以下内容:

  • secretKey、merchantKey、salt 或 signingKey;
  • checksum 是否需要包含密钥;
  • 是否要求使用 hash_hmac();
  • 是否提供公钥、私钥或证书;
  • 回调通知是否也需要验签;
  • checksum 的字段顺序和分隔符;
  • 输出格式是十六进制还是 Base64。

构造待签名字符串时,必须严格遵守文档规定。字段顺序、大小写、空值处理和金额格式只要有一处不同,最终结果就会完全不同。

例如,下面几个金额对应的字符串并不相同:

100
100.0
100.00
0100.00

虽然它们在业务上表示相同的金额,但计算出的 checksum 不同。金额应使用支付网关指定的固定格式,不要直接依赖浮点数的默认字符串转换。

回调通知也必须验证

checksum 不应只用于商户向支付网关发起请求。网关返回支付结果或调用异步通知地址时,商户同样需要验证响应签名。

典型流程如下:

  1. 接收网关回调;
  2. 按照文档规定提取并拼接字段;
  3. 使用密钥重新计算签名;
  4. 使用 hash_equals() 比较;
  5. 签名通过后,再查询并更新订单;
  6. 校验订单号、商户号、币种和金额;
  7. 对重复通知进行幂等处理。

不能仅凭浏览器跳转页面中的 status=success 判断支付成功,因为前端参数可能被修改。对于重要交易,最好再调用支付网关的订单查询接口,确认最终状态。

注意事项

checksum 或签名不能替代 HTTPS。HTTPS 用于保护传输通道,HMAC 或数字签名用于验证消息内容和发送方,两者解决的是不同问题。

还需要注意以下事项:

  • 不要将商户密钥放在前端 JavaScript、HTML 或移动端可提取的配置中;
  • 不要记录完整密钥、银行卡信息或其他敏感支付数据;
  • 签名应覆盖金额、订单号、商户号、币种等关键字段;
  • 如果协议支持,应加入时间戳和唯一随机值,降低请求被重放的风险;
  • 不要自行改变文档规定的 URL 编码、字符集或字段排序;
  • checksum 验证成功后,仍要检查订单金额和状态。

如果支付网关的完整文档确实只要求对公开字段进行普通 SHA-256 哈希,并且没有密钥、HMAC、数字签名或服务器端二次验证机制,那么这个 checksum 本身无法提供有效的身份认证。此时应向网关确认其安全设计,特别是攻击者修改金额并重新计算 checksum 时,系统如何阻止这类请求。

备注:内容仅供参考。