第二章 哈希算法与消息摘要:MD5、SHA家族、HMAC在接口校验中的应用
说到接口加密,我第一个想到的其实不是AES或者RSA这些复杂的算法,而是哈希。
为什么?因为我们做接口测试,最常遇到的一个场景就是验签。服务端给你返回一串数据,外加一个签名。你得自己算一遍这个签名,看看跟人家给的对不对得上。这个签名用到的底层技术,就是哈希算法。
说白了,哈希就是一种“指纹”技术——你给它任意长度的数据,它给你算出一个固定长度的摘要。这个摘要,就可以代表原始数据。
2.1 消息摘要的基本概念
消息摘要,也叫数字摘要。它的核心特性就三个:
- 固定长度输出:不管原文是1个字符还是1个G,算出来的摘要长度都一样。
- 不可逆:从摘要你没法反推出原文。这其实也是哈希被称为“单向函数”的原因。
- 抗碰撞:很难找到两个不同的输入,能得到同样的输出。
为什么接口测试要用它?
因为我们需要一种机制来验证“数据在传输过程中有没有被篡改”。哈希正好干这个。你发送数据时算一次摘要,别人收到数据时再算一次。两个摘要一比对,就知道数据是不是原装的。
我在项目中见过很多新人,上来就用哈希算法去加密敏感字段,比如密码。这其实是误用。哈希不是加密,它不负责保护数据机密性,只负责数据完整性。密码存数据库用的是哈希的不可逆特性,那是另外一个故事了。
| 特性 | 含义 | 接口测试中的价值 |
|---|---|---|
| 固定长度 | 输出长度不依赖输入 | 签名域段长度可预测,便于协议解析 |
| 不可逆 | 无法从摘要还原原文 | 签名本身不泄露敏感信息 |
| 抗碰撞 | 不同输入几乎不会产生同样输出 | 唯一标识数据,防止抵赖 |
| 高效计算 | 硬件级别优化,性能远优于非对称加密 | 高并发场景下不影响接口响应时间 |
2.2 MD5:经典但已淘汰
MD5,全称Message Digest Algorithm 5。它曾是应用最广的哈希算法,现在已经不推荐用在安全场景了。
我职业生涯早期,很多接口签名用的都是MD5。后来有一次做安全测试,我拿一个彩虹表去撞库,发现5分钟内就能找到几个碰撞实例。那之后,我在公司内部推行了一个规范:所有新接口签名,严禁使用裸MD5。
警告:MD5已经能被轻易碰撞。2004年,中国山东大学的王小云教授团队就公布了MD5的碰撞攻击方法。2017年,谷歌更是公开了SHA-1的两个碰撞实例。MD5的情况更糟。
但也不是完全不能用。我个人的习惯是:MD5只用于非安全场景的校验,比如数据分片后的快速比对、消息去重、或者对日志内容做防重复索引。
2.3 SHA家族:现在的主力
SHA(Secure Hash Algorithm)是美国国家标准与技术研究院发布的一系列哈希算法。目前市面上主要是SHA-1和SHA-2家族。
- SHA-1:输出160位(20字节)。跟MD5一样,已经被找到碰撞攻击方法。不建议在新项目中使用。
- SHA-256 / SHA-384 / SHA-512:属于SHA-2家族。输出长度不同,安全性逐级递增。目前是业界标准。
为什么SHA-256比MD5安全?说白了,就是位长更长、轮数更多、设计更复杂。但我建议你直接记住结论:SHA-256可以满足当前几乎所有接口验签场景。如果上游厂商要求用SHA-256,你不用犹豫,直接用。
| 算法 | 输出位数 | 安全性等级 | 适用场景 |
|---|---|---|---|
| MD5 | 128 bit | 已攻破 | 非安全校验、去重、分片 |
| SHA-1 | 160 bit | 已攻破 | 不推荐 |
| SHA-256 | 256 bit | 安全 | 接口签名、证书、完整性校验 |
| SHA-512 | 512 bit | 极高 | 高安全级别或合规要求场景 |
2.4 HMAC:带密钥的哈希
你可能会问:既然哈希这么强大,我的接口签名直接用SHA-256(消息体)不就行了吗?
不够。如果你直接用算法名+消息体,那任何人只要知道你的算法,就能算出跟你一模一样的签名。没有密钥保护,签名就失去了意义。
所以有了HMAC(Hash-based Message Authentication Code)。它把密钥跟消息搅在一起,再通过哈希算法计算,输出一个基于密钥的消息认证码。
你在接口文档里经常看到这样的签名公式:sign = HmacSHA256(secret, requestBody)。这,才是真正实用的签名方式。
我想强调的实战细节:
HMAC的计算顺序和格式,不同厂商定义不同。我遇到过最坑的一次,某厂商要求先拼接key=value&key=value字符串,再对这个字符串做HmacSHA256。但另一家却要求先对value做一次MD5,再做HMAC。所以不要只看算法名称,一定要看清楚“待签名的内容到底是什么”。
来看一个HmacSHA256的代码示例,接口测试中经常用到:
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class HmacUtil {
public static String hmacSha256(String data, String key) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
mac.init(secretKey);
byte[] rawHmac = mac.doFinal(data.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(rawHmac);
}
}
如果是Python,更简洁:
import hmac
import hashlib
import base64
def hmac_sha256(data: str, key: str) -> str:
sig = hmac.new(key.encode(), data.encode(), hashlib.sha256).digest()
return base64.b64encode(sig).decode()
2.5 接口校验中的实战经验
在接口测试时,哈希的使用场景主要有三个:
- 请求签名:客户端用HMAC+密钥,对请求参数或请求体做签名,放在header里。服务端收到后用同样的密钥和签名逻辑验一次。
- 响应完整性:服务端返回数据时,附带签名字段。客户端拿到后计算签名,确认响应没被篡改。
- 回调通知签名:第三方支付/撮合平台异步通知时,参数都拼成字符串再做签名验签。这个坑特别多,字段顺序、空值处理、编码都得对齐。
避坑提醒:我曾经在测试一个支付回调接口时,发现对方的文档里写着“对所有参数按key升序排列,拼接成key1=value1&key2=value2,再做MD5”。
结果实际跑通后,验签一直失败。最后排查发现,他们的MD5是对小写字符串计算的,而我试了默认大小写。细节决定成败,在接口签名测试里,大小写、编码(UTF-8/GBK)、空值处理、排序顺序,每一个都是潜在的问题点。
2.6 哈希与MAC的区别:一张图看懂
很多人搞不清哈希和MAC的区别。一句话:哈希是算法,MAC是认证机制。MAC通过哈希算法加密钥实现,所以叫HMAC。
这个图的逻辑很简单:原始消息加上密钥,一起进HmacSHA256算法,算出MAC。服务端和客户端各自算一遍,比对结果。不匹配,就说明消息在传输过程中被动过手脚。
我总结的三个关键点:
- 密钥共识:双方事先约定好密钥,密钥必须保密。
- 摘要长度:HmacSHA256输出32字节,相比MD5的16字节,安全余量大很多。
- 时序一致:验签时,被签名的内容(比如时间戳+请求体+随机数)必须跟签名时完全一致。我在项目中见到的最常见错误就是时间戳精度不一致,导致验签失败。
2.7 哈希与加密的区别(测试人员必须分清)
很多测试同学问我:哈希是不是加密的一种?
不是,完全两回事。
| 维度 | 哈希(Hash) | 加密(Encryption) |
|---|---|---|
| 是否可逆 | 不可逆 | 可逆(通过解密) |
| 输出长度 | 固定 | 通常与输入长度相关 |
| 是否需要密钥 | 不需要(裸哈希)或需要(HMAC) | 必须 |
| 目的 | 完整性校验、身份认证 | 机密性保护 |
| 接口测试场景 | 签名验签、密码验证 | 传输敏感数据、字段脱敏 |
一个实用的测试技巧:
我习惯在自动化测试框架里加一个“签名工具类”,把常见的HMAC、SHA-256、MD5都封装到一起。这样无论是做正向验签,还是模拟篡改数据看服务端反应,都能一键调用。你可以把这个工具类的代码放到公共代码库,团队所有人都能用,避免重复造轮子。
嗯,这一章的内容就到这里。哈希算法听起来简单,但实际用起来坑不少。记住我上面讲的几个避坑点:密钥保护、签名内容一致性、算法选择(能用SHA-256别用MD5,能用HMAC别用裸哈希)。