同样是 uint32_t 乘法回绕——在 calloc 里是漏洞,在哈希函数里是设计。
区别只有一个:程序员是否知道它会发生。
把哈希函数想象成一台搅拌机:放进去一杯橙汁,搅出固定颜色的液体;放进去同一杯橙汁加一滴墨水,搅出的颜色和之前完全不同——不是"稍微深一点",而是彻底变了。它有三条关键性质:① 同样的材料每次搅出一样的颜色(确定性);② 材料差一点点,颜色天翻地覆(雪崩效应);③ 看着颜色,你没办法反推原来放了什么(单向性)。安全场景下最重要的是第②条。
如果输入差一点,输出也只差一点,攻击者可以:
取第一轮实际值,将 h 写成多项式形式:
第一项占 16~47 位,第二项占 0~39 位,重叠区间是 第 16~39 位,两项在此叠加混合。
↑ 橙色区域(位 39~16):A(原高16位)和 B(原低16位)的信息在此交叉叠加,互相影响
"折叠"是直觉描述:高位的信息没有消失,而是通过加法进位的方式"压入"了低32位。 这正是 uint32_t 比 uint64_t 更适合做哈希的原因——回绕强制把所有信息压缩进固定范围,同时保留了扩散效果。
| 场景 | 回绕是否预期 | 类型是否正确 | 行为定义 | 后果 |
|---|---|---|---|---|
|
calloc 乘法漏洞
count × sizeof(element_t)
|
❌ 意外 | ❌ 取决于实现 | 未定义 / 回绕 | 堆溢出 → RCE |
|
FNV-1a 哈希
h × 16777619
|
✅ 设计需求 | ✅ uint32_t 无符号 | C 标准定义 mod 2³² | 雪崩效应 → 安全哈希 |
无符号整数的回绕是 C 标准定义的 mod 2ⁿ 运算,行为完全可预测,可以作为算法设计的基础。
有符号整数溢出是未定义行为,任何依赖它的算法都是错误的。
区别不在于回绕本身,在于:程序员是否知道它会发生,并选对了类型。
关于 FNV-1a、哈希函数原理、整数安全,有任何问题都可以直接问。