SHA256 字节顺序问题

问题描述 投票:0回答:1

对于一个项目,我想从头开始用 C 语言实现 SHA256。然而,规范here将小字节序和大字节序混合在一起,使事情变得不清楚。我已经编写了整个哈希过程,但我不断得到错误的结果。查看中间结果后,我注意到字节顺序存在重大问题(我在 CLion 上使用了内存监视)。我正在一台小端机上工作。

让我们有一个像“abc”这样的输入。根据规范,该输入应该被填充以获得 64 字节(512 位)块。首先,附加 1,然后清零,直到最后 64 位,这表示以位 (24) 为单位的消息长度。根据规范,这个 64 位块应该是大端字节序。

这是十六进制的填充块,采用小端字节序。较小的地址在顶部,较大的地址在底部。

   61 62 63 80   │ abc· │
   00 00 00 00   │ ···· │
   00 00 00 00   │ ···· │
   00 00 00 00   │ ···· │
   00 00 00 00   │ ···· │
   00 00 00 00   │ ···· │
   00 00 00 00   │ ···· │
   00 00 00 00   │ ···· │
   00 00 00 00   │ ···· │
   00 00 00 00   │ ···· │
   00 00 00 00   │ ···· │
   00 00 00 00   │ ···· │
   00 00 00 00   │ ···· │
   00 00 00 00   │ ···· │
   00 00 00 00   │ ···· │
   00 00 00 18   │ ···· │

字符串不受字节顺序的影响,因为它只影响多字节值。然而,规范随后继续声称必须定义初始哈希值(它们的值在规范中给出),但是它无法传达它们是小端还是大端——这至关重要,并且对结果:

H0 = 0x6a09e667
H1 = 0xbb67ae85
H2 = 0x3c6ef372
H3 = 0xa54ff53a
H4 = 0x510e527f
H5 = 0x9b05688c
H6 = 0x1f83d9ab
H7 = 0x5be0cd19

然后问题就开始了。有一个消息调度 (

uint32_t array[64]
),它可以在 512 位块中从 16 个 32 位字中生成 64 个 32 位字。其定义在规范第 6.2.2 节中。如下:

对于时间表的前16个字,它们只是从原始消息中复制的。那么第一个问题来了——如果要解释为32位数字,原始消息的第一行应该如何保存在schedule中?

应该是

0x61626380
还是
0x80636261
?这些数字非常不同。

前 16 个完成后,接下来的 48 个将被创建(操作在文档的 4.1.2 部分中指定):

for (i = 16; i <= 63; i++) {
    uint32_t s1 = (rightRotate(W[i-2], 17)) ^ (rightRotate(W[i-2], 19)) ^ (W[i-2] >> 10);
    uint32_t s0 = (rightRotate(W[i-15], 7)) ^ (rightRotate(W[i-15], 18)) ^ (W[i-15] >> 3);
    uint32_t final = (s1 + W[i-7] + s0 + W[i-16]);
    W[i] = final;
}

网站 https://sha256algorithm.com/ 允许您逐步执行此算法。它再次未能提供有关字节顺序的详细信息。例如,在这个网站中,w[16]应该是

01100001011000100110001110000000
。它与块的第一行相同。
0x61626380
。我的代码也这样做,但该值在我的调试器中显示为
0x80636261
w[0]
中的
w[16]
w[17]
应该是
00000000000011110000000000000000
。 0x00FF0000。然而,我的代码产生以下值:
00 0f 06 00
。这就是问题所在 - 字节顺序在生成单词 Schedule 期间会产生问题,因为即使对值进行求和也会根据其操作数的字节顺序而有所不同。

我的问题是这样的: 处理价值观的正确方法是什么?什么应该是大端,什么应该是小端,在没有设置顺序的情况下,一行字符应该如何转换为32位int?

下面我将提供截图。第一个展示了W[0]到W[17]如何保存在我的内存中,第二个展示了W[17]部分应该如何根据网站计算。

我的旋转定义如下:

uint32_t rightRotate(uint32_t value, unsigned int count) {
    return (value >> count) | (value << (32 - count));
}
c hash cryptography endianness sha
1个回答
0
投票

典型的 SHA-2 实现只有两种情况需要在字节和较大字之间转换数据。这些是将消息字节转换为消息字以及将状态字转换为散列输出时的情况。在这两种情况下,数据都以大端格式转换。

因此,对于消息“abc”,第一个未展开的消息字为

0x61626380
(带填充),其余均为0。处理结束时的第一个状态字为
0xba7816bf
,序列化为
0xba 0x78 0x16 0xbf 
.

常量的字节顺序无关紧要。它们始终是 32 位字,典型的实现只会将它们视为 32 位字,因此它们将始终按照本地计算机所处的字节顺序进行处理,因为它们永远不会存储为除 32 位字之外的任何内容。

如果您使用的是当今最常见的处理器之一(x86/x86-64、ARM/ARM64 或 RISC-V),那么这些通常是小端处理器,因此您的 32 位数量会很小-endian 存储在内存中时。这通常并不重要,除非您使用以字节形式转储此数据的调试器,在这种情况下它会向后查找。如果您使用

printf("%08x", state[0])
之类的工具或将调试器置于 32 位模式来转储数据,那么您将获得格式正确的 32 位输出,并且无需考虑处理器如何存储字节。我个人不使用调试器,所以这不会以任何方式影响我,但自从你使用以来,这是一个需要注意的事情。

所有这些也适用于 SHA-512 的 64 位字及其派生算法。大多数其他加密哈希函数的操作方式类似,只是其中一些(例如 BLAKE2)是小尾数法。

© www.soinside.com 2019 - 2024. All rights reserved.