Hash算法是如何保证在大概率的情况下不会出现冲突的

5 阅读6分钟

巨大的输出空间

大多数现代哈希算法(如 SHA-256)产生 256 位的输出。这意味着总共有 2^256 种可能的哈希值——这是一个天文数字(约 10^77)。现实世界中的数据量与之相比极其微小,所以随机碰撞的概率极低。

雪崩效应

好的哈希算法具有“雪崩效应”:输入中哪怕只有 1 比特(一个二进制位)的变化,输出的哈希值中大约会有一半的比特位发生翻转。这导致哈希值在输出空间中是均匀分布的。没有“聚集”现象,也就不会让某些哈希值更容易被命中。

雪崩效应 (Avalanche Effect) :当输入发生微小的改变(例如,只翻转一个比特位,即把 0 变成 1 或 1 变成 0),输出的哈希值中,平均大约有一半(50%)的比特位会发生翻转

为什么是 50%?  如果是 0% 或 100%,说明函数是线性的或常数,特别容易推出来。50% 意味着改变是随机的、均匀的,且无法从输出的变化反推输入的变化

输入1: Hello World
哈希1: a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e

输入2: hello World
哈希2: f7e49140acb3b76501d2e9395a1a89faa855dbfd1b529caf7a9e1c87f7fec974

不同的比特位数: 129 / 256
变化比例: 50.39%   # 非常接近理想的 50%

确定性 + 伪随机性

相同的输入永远得到相同的输出,但不同的输入产生的输出看起来像独立、均匀的随机数。这使得我们可以用生日悖论来估算碰撞概率:

生日悖论:在一个仅有23人的群体中,至少有两个人生日相同的概率就超过了50% 计算“至少有两个人生日相同”的概率比较复杂,一个更简单的方法是计算它的对立事件——“所有人的生日都不同”的概率,然后用1减去这个值。

我们假设一年有365天,且每天出生的概率均等。

  1. 第一个人的生日:可以是365天中的任意一天,概率为 365/365
  2. 第二个人的生日:为了不与第一个人重复,他/她有 364 天可选,概率为 364/365
  3. 第三个人的生日:为了不与前两个人重复,他/她有 363 天可选,概率为 363/365
  4. 以此类推,直到第23个人,他/她有 343 天可选,概率为 343/365

那么,23个人生日都不同的概率是:
P(都不同) = (365/365) × (364/365) × (363/365) × ... × (343/365) ≈ 0.493

因此,至少有两个人生日相同的概率是:
P(至少两人相同) = 1 - P(都不同) ≈ 1 - 0.493 = 0.507

  • 对于 256 位哈希,要获得 50% 的碰撞概率,需要大约 2^128 次哈希计算。
  • 2^128 是一个远超当前全球总计算能力的数量级。换句话说,在你的有生之年,即使动用全世界的计算机,碰撞发生的概率也无限趋近于零。

抗碰撞设计(针对加密哈希)

对于加密哈希(如 SHA-256, SHA-3),算法经过专门设计以抵抗两类攻击:

  • 弱抗碰撞:给定一个哈希值 h(x),很难找到另一个 y 使得 h(y) = h(x)
  • 强抗碰撞:很难找到任意两个不同的 x 和 y,使得 h(x) = h(y)

弱抗碰撞

这个很难找到,并不是指的数学上找不到,而是在你我以及全人类目前拥有的计算能力下,即使耗尽宇宙寿命也算不出来

假设一个哈希函数 H,输入是任意长度的二进制数据,输出是 4 位(16 种可能) 给定一个哈希值 1010。若“一定能找到一个 y 使得 H(y)=1010

  1. 数学上: 正确。因为输入无限(0, 1, 00, 01, ...),而输出只有 16 种,根据鸽巢原理,每种输出都有无限多个原像。所以解一定存在
  2. 但实际中:如果 H 是密码学哈希,只能暴力尝试。平均需要尝试 2^3 = 8 次(对于 4 位),这很容易。但对于 SHA-256(256 位),你需要平均尝试 2^255 次,这远超宇宙原子总数

强抗碰撞

若定能找到两个不同的 x 和 y 使得 H(x)=H(y)

  • 数学上: 正确。因为输入空间(比如所有 5 位长的二进制串,共 32 种)大于输出空间(16 种),根据鸽巢原理,必然存在碰撞。
  • 实际中:对于 4 位哈希,暴力枚举 32 个输入就一定能找到。但对于 SHA-256,你需要枚举大约 2^128 个输入才能以 50% 概率找到一对碰撞,这同样做不到

实际应用中的处理方式

  • 安全场景(密码、数字签名) :依赖加密哈希的碰撞阻力。一旦发现算法有理论碰撞(如 MD5, SHA-1 已被攻破),就立即淘汰,升级到更强的算法(如 SHA-256, SHA-3)。

  • 非安全场景(哈希表、散列表) :冲突一定会发生(因为输出空间很小,比如 32 位)。此时不是靠算法避免冲突,而是用冲突解决机制来处理:

    • 链地址法:同一哈希值的元素存在一个链表里。
    • 开放寻址法:冲突后找下一个空槽位。
    • 等等等等....

MD5被攻破,恰恰不是因为它的“单向性”(不可逆)出了问题,而是它的“抗碰撞性”被找到了漏洞

简单来说,MD5依然“单向”,但你可以在不破坏单向性的情况下,人为制造出“两个不同输入,算出一个相同哈希”的情况。这就像两把不同的钥匙,恰好能打开同一把锁