BLAKE2哈希算法的压缩函数设计
好的,我们先看题目描述。BLAKE2 是一种广泛使用的、速度很快的密码学哈希函数,它是 SHA-3 竞赛入围算法 BLAKE 的改进版。BLAKE2 家族包含 BLAKE2b(输出 512 位,适用于 64 位平台)和 BLAKE2s(输出 256 位,适用于 32 位平台)。它的核心创新和性能优势很大程度上来源于其压缩函数的设计,该函数结合了 HAIFA 迭代结构和一个类 ChaCha 的核心排列。
下面,我将循序渐进地讲解 BLAKE2 压缩函数的设计细节。
第一步:理解压缩函数在整个哈希过程中的位置
- 回顾哈希函数一般结构:像 SHA-256 一样,大多数哈希函数采用 Merkle-Damgård 迭代结构或其变体。BLAKE2 采用的是 HAIFA 迭代结构,它是对 Merkle-Damgård 的加强,引入了额外的输入(如消息位长度、盐值等)来抵抗一些特定攻击。
- 压缩函数的作用:无论采用何种迭代结构,其“发动机”都是一个压缩函数。它的任务是:接收一个固定大小的 内部状态(或叫链接变量) 和一个 消息分组,进行一轮复杂的混合运算,输出一个新的、与输入大小相同的内部状态。
- BLAKE2 的输入输出:对于 BLAKE2b,压缩函数
F定义为:
F(h, m, t, f)→h'h:输入的状态向量,8个 64 位字(总共 512 位)。m:输入的消息块,16个 64 位字(总共 1024 位)。t:一个 2 字的计数器,表示当前已处理的消息字节总数。这是 HAIFA 结构的关键,增加了位置敏感性。f:一个标志字,如果这是最后一个消息块,则置为一个特定值(类似 MD/SHA 系列的填充指示位)。
第二步:压缩函数的初始化——构建初始向量 V
压缩函数的第一步不是直接操作输入的状态 h,而是先构建一个更大的 16 字(对于 BLAKE2b 是 16×64 位)的工作向量 V。
- V 的低 8 个字(V[0..7]):直接复制自输入状态
h[0..7]。这部分代表了哈希计算的“当前进度”。 - V 的第 8 到 15 个字(V[8..15]):这是 BLAKE2 设计的一个巧妙之处,它直接植入了算法的一些常量、计数器
t和标志f,将它们与核心运算深度绑定,而不是简单地预处理或后处理。V[8..11]:设置为四个初始化常量IV[0..3]。这些常量与 SHA-512 的初值类似,由圆周率 π 的小数部分平方根生成,确保“无后门”。V[12..13]:设置为计数器t的两个字。t[0]是低位,t[1]是高位。这使得压缩函数处理每个消息块的上下文都不同(因为t在增长),极大地增强了算法的安全性。V[14..15]:设置为标志f的两个字。对于中间块,f = 0;对于最后一个块,f = (0xFFFFFFFFFFFFFFFF)(全1)。这使得压缩函数能明确感知到这是不是最后一个块。
通过这种方式,消息、计数器、标志和初始常量在运算的一开始就与当前状态 h 并置在同一个向量 V 中。
第三步:核心运算——类 ChaCha 的 G 函数轮运算
这是 BLAKE2 压缩函数的核心和性能关键。它大量借鉴了流密码 ChaCha 的设计思想,使用一系列简单、高效的 ARX(加法、循环移位、异或)操作。
-
G 函数:这是最小的混合单元。一次
G函数调用会混合V中的 4 个字,并用到消息m中的 2 个字。对于 BLAKE2b,G函数操作 64 位字,定义如下:# 伪代码表示 def G(v, a, b, c, d, x, y): v[a] = (v[a] + v[b] + x) & 0xFFFFFFFFFFFFFFFF # 加法,模 2^64 v[d] = rotate_right(v[d] ^ v[a], 32) # 异或和循环右移(对BLAKE2b是32位) v[c] = (v[c] + v[d]) & 0xFFFFFFFFFFFFFFFF v[b] = rotate_right(v[b] ^ v[c], 24) # 循环右移24位 v[a] = (v[a] + v[b] + y) & 0xFFFFFFFFFFFFFFFF v[d] = rotate_right(v[d] ^ v[a], 16) # 循环右移16位 v[c] = (v[c] + v[d]) & 0xFFFFFFFFFFFFFFFF v[b] = rotate_right(v[b] ^ v[c], 63) # 循环右移63位(这个63很重要,是BLAKE的特色)注意:
a, b, c, d是指向V向量中 4 个特定位置的索引。x, y是来自当前消息块m的两个字。这确保了消息数据被直接、高强度地混入状态中。- 运算包括模加、异或和循环移位。循环移位的位数(32, 24, 16, 63)是经过精心挑选的,以最大化扩散速度和实现非线性。
-
一轮(Round)运算:一轮运算由 8 次
G函数调用组成,覆盖V向量的所有 16 个字,并消耗消息块m的全部 16 个字(每个字被使用一次)。一轮中G函数的调用顺序(即a, b, c, d的索引选择)也经过特别设计,通常称为“排列”或“调度”。- 对于 BLAKE2b,典型的排列(列步和行步)确保状态矩阵的每一列和每一对角线上相邻的四个字都能被一个
G函数充分混合。
- 对于 BLAKE2b,典型的排列(列步和行步)确保状态矩阵的每一列和每一对角线上相邻的四个字都能被一个
-
总轮数:为了平衡安全与效率,BLAKE2 压缩函数执行 12 轮(BLAKE2b)或 10 轮(BLAKE2s)。这比其前身 BLAKE 的轮数(14/16轮)要少,但分析表明这已足够安全,并且带来了显著的性能提升。
第四步:压缩函数的最终输出
在执行完所有轮次后,16 字的工作向量 V 已经完成了与消息、计数器、标志的充分混合。现在需要从这个混合结果中,提取出新的 8 字内部状态 h'。
- 提取新状态:
- 新的状态
h'的前 8 个字(h'[0..7])通过一个简单的运算得到:
h'[i] = h[i] XOR V[i] XOR V[i+8],其中i从 0 到 7。
- 新的状态
- 为什么这样设计?
V[i]是初始状态h[i]与常量/计数器/标志经过多轮混合后的结果。V[i+8]是那些常量/计数器/标志自身与状态混合后的结果。- 异或(XOR) 操作确保了这是一个可逆的、无进位溢出的组合。它结合了旧状态和本轮运算的全部成果。
- 这种“旧状态 XOR 新向量”的构造模式,在密码学上被称为 Davies-Meyer 结构,是构建抗碰撞哈希函数的常用且被证明安全的方法。
总结与特性
让我们梳理一下 BLAKE2 压缩函数设计的精妙之处:
- HAIFA 结构集成:通过将计数器
t和标志f直接输入到工作向量V中,使其参与每一轮的运算,天然支持了 HAIFA 结构,增强了抵抗长度扩展攻击和某些多碰撞攻击的能力。 - ARX 设计哲学:基于加、循环移位、异或的
G函数在现代 CPU(尤其是 64 位和 SIMD 指令集)上运行效率极高,这是 BLAKE2 速度领先的关键。 - 宽管道(Wide Pipe):内部工作状态
V(1024位)是最终输出/输入状态h(512位)的两倍宽。这提供了强大的内部安全性余量。 - 深度消息注入:消息的每一个字,在每一轮中都被精确地使用一次,确保了消息比特的快速扩散。
- 精心挑选的常数:初始化常量、循环移位数、
G函数的调度顺序都经过密码学分析,旨在打破对称性、防止固定点、实现最优扩散。
因此,BLAKE2 压缩函数是一个将安全性证明(基于 HAIFA、Davies-Meyer 结构)、高性能实现(ARX 操作)和简洁优雅设计完美结合的典范。它在许多性能测试中超越 SHA-256 和 SHA-3,被广泛应用于文件校验、密码存储、区块链(如 Arweave)和许多需要高速哈希的场景中。