7.6.1 什么是Keccak 如前所述，Keccak 是一种被选定为SHA-3 标准的单向散列函数算法。 Keccak 可以生成任意长度的散列值，但为了配合SHA-2 的散列值长度，SHA-3 标准中共规定了SHA3-224、SHA-3-256、SHA3-384、SHA3-512 这4 种版本。在输入数据的长度上限方面，SHA-1 为264－1 比特，SHA-2 为2128－1 比特，而SHA-3 则没有长度限制。 此外，FIPS 202 中还规定了两个可输出任意长度散列值的函数（extendable-output function，XOF），分别为SHAKE128 和SHAKE256。据说SHAKE 这个名字取自Secure Hash Algorithm 与Keccak 这几个单词。 顺便一提，Keccak 的设计者之一Gilles Van Assche 在GitHub 上发布了一款名为KeccakTools的软件①。 ① https://github.com/gvanas/KeccakTools 7.6.2 海绵结构 下面我们来看一看Keccak 的结构。Keccak 采用了与SHA-1、SHA-2 完全不同的海绵结构（sponge construction）（图7-10）。 图7-10 海绵结构① ① 图7-10 和图7-11 是根据Keccak 官方网站（ http://sponge.neokeon.org ）中作者以Creative CommonsAttribution 授权协议发布的图制作而成的。 Keccak 的海绵结构中，输入的数据在进行填充之后，要经过吸收阶段（absorbing phase）和挤出阶段（squeezing phase），最终生成输出的散列值。 “海绵结构”这个名字听上去有点怪，请大家想象一下将一块海绵泡在水里吸水，然后再将里面的水挤出来的情形。同样地，Keccak 的海绵结构是先将输入的消息吸收到内部状态中，然后再根据内部状态挤出相应的散列值。 吸收阶段的流程如下。 ● 将经过填充的输入消息按照每r个比特为一组分割成若干个输入分组 ● 首先，将“内部状态的r个比特”与“输入分组1”进行 XOR，将其结果作为“函数f的输入值” ● 然后，将“函数f的输出值r个比特”与“输入分组2”进行XOR，将其结果再次作为“函数f 的输入值” ● 反复执行上述步骤，直到到达最后一个输入分组 ● 待所有输入分组处理完成后，结束吸收阶段，进入挤出阶段 函数f 的作用是将输入的数据进行复杂的搅拌操作并输出结果（输入和输出的长度均为b = r + c 个比特），其操作对象是长度为b = r + c 个比特的内部状态，内部状态的初始值为0。也就是说，通过反复将输入分组的内容搅拌进来，整个消息就会被一点一点地“吸收”到海绵结构的内部状态中，就好像水分被一点一点地吸进海绵内部一样。每次被吸收的输入分组长度为r 个比特，因此r 被称为比特率（bit rate）。 通过图7-10 我们可以看出，函数f 的输入长度不是r 个比特，而是r + c 个比特，请大家注意这一点，这意味着内部状态中有c 个比特是不受输入分组内容的直接影响的（但会通过函数f受到间接影响）。这里的c 被称为容量（capacity）。 吸收阶段结束后，便进入了挤出阶段，流程如下。 ● 首先，将“函数f的输出值中的r个比特”保存为“输出分组1”，并将整个输出值（r + c个比特）再次输入到函数f 中 ● 然后，将“函数f的输出值中的r个比特”保存为“输出分组2”，并将整个输出值（r + c个比特）再次输入到函数f 中 ● 反复执行上述步骤，直到获得所需长度的输出数据 无论是吸收阶段还是挤出阶段，函数f 的逻辑本身是完全相同的，每执行一次函数f，海绵结构的内部状态都会被搅拌一次。 挤出阶段中实际上执行的是“对内部状态进行搅拌并产生输出分组（r 个比特）”的操作，也就是以比特率（r 个比特）为单位，将海绵结构的内部状态中的数据一点一点地“挤”出来，就像从海绵里面把水分挤出来一样。 在挤出阶段中，内部状态r + c 个比特中的容量（c 个比特）部分是不会直接进入输出分组的，这部分数据只会通过函数f 间接影响输出的内容。因此，容量c 的意义在于防止将输入消息中的一些特征泄漏出去。 7.6.3 双工结构 作为海绵结构的变形，Keccak 中还提出了一种双工结构（图7-11）。 图7-11 双工结构 在海绵结构中，只有将输入的消息全部吸收完毕之后才能开始输出，但在双工结构中，输入和输出是以相同的速率进行的。在双向通信中，发送和接收同时进行的方式称为全双工（fullduplex），Keccak 的双工结构也代表同样的含义。 通过采用双工结构，Keccak 不仅可用于计算散列值，还可以覆盖密码学家的工具箱中的其他多种用途，如伪随机数生成器、流密码、认证加密、消息认证码等。 7.6.4 Keccak 的内部状态 刚才我们介绍了Keccak 中b = r + c 个比特的内部状态是如何通过函数f 进行变化的，下面我们来深入地看一看内部状态。 Keccak 的内部状态是一个三维的比特数组，如图7-12 所示。图中的每个小方块代表1 个比特，b 个小方块按照5×5×z 的方式组合起来，就成为一个沿z 轴延伸的立方体。 我们将具备x、y、z 三个维度的内部状态整体称为state，state 共有b 个比特。 如果我们只关注内部状态中的两个维度，可以将xz 平面称为plane，将xy 平面称为slice，将yz 平面称为sheet（图7-13）。 图7-12 Keccak 的内部状态（state）① 图7-13 Keccak 的部分内部状态 ① 图7-12～图7-17 是根据Keccak 官方网站（ http://sponge.neokeon.org ）中作者以Creative Commons Attribution授权协议发布的图制作而成的。 同样地，如果我们只关注其中一个维度，可以将x 轴称为row，将y 轴称为column，将z轴称为lane。 因此，我们可以将state 看成是由5×5 = 25 条lane 构成的，也可以看成是由与lane 的长度相同数量的slice 堆叠而成的。 Keccak 的本质就是实现一个能够将上述结构的state 进行有效搅拌的函数f，这与分组密码设计中的搅拌过程非常相似。此外，由于内部状态可以代表整个处理过程中的全部中间状态，因此有利于节约内存。Keccak 用到了很多比特单位的运算，因此被认为可以有效抵御针对字节单位的攻击。 7.6.5 函数Keccak-f [b ] 下面我们来看一看负责对内部状态进行搅拌的函数f。Keccak 的函数f 实际上应该叫作Keccak-f [b]，从这个名称可以看出，这个函数带有一个参数b，即内部状态的比特长度。这里的参数b 称为宽度（width）。 根据Keccak 的设计规格，宽度b 可以取25、50、100、200、400、800、1600 共7 种值，SHA-3 采用的是其中的最大宽度，即b = 1600。宽度b 的7 种取值的排列看起来好像有点怪，其实这7 个数字都是25 的整数倍，即25 的1（=20）倍、2（=21）倍、4（=22）倍、8（=23）倍、16（=24）倍、32（=25）倍和64（=26）倍。根据图7-13 可知，一片slice 的大小为5×5 = 25 个比特，因此b25就相当于slice 的片数（即lane 的长度）。SHA-3 的内部状态大小为b = 5×5×64 = 1600个比特。 由此可见，在Keccak 中，通过改变宽度b 就可以改变内部状态的比特长度。但无论如何改变，slice 的大小依然是5×5，改变的只是lane 的长度而已，因此Keccak 宽度的变化并不会影响其基本结构。Keccak 的这种结构称为套娃结构，这个名字取自著名的俄罗斯套娃，每个娃娃的形状都是相同的，只是大小不同而已。利用套娃结构，我们可以很容易地制作一个缩水版Keccak模型并尝试对其进行破解，以便对该算法的强度进行研究。 Keccak-f [b] 中的每一轮包含5 个步骤：θ（西塔）、ρ（柔）、π（派）、χ（凯）、ι（伊欧塔），总共循环12 + 2ℓ 轮①。具体到SHA-3 中所使用的Keccak-f [1600] 函数，其循环轮数为24 轮。 ① Keccak 的设计规格中规定：2ℓ = b25。 步骤θ 图7-14 所示为对其中1 个比特应用步骤θ 时的情形，这一步的操作是将位置不同的两个column 中各自5 个比特通过XOR 运算加起来（图中的Σ 标记），然后再与置换目标比特求XOR并覆盖掉目标比特。 图7-14 步骤θ 步骤ρ 图7-15 所示为应用步骤ρ 时的情形，这一步的操作是沿z 轴（lane 方向）进行比特平移。 图7-15 步骤ρ 步骤π 图7-16 所示为对其中1 片slice 应用步骤π 时的情形，实际上整条lane 上的所有slice 都会被执行同样的比特移动操作。 图7-16 步骤π 步骤χ 图7-17 所示为对其中1 个row 应用步骤χ 时的情形。这里我们使用了一些逻辑电路中的符号，其中代表对输入比特取反，即NOT ； 代表仅当两个输入比特均为1 时则输出1，即AND。 图7-17 步骤χ 步骤ι 步骤ι 是用一个固定的轮常数对整个state 的所有比特进行XOR 运算，目的是让内部状态具备非对称性。 根据《散列函数SHA-224、SHA-512/224、SHA-512/256 和SHA-3（Keccak）的实现评估》[Sakiyama]，除了步骤θ 中的奇偶性处理（Σ 标记）以及步骤χ 中的NOT 和AND 以外，其余的操作仅通过硬件电路就都可以实现。 7.6.6 对Keccak 的攻击 Keccak 之前的单向散列函数都是通过循环执行压缩函数的方式来生成散列值的，这种方式称为MD 结构（Merkle-Damgård construction）。MD4、MD5、RIPEMD、RIPEMD-160、SHA-1、SHA-2 等几乎所有的传统单向散列函数算法都是基于MD 结构的。 当初之所以开始征集SHA-3 算法，就是因为针对当时广泛使用的SHA-1 算法已经出现了理论上可行的攻击方法。为了规避SHA-1 的风险，SHA-2 应运而生，但SHA-2 依然是基于和SHA-1 相同的MD 结构，针对SHA-1 的攻击方式很有可能也会适用于SHA-2，问题没有得到根本解决。Keccak 则采用了和MD 结构完全不同的海绵结构，因此针对SHA-1 的攻击方法对Keccak 是无效的。 到目前为止，还没有出现能够对实际运用中的Keccak 算法形成威胁的攻击方法。 7.6.7 对缩水版Keccak 的攻击竞赛 由于Keccak 具备套娃结构，其实现中包含对轮处理的多次迭代，因此我们很容易实现一个强度较低的“缩水版Keccak”。通过设计一个比实际作为SHA-3 标准运用的Keccak 强度低一些的版本，并尝试对其进行攻击，就可以据此来评估实际运用的标准版Keccak 的强度。 Keccak 的设计者还举办了名叫Keccak Crunchy Crypto Collision and Pre-image Contest① 的相关“竞赛”，内容就是对缩水版的Keccak 进行攻击。在竞赛中使用的缩水版Keccak 比标准版减少了迭代轮数，参赛者可以通过改变宽度b 等各种方法来进行攻击。 Keccak 被选为SHA-3 标准的其中一个原因就是“结构清晰，易于分析”。这个原因似乎有点违背常识，因为“易于分析”也就表示“容易找到弱点”。而一个容易找到弱点的算法为什么会被选为SHA-3 呢？其实，正是因为我们可以比较容易地分析缩水版的Keccak，也就能够比较容易地对实际运用的标准版算法的强度进行评估，而作为一个将在全世界广泛使用的单向散列函数算法，“易于分析”可以说是一个十分优秀的特性。 ① http://keccak.noekeon.org/crunchy_contest.html