【问题标题】:Meaning of "Length_Low" and "Length_High" in SHA256 (RFC 4634)?SHA256 (RFC 4634) 中“Length_Low”和“Length_High”的含义?
【发布时间】:2022-01-08 11:13:45
【问题描述】:

我今天在学习SHA2,到了我看不懂的代码的地方。

RFC 4634(其中定义了 SHA2)在 sha.h 文件中定义了两个变量 Length_LowLength_High

uint32_t Length_Low;                /* Message length in bits */
uint32_t Length_High;               /* Message length in bits */

例如,在sha224-256.c 文件中,变量Length_Low 在两个地方被主动更改。这里:

/*
 * add "length" to the length
 */
static uint32_t addTemp;
#define SHA1AddLength(context, length)                     \
    (addTemp = (context)->Length_Low,                      \
     (context)->Corrupted =                                \
        (((context)->Length_Low += (length)) < addTemp) && \
        (++(context)->Length_High == 0) ? 1 : 0)

这里:

/*
* Store the message length as the last 8 octets
*/
context->Message_Block[56] = (uint8_t) (context->Length_High >> 24);
context->Message_Block[57] = (uint8_t) (context->Length_High >> 16);
context->Message_Block[58] = (uint8_t) (context->Length_High >> 8);
context->Message_Block[59] = (uint8_t) (context->Length_High);
context->Message_Block[60] = (uint8_t) (context->Length_Low >> 24);
context->Message_Block[61] = (uint8_t) (context->Length_Low >> 16);
context->Message_Block[62] = (uint8_t) (context->Length_Low >> 8);
context->Message_Block[63] = (uint8_t) (context->Length_Low);

我了解 RFC 4634 和 SHA2 的原理。但是,作为 C 语言的初学者,我只能理解论文中 99% 的代码。我附上的代码片段就属于这剩下的1%。

您能否解释一下,Length_LowLength_High 变量在 SHA2 的实现中起什么作用?它们在代码层面的意义是什么?

其次,代码片段中发生了什么?我可以识别复合运算符和移位,但我对代码的难度感到不知所措——尤其是在第二个片段中,取消引用、递增、定义等发生在同一行代码中。

【问题讨论】:

  • 只是一个猜测,但我认为这两个变量是 64 位长度指示器的低 32 位和高 32 位。

标签: c pointers sha


【解决方案1】:

Meta:stackexchange,尤其是 stackoverflow,政策是不发布“代码”的图像,广泛定义为包括配置文件和日志或错误消息等内容,因为它们在移动设备上很难阅读,无法阅读由视障人士,不可剪切和粘贴,不可搜索。再加上你的,我认为你的 IDE 重新格式化和着色,在我看来非常丑陋。幸运的是,所有 RFC 都是以文本形式发布的,我可以很容易地替代它。

另外,顺便说一句:SHA-256 和 SHA-224(以及之前的 SHA-1 和 MD5)的输入块大小是 64 八位字节 或 512 位,而不是 64 位,无论如何与长度字段无关。长度字段确实是 64 位,并在代码中实现为高半和低半的两个 32 位变量。

static uint32_t addTemp;
#define SHA224_256AddLength(context, length)               \
  (addTemp = (context)->Length_Low, (context)->Corrupted = \
    (((context)->Length_Low += (length)) < addTemp) &&     \
    (++(context)->Length_High == 0) ? 1 : 0)

将一段输入数据的长度(实际上总是使用 8 表示完整的八位字节或 1-7 表示剩余位)添加到 2x32 位长度字段是相当棘手的代码。首先它将传入的Length_Low 保存在addTemp 中,然后从内到外:

(context)->Length_Low += (length) // call this CODE1

length 的值添加到结构中的Length_Low 字段中;因为这在 C 中使用无符号算术,如果结果(数学上)溢出,它会被环绕(取模 232)。因此,当且仅当发生溢出/环绕时,总和(Length_low 中的新值)小于 addTemp 中的原始值。这是经过测试的,在这种情况下,Length_High 字段会递增:

( CODE1 < addTemp) && (++(context)->Length_High == 0) // call this CODE2

如果 增加高半部分后为零,这意味着 it 也溢出/环绕,这意味着实际消息长度太大而无法容纳 64 -bit 字段,按照规范要求,所以这被认为是错误并存储在Corrupted 字段中,稍后将对其进行测试以报告哈希操作失败:

(addTemp=..., (context)->Corrupted = CODE2 ? 1 : 0)

应该注意? 1 : 0 在技术上是不必要的; C 中的 &amp;&amp;|| 运算符(以及比较/相等运算符,如 &lt;==)已经定义为返回 1 表示真,返回 0 表示假(尽管 测试if(x)while(x) 接受任何非零值为真)。然而,有些人觉得写出来更清楚,而且这种动机在 RFC 中尤其强烈,该 RFC 发布给广大受众,包括那些(像你一样!)对 C 知之甚少的人。

事实上,把它写成一个(非常小的)函数而不是一个宏可能会更好,这将允许使用更明显的语句而不是复杂的嵌套表达式,并且在 2006 年任何体面的编译器(更少now) 将内联和折叠以生成与宏相同的代码。但这个世界并不完美。

相比之下,您的第三块非常简单。它只取 2x32 位长度字段并将其存储为 8 个 8 位单元的大端序列,存储在当前 Message_Block 的最后 8 个元素中。

【讨论】:

  • 抱歉截图。我用代码片段交换了图片。
  • 感谢您的精彩回答!那么,规范不使用int64_tLL 后缀作为长度字段只是为了确保代码可以在尽可能多的机器上运行,这是否正确?或者,引入两个 32 位字段而不是一个 64 位的原因是什么?
  • 需要明确的是,规范是 cn1 或更高版本的 FIPS180-2,其相关内容在 RFC4634 的第 2-6 节中基本重复,并使用其自己的数学符号,而不是 C;只有第 8 节是 C。 算法 SHA-224 和 -256 明确设计为可以有效地完成“核心”操作(即 RFC 的第 6 节和代码中的SHA224_256ProcessMessageBlock)在 32 位 CPU 上,因为在 2006 年只有一小部分 CPU 是 64 位的,虽然 RFC 没有这么说,但我认为它使用 2x32 位的长度来保持这种在大多数 CPU 上运行的能力。 ...
  • ... 请注意 SHA384&512 的代码,因为算法是 64 位的,它支持在 32 位 CPU 上工作,代码更复杂且效率稍低;请参阅第 46 页上的 cmets。还有一点:[u]int64 类型,如果 C 实现完全有它们,则不一定使用LL 后缀;一些 C 实现选择将 [unsigned] long 设为 64 位,它们使用 LUL。但是长度字段没有任何文字值可以这样表达。
猜你喜欢
  • 1970-01-01
  • 2019-06-14
  • 2010-11-17
  • 1970-01-01
  • 2013-05-12
  • 2012-04-16
  • 2017-08-19
  • 1970-01-01
相关资源
最近更新 更多