3、哈希去重原理:哈希函数基础,MD5/SHA-1/SHA-256对比,哈希碰撞与解决方案

哈希去重,说白了就是给每条数据算一个「指纹」。指纹相同,数据就重复。这个思路简单,但背后的门道不少。我最早接触哈希去重是在做日志清洗的时候,几亿条日志用字符串比对根本跑不动,换成哈希后速度直接起飞。但后来也踩过坑——嗯,今天咱们就把这些坑一个个填上。

3.1 哈希函数基础

哈希函数是个数学映射。输入任意长度的数据,输出固定长度的摘要。这个摘要就是哈希值。

好的哈希函数有几个关键特性:

  • 确定性:同样的输入,永远得到同样的输出。
  • 快速计算:几微秒内算出结果。
  • 雪崩效应:输入改一个比特,输出大变样。
  • 抗碰撞性:很难找到两个不同输入产生相同输出。

举个例子,你输入「hello」和「Hello」,哈希值会完全不同。这就是雪崩效应在起作用。

我的习惯:在去重场景里,我一般把原始数据和哈希值一起存。万一哈希碰撞了,还能用原始数据做二次校验。别只存哈希,那是给自己挖坑。

3.2 MD5 / SHA-1 / SHA-256 对比

这三个是哈希去重里最常见的算法。我一个个说。

MD5

MD5 输出 128 位,32 个十六进制字符。计算速度极快,每秒能算几百万次。但它的安全性早就被攻破了。2004 年就有研究者找到了碰撞。

我的建议:MD5 只能用在非安全场景,比如内部测试、临时去重。生产环境?别用。

SHA-1

SHA-1 输出 160 位,40 个十六进制字符。比 MD5 慢一点,但也没慢多少。2017 年 Google 公开了 SHA-1 碰撞实例,两个不同的 PDF 文件产生了相同的 SHA-1 值。

避坑指南:我曾经在一个旧系统里看到 SHA-1 做数据指纹,当时觉得问题不大。后来数据量上亿后,真撞上了。排查了两天才定位到是哈希碰撞。从那以后,我所有新项目都只用 SHA-256。

SHA-256

SHA-256 输出 256 位,64 个十六进制字符。属于 SHA-2 家族。目前没有公开的碰撞攻击。计算速度比前两个慢,但对去重场景来说完全够用。

为什么慢反而好?你想想看,攻击者想伪造碰撞也更难。安全性和性能之间,我选安全性。

算法 输出长度 安全性 速度 推荐场景
MD5 128 位 / 32 字符 低(已破解) 极快 非安全场景、内部测试
SHA-1 160 位 / 40 字符 中(已破解) 快 不推荐新项目使用
SHA-256 256 位 / 64 字符 高(未破解) 中等 生产环境、数据去重
核心结论:数据去重场景,直接上 SHA-256。别纠结。MD5 和 SHA-1 的碰撞风险不值得你赌。

3.3 哈希碰撞与解决方案

哈希碰撞,就是两个不同的数据算出了相同的哈希值。理论上,只要输出长度有限,碰撞就必然存在。这叫「鸽巢原理」——256 位的输出只有 2^256 种可能,但输入是无限的。

实际中碰撞概率有多低?SHA-256 的碰撞概率大约是 2^-128。这个数字有多大?你每秒算 10 亿次,算上 100 亿年,碰撞概率还是微乎其微。

但「微乎其微」不等于「零」。在超大规模数据场景下,比如百亿级数据去重,碰撞还是有可能发生的。

解决方案一:双重哈希

用两种不同的哈希算法算两次。比如 SHA-256 + BLAKE2。两个哈希值都相同才判定为重复。碰撞概率直接降到两个算法碰撞概率的乘积。

// 双重哈希示例
String sha256 = DigestUtils.sha256Hex(data);
String blake2 = Blake2b.Digest.newInstance(256).digest(data);
// 只有两个哈希都匹配,才认为是重复数据
if (sha256.equals(existingSha256) && blake2.equals(existingBlake2)) {
    // 重复数据
}

解决方案二:哈希 + 原始数据校验

哈希值相同后,再比对原始数据。这是最稳妥的方案。代价是多存一份原始数据或原始数据的部分特征。

我的做法:在去重系统里,我通常存「哈希值 + 数据长度 + 数据前 64 字节」。哈希碰撞后,先比对长度,再比对前 64 字节。99.99% 的假阳性在这一步就被过滤掉了。

解决方案三:使用更长的哈希

SHA-512 或者 SHA-3。输出越长,碰撞概率越低。代价是存储空间和计算时间增加。

注意:不要以为用了 SHA-256 就万事大吉。如果你的去重系统涉及安全审计、法律合规,必须做碰撞检测和容错处理。我曾经见过一个金融系统因为哈希碰撞导致两条不同交易被判定为重复,少结算了一笔钱。那场面,啧。

3.4 哈希去重流程总览

下面这张图展示了哈希去重的完整流程。从原始数据到哈希计算,再到碰撞检测和去重决策。

哈希去重核心流程 原始数据 计算 SHA-256 哈希 查询哈希索引表 ? 碰撞检测 + 二次校验 标记为重复数据 确认为新数据,入库 丢弃或标记重复 说明:碰撞检测分支只在哈希索引表中已存在相同哈希值时触发,正常情况走右侧分支

流程其实不复杂。数据进来,算哈希,查索引。哈希不存在就直接入库。哈希存在了,走碰撞检测分支。检测通过才是真重复,否则就是碰撞,按新数据处理。

我的经验:哈希索引表一定要加唯一索引。数据库层面做一层保障。代码里判断重复后,插入时如果报唯一键冲突,说明有并发问题,需要重试或加锁。别问我怎么知道的——线上事故教会的。