SHA-256 哈希算法中的 SHA-256-224 变体与截断输出详解
今天我们来深入探讨 SHA-256 哈希算法的一个变体:SHA-224。您可能知道 SHA-256 能输出 256 位的哈希值,但 SHA-224 输出的却是 224 位。这 224 位是从何而来?仅仅是简单地截断 SHA-256 的结果吗?其中涉及到哪些关键步骤以确保其安全性和独立性?我们来一步步拆解。
1. SHA-256-224 的本质与设计目标
SHA-224 是 SHA-2 哈希函数家族中的一员,与 SHA-256 使用相同的基础算法。其主要设计目标包括:
- 输出长度: 提供 224 位的哈希输出,以满足一些对安全性有一定要求但需要较短摘要的应用场景。
- 安全性继承: 继承 SHA-256 的核心结构与安全强度,但通过不同的初始化值和截断操作,使其成为一个独立的哈希函数,从而在需要不同输出长度的协议中避免潜在的冲突关联风险。
2. SHA-256-224 的核心处理流程
SHA-224 的内部处理与 SHA-256 完全一致,包括:
- 预处理: 对输入消息进行填充,使其长度为 512 位的整数倍。填充规则为:在消息末尾附加一个“1”比特,接着补“0”,直到长度模 512 等于 448,最后追加一个 64 位的消息长度(单位:比特)。这与 SHA-256 的填充规则完全相同。
- 消息分块: 将填充后的消息分割成 512 位的块 \(M^{(0)}, M^{(1)}, \dots, M^{(N-1)}\)。
- 压缩函数: 对每个消息块,执行 SHA-256 的压缩函数。这个函数涉及 64 轮运算,每轮使用轮常数 \(K_t\) 和消息调度表 \(W_t\) 来更新 8 个 32 位的工作变量 \(a, b, c, d, e, f, g, h\)。
3. SHA-224 区别于 SHA-256 的两个关键点
尽管内部处理一致,但 SHA-224 在初始化和最终输出阶段与 SHA-256 有显著不同。
关键点一:不同的初始哈希值 \(H^{(0)}\)
SHA-256 使用 8 个 32 位的初始哈希值 \(H_0^{(0)}\) 到 \(H_7^{(0)}\),它们来源于前 8 个素数平方根的小数部分前 32 位。
SHA-224 则使用不同的初始化值,其设计意图是与 SHA-256 的初始状态“去关联”。这 8 个初始值计算如下:
- 计算 SHA-256 对特定 ASCII 字符串的哈希值。这个字符串是 “
sha224” 吗?不,这是一个更复杂、更不直观的种子。实际上,它是用 SHA-256 对字符串 “SHA-224” 进行哈希,然后将得到的 256 位哈希值作为初始值的基础。具体来说:- 计算 SHA-256(“
SHA-224”) 得到一个 256 位的哈希值。 - 将这个 256 位的结果等分成 8 个 32 位的字。
- 将这些字取为 SHA-224 的 8 个初始工作变量 \(a_0, b_0, c_0, d_0, e_0, f_0, g_0, h_0\)。
- 计算 SHA-256(“
- 但有一个至关重要的步骤: 为了避免与原始的 SHA-256 初始化产生任何简单的数学关系,设计者对这个从哈希推导出的初始状态又进行了一个小的、确定的、非线性的“搅动”,以确保其独立性和无后门。最终使用的初始哈希值 \(H^{(0)}\) 是:
\[H_0^{(0)} = 0xc1059ed8, \quad H_1^{(0)} = 0x367cd507, \quad H_2^{(0)} = 0x3070dd17, \quad H_3^{(0)} = 0xf70e5939 \]
\[ H_4^{(0)} = 0xffc00b31, \quad H_5^{(0)} = 0x68581511, \quad H_6^{(0)} = 0x64f98fa7, \quad H_7^{(0)} = 0xbefa4fa4 \]
> **注意**: 这与 SHA-256 的初始化值(如 0x6a09e667, 0xbb67ae85 等)完全不同。通过使用不同的、看似随机的常数,SHA-224 在算法层面就被定义为一个独立的函数,即使它与 SHA-256 共享相同的核心引擎。
关键点二:最终的截断与输出
这是 SHA-224 名称的由来,也是最直观的差异步骤。
- 完成压缩: 在处理完所有消息块后,我们会得到最终的工作变量组合,形成 256 位的中间结果。
- 截断操作: 我们丢弃这个 256 位结果中的最后 32 位。更准确地说,我们丢弃最低有效位所在的 32 位,或者说,我们只取高 7 个 32 位字(224位)。
- 输出连接: 将剩余的 7 个 32 位字(224 位)按照从最高有效字到最低有效字的顺序拼接起来,就得到了最终的 SHA-224 哈希值。
用公式表达,假设处理完最后一个消息块后,得到的 256 位最终状态为 \(H_0^{(N)} \parallel H_1^{(N)} \parallel \dots \parallel H_7^{(N)}\)(其中 \(H_0^{(N)}\) 是最高有效字)。
SHA-224 的输出为:
\[\text{SHA-224}(M) = H_0^{(N)} \parallel H_1^{(N)} \parallel H_2^{(N)} \parallel H_3^{(N)} \parallel H_4^{(N)} \parallel H_5^{(N)} \parallel H_6^{(N)} \]
\(H_7^{(N)}\) 被舍弃了。
4. 为什么要这样设计?——安全性考量
- 避免长度扩展攻击: 长度扩展攻击是一种针对 Merkle-Damgård 结构哈希函数的攻击。攻击者如果知道 Hash(Message1),即使不知道 Message1 的内容,也能计算出 Hash(Message1 || Padding || Message2)。如果 SHA-224 仅仅是截断 SHA-256 的结果,并且使用与 SHA-256 相同的初始值,那么攻击者就有可能利用 SHA-256 的完整 256 位内部状态信息来对 SHA-224 发动攻击。通过使用不同的初始值,SHA-224 的初始状态对攻击者是未知的,从而有效地阻断了这类攻击。
- 函数独立性: 不同的初始值确保了 SHA-224 和 SHA-256 是算法上独立的两个函数。即使未来在 SHA-256 上发现了弱点,这个弱点也不一定会直接、轻易地迁移到 SHA-224 上,反之亦然。
- 输出截断的合理性: 在密码学中,截断哈希输出是一种常见且被证明是安全的方法(前提是核心算法是安全的)。它提供了一种从强哈希函数派生出不同输出长度的、相互独立的哈希函数的简便且安全的方法。
5. 总结与回顾
SHA-224 的本质是一个带有特定初始化和输出截断的 SHA-256 算法。它的安全性依赖于 SHA-256 压缩函数的强度,并通过以下两步确保了其作为独立 224 位哈希函数的鲁棒性:
- 初始化去关联: 使用与 SHA-256 完全不同的初始哈希值,阻止了与 SHA-256 之间的简单转换和长度扩展攻击。
- 安全截断: 在处理完所有数据后,丢弃最终 256 位哈希值中的 32 位,输出剩余的 224 位。这是一种标准且安全的方式来获得更短的摘要。
因此,SHA-224 并非一个“简化版”的 SHA-256,而是一个巧妙地复用其强大核心引擎,并通过精心设计的初始化来获得独立性的、安全的 224 位哈希函数。