4、哈希算法与数字签名:算法对比与签名验证

哈希算法在接口测试里,说得直白点,就是给数据打一个「数字指纹」。你传一段内容,算法算出一个固定长度的字符串。内容改一丁点,指纹就全变了。我刚开始做接口测试时,没太在意这个,后来有一次线上排查数据篡改问题,才发现没做哈希校验有多被动。这篇我带你理清几个常用算法的差异,以及签名验证到底在验证什么。

4.1 MD5:普及最广,但已不推荐

MD5 算是最出名的哈希函数了。输出 128 位,也就是 32 个十六进制字符。速度很快,但安全性嘛……我建议你别再拿它做安全校验了。

我曾经在维护一个老项目时,发现线上 RPC 接口用 MD5 加 timestamp 做鉴权。翻资料确认:2004 年就有 MD5 碰撞攻击演示,两个不同文件能算出相同 MD5 值。本质上相当于一把锁有很多把不同的钥匙都能打开,这就很危险了。

MD5 现在适合干什么?数据完整性校验——比如下载文件后比对 MD5,看传输过程中有没有被损坏。但涉及防伪造、防篡改的场景,我个人习惯直接跳过 MD5。

4.2 SHA-1:已经进入淘汰期

SHA-1 输出 160 位,安全性比 MD5 强一些。但 2017 年 Google 和 CWI 研究所展示了 SHAttered 攻击,两个不同 PDF 文件可以算出同一 SHA-1 值。我在当时的一个银行接口平台项目里,审计要求所有 SHA-1 证书必须替换成 SHA-256。你想想看,连金融监管都明令禁止了,咱们做测试的更应该主动避坑。

测试环境如果看到接口文档还写 SHA-1 签名,我个人习惯是直接问安全团队是否可以升级。毕竟你交付的测试用例一旦使用了不安全的算法,整个测试链条的保护强度就打折扣了。

4.3 SHA-256:现阶段的主流选择

SHA-256 是 SHA-2 家族里最常用的成员,输出 256 位。它的碰撞概率在可预见的未来几乎为零。很多 REST API 的签名方案,比如阿里云、腾讯云的 OpenAPI,底层签名字符串就是用 SHA-256 做哈希。

import hashlib

def sha256_demo(data: str) -> str:
    """SHA-256 哈希示例"""
    sha256 = hashlib.sha256()
    sha256.update(data.encode('utf-8'))
    return sha256.hexdigest()

# 尝试验证一下
raw = "method=POST&path=/api/order&timestamp=1697342100"
digest = sha256_demo(raw)
print(digest)
# 输出: a3f0b8c9d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7

我在项目中遇到过一种情况:对接方要求签名时先对参数排序,再用 key=value 拼接成字符串,最后用 SHA-256 取摘要。如果顺序搞错了,签名对不上,接口返回 403。这个细节,测试用例里一定要覆盖到。

4.4 HMAC:带密钥的哈希

HMAC(Hash-based Message Authentication Code)不是单纯哈希,它把密钥和消息混合后再哈希。说白了,它解决了一个问题:你只给我消息摘要,我不知道这是不是你本人算的。加上密钥之后,只有持有相同密钥的人才能生成和校验摘要。

HMAC 可以和多种哈希函数组合,常见的是 HMAC-SHA256。我在做移动端 API 鉴权时,签名方案基本都用 HMAC-SHA256。

import hmac
import hashlib

def hmac_sha256_demo(secret: str, message: str) -> str:
    """HMAC-SHA256 签名示例"""
    h = hmac.new(
        key=secret.encode('utf-8'),
        msg=message.encode('utf-8'),
        digestmod=hashlib.sha256
    )
    return h.hexdigest()

secret_key = "my_secret_"
msg = "GET\n/api/user\n1697342100"
sig = hmac_sha256_demo(secret_key, msg)
print(sig)
# 输出: b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7
我建议你在编写单元测试时,专门构造一个「密钥/消息已知」对,用官方实现算出一个固定签名,然后让被测代码输出和它比较。这样可以非常快发现拼接逻辑或编码问题。

4.5 数字签名验证机制

哈希算法和 HMAC 算是消息认证,而数字签名更进一步——它用非对称密钥对(公钥+私钥)来签名和验签。私钥签名,公钥验证。这样你不需要在多个服务之间共享一个密钥,只需要分发公钥就行。

典型的数字签名流程:

  1. 发送方对消息做哈希(比如 SHA-256),得到摘要
  2. 用私钥对摘要加密,生成签名
  3. 接收方收到消息+签名
  4. 接收方用同样的哈希算法算出本地摘要
  5. 用公钥对签名解密,比对两个摘要是不是一致

我在项目中遇到过一个坑:接收方在验签时,用的哈希算法和发送方不一样——发送方用 SHA-256,接收方用 SHA-1。结果签名永远验证失败。这种问题往往不是哪边代码有 bug,就是算法配置不一致造成的。测试时一定要把签名算法版本作为变量暴露出来。

我曾经在项目里直接 hashlib.sha256(key + message) 做 HMAC,后来发现有人能从时序攻击里还原部分密钥。正确的做法是用标准 hmac 库,它使用双哈希策略(inner + outer padding),恰好能防御这类攻击。测试是否用的标准 HMAC 实现,也应该成为集成测试的一个检查点。

4.6 算法对比与选型速查

下面这个表格是我日常选型时自己对照用的,也帮你列出来了:

算法输出长度安全性推荐用途使用注意
MD5128 位已破解文件完整性校验禁止用于安全签名
SHA-1160 位理论上不安全不建议新项目使用遗留系统迁移中
SHA-256256 位安全通用签名、证书、哈希推荐默认选项
HMAC-SHA256256 位安全(带密钥)API 请求签名密钥管理要严格
RSA/SHA-256256 位签名安全数字签名(非对称)私钥不可泄露

另外,我再补充一个 HMAC 和数字签名的对比表,方便你决定在什么场景用什么方案:

对比项HMAC数字签名
密钥类型对称密钥(收发双方相同)非对称密钥(私钥签名,公钥验签)
密钥分发难度需要安全通道共享密钥公钥可以公开分发
性能快(对称计算量小)相对慢(非对称计算量大)
典型场景内部微服务之间的鉴权对外接口、开放平台、证书
防否认(Non-repudiation)有限(密钥共享,双方都可生成)强(仅私钥持有者可生成)
我个人习惯:内部服务间通信首选 HMAC-SHA256,对外暴露的 OpenAPI 用 RSA/SHA-256 签名。选型对了,测试也能少踩很多坑。

好了,上面就是我对哈希算法和签名验证机制的整理总结。这部分知识在接口测试里几乎每天都在用到:你要检查签名是否正确、摘要是否一致、算法版本是否匹配。这些细节,我在实际项目中踩过的坑,也一并写在了这里。希望能帮你少走弯路。