在 O(n) 的复杂度内反转 SHA-256 sigma0 函数?
Posted
技术标签:
【中文标题】在 O(n) 的复杂度内反转 SHA-256 sigma0 函数?【英文标题】:Reverse SHA-256 sigma0 function within complexity of O(n)? 【发布时间】:2021-06-10 22:20:57 【问题描述】:简介
作为 SHA-256 散列算法的一部分,为了方便起见,有一个函数通常被称为 σ1
或 sigma0
。基本上,它将 X 作为输入,其中 X 是 32 位无符号值。然后像这样转换它:
ROTATE_RIGHT(X, 7) ^ ROTATE_RIGHT(X, 18) ^ SHIFT_RIGHT(X, 3)
一点解释,如果需要的话:
ROTATE_RIGHT(X, Y) - 将 X 的位向右旋转 Y SHIFT_RIGHT(X, Y) - 将 X 的位向右移动 Y,因此结果的前 Y 位始终为 0另外,如果您需要代码,这里是 Python 的完整版本:
def rotate_right(x, y):
return (((x & 0xffffffff) >> (y & 31)) | (x << (32 - (y & 31)))) & 0xffffffff
def shift_right(x, n):
return (x & 0xffffffff) >> n
def sigma0(x):
return rotate_right(x, 7) ^ rotate_right(x, 18) ^ shift_right(x, 3)
反转功能
我开始怀疑那个东西是否可逆,令我惊讶的是,很快就编写了一个函数,通过给定sigma0
的输出,返回该函数的输入,或者简单地说,反转sigma0
函数。我不会把代码放在这里,因为它是用 Node.js 编写的,并且由于通过掩码搜索特定 sigma0
输入的更复杂的需求进行了很多修改,但我想给你一个关于我如何解决的基本概念它,所以也许你可以启发我一些关于如何实现我需要的新想法。
我的解决方案很简单,但也是递归的。我们知道每个输出的位都是两个或三个输入位的异或运算的结果。所以我做了一个依赖表,这样我就可以看到输出的位是如何受到输入的影响的:
I: 00,01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31
R7 25,26,27,28,29,30,31,00,01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24
R18 14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,00,01,02,03,04,05,06,07,08,09,10,11,12,13
S3 zz,zz,zz,00,01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28
---------------------------------------------------------------------------------------------------
O: 00,01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31
这是怎么回事?比如说,在输出的第一位我们有 1。为方便起见,我将其写为O[0]
,O[1]
,...O[31]
,所以O[x]
是输出的第 (x+1) 位.输入相同,标记为I
。
所以,O[0] == 1
。在上表中,我们看到O[0]
是I[25]
和I[14]
异或运算的结果。这意味着只有一个输入位必须为 1。所以此时我们可以说我们可以为输入创建两个合适的掩码:
##############0#########1#######
##############1#########0#######
这些面具至少对我来说是解决方案的关键。 #
表示任何值(0 或 1)。当我们创建掩码时,我们为下一位调用递归函数,但保留掩码。如果我们没有可能适合先前掩码的掩码,则先前的掩码没有解决方案,如果我们达到第 32 位,则保证掩码中没有锐利,这就是答案。
首先,我需要告诉你这个东西是有效的。但是在 Node.js 上,它会计算大约 100 毫秒的每个值,我不知道我的递归算法的最差复杂度是什么,因为它很难测量。它不满足我,我想尽办法解决这个 O(n)。
问题
我想知道是否有可能在 O(n) 的复杂度内编写一个反转 sigma0
的函数,其中 n
是输入/输出中的位数,它等于 32,没有递归,掩码或树木,简单而快速。
我还没有为我的陈述得出任何数学证明,但是我测试了很多不同的值,我可以自信地声称输入值的数量等于这个函数的输出值的数量,并且两者都等于2^32 - 1
。换句话说,对于每个输出,sigma0
函数的可能输入只有一个。
这让我想到,sigma0
原始函数产生的结果复杂度为 O(n),这意味着反向函数必须有一个也适用于 O(n) 的解决方案。
如果你在数学上证明我这是不可能的,我也会接受这个答案,但我没有发现任何表明这项任务不可能的东西。
消耗资源的解决方法
如果我有 16gb 的空闲内存,我可以将所有可能的值预先计算到文件中,然后将它作为一个巨大的数组加载到内存中。但这不是一个解决方案,因为还有其他 3 个类似的功能,要为所有这些功能做到这一点,我需要 64gb 的内存,这对于这个简单的任务来说太贵了。
UPD:高斯消元法
感谢 Artjom B. 的评论,我找到了一种通过高斯消元法求解 XOR 方程的好方法。目前我正在尝试解决这样的矩阵:
Input: 00000000100110101000111011101001
Output: 01110001101010000010010011100110
0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 | 0
1: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 | 1
2: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 | 1
3: 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 | 1
4: 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 | 0
5: 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 | 0
6: 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 | 0
7: 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 | 1
8: 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 | 1
9: 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 | 0
10: 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 | 1
11: 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 | 0
12: 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 | 1
13: 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 | 0
14: 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 | 0
15: 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 | 0
16: 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 | 0
17: 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 | 0
18: 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | 1
19: 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | 0
20: 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | 0
21: 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 | 1
22: 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 | 0
23: 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 | 0
24: 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 | 1
25: 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 | 1
26: 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 | 1
27: 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 | 0
28: 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 | 0
29: 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 | 1
30: 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 | 1
31: 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 | 0
发布了矩阵,这样您就可以看到它的外观,而不会浪费您自己的时间来创建它。解决与否后,我会更新我的问题。
【问题讨论】:
“这让我想到 sigma0 原始函数产生复杂度为 O(n) 的结果这一事实意味着反向函数必须有一个也适用于 O(n) 的解。”这根本不是真的。有许多函数在一个方向上是简单的,但它们的逆函数却不是。这是大多数现代密码学的基础。您正在陈述 P = NP 的更具侵略性的版本,就好像它是给定的,而不是复杂性理论的重大问题之一。没有已知有效逆的加密哈希的全部意义。 无论如何,这个问题与 Stack Overflow 无关,因为它与特定的实现无关。它可能与 crypto.stackexchange.com 相关,但如果您认为您已经设计了 SHA-256 的有效反转,假设您还没有。回顾您的工作,您似乎已经证明,您可以在给定任意时间或任意内存的情况下找到散列的原像(这是众所周知的事实,并且对于任何散列函数都得到了简单的证明)。 你似乎已经发现是什么让它如此不可逆转。扭转各个阶段是非常困难的。目前没有证据表明不可能逆转这些阶段。每个人都希望找到这样的证据。没有人知道如何扭转它,但人们普遍认为它是不可逆转的。 (至于为什么离题,*** 与编程问题有关。您所描述的内容与编程的任何特定方面无关。这是密码学的一般问题。)请参阅***.com/help/on-topic 您是否真的尝试过在 GF(2) 上使用高斯消元法? math.stackexchange.com/questions/169921/… @orlp 感谢您的回答。虽然 IMO 这个问题最初并不适合 Stack Overflow,但我认为以详细而强大的实现形式给出的答案不仅仅是将其引向主题(并暗示这个问题也可能很好)。撤回接近投票。 【参考方案1】:如果我们将sigma0
视为 GF(2)32 向量上的函数,您会注意到它是线性的。 GF(2)32 中的加法只是二进制 XOR:
>>> sigma0(235 ^ 352124)
2045075788
>>> sigma0(235) ^ sigma0(352124)
2045075788
这意味着如果我们可以找到sigma0(x0) = 0b1
、sigma0(x1) = 0b10
等,我们可以轻松地逐位反转任何内容。我们可以通过z3
轻松找到这些逆:
import z3
def z3_sigma0(x):
return z3.RotateRight(x, 7) ^ z3.RotateRight(x, 18) ^ z3.LShR(x, 3)
s = z3.Solver()
xs = [z3.BitVec(f"xi", 32) for i in range(32)]
for i in range(32):
s.add(z3_sigma0(xs[i]) == (1 << i))
print(s.check())
m = s.model()
for i in range(32):
print("x:02 = 0x:08x".format(i, m[xs[i]].as_long()))
这会立即输出:
sat
x00 = 0x185744e9
x01 = 0x30ae89d2
x02 = 0x615d13a4
x03 = 0xdaed63a1
x04 = 0x9cd03a8e
x05 = 0x08fdcc39
x06 = 0x11fb9872
x07 = 0x23f730e4
x08 = 0x5fb92521
x09 = 0xbf724a42
x10 = 0x57ee6948
x11 = 0xafdcd290
x12 = 0x76b358ec
x13 = 0xf531f531
x14 = 0xc36917ae
x15 = 0xb78f9679
x16 = 0x4615d13e
x17 = 0x947ce695
x18 = 0x19a4740f
x19 = 0x2b1facf7
x20 = 0x4e681d07
x21 = 0x84877ee7
x22 = 0x385344eb
x23 = 0x70a689d6
x24 = 0xf91a5745
x25 = 0xc36917af
x26 = 0xb78f967b
x27 = 0x4615d13a
x28 = 0x8c2ba274
x29 = 0x290afdcd
x30 = 0x4a42bf73
x31 = 0x94857ee6
因此我们可以使用它来制作我们的反演函数:
sigma0_singleton_inverses = [
0x185744e9, 0x30ae89d2, 0x615d13a4, 0xdaed63a1, 0x9cd03a8e, 0x08fdcc39,
0x11fb9872, 0x23f730e4, 0x5fb92521, 0xbf724a42, 0x57ee6948, 0xafdcd290,
0x76b358ec, 0xf531f531, 0xc36917ae, 0xb78f9679, 0x4615d13e, 0x947ce695,
0x19a4740f, 0x2b1facf7, 0x4e681d07, 0x84877ee7, 0x385344eb, 0x70a689d6,
0xf91a5745, 0xc36917af, 0xb78f967b, 0x4615d13a, 0x8c2ba274, 0x290afdcd,
0x4a42bf73, 0x94857ee6
]
def inv_sigma0(x):
r = 0
for i in range(32):
if x & (1 << i):
r ^= sigma0_singleton_inverses[i]
return r
确实:
>>> def test_inv_once():
... r = random.randrange(2**32)
... return inv_sigma0(sigma0(r)) == r
>>> all(test_inv_once() for _ in range(10**6))
True
上面可以写成完全无环无分支:
def inv_sigma0(x):
xn = ~x
r = (((xn >> 0) & 1) - 1) & 0x185744e9
r ^= (((xn >> 1) & 1) - 1) & 0x30ae89d2
r ^= (((xn >> 2) & 1) - 1) & 0x615d13a4
r ^= (((xn >> 3) & 1) - 1) & 0xdaed63a1
r ^= (((xn >> 4) & 1) - 1) & 0x9cd03a8e
r ^= (((xn >> 5) & 1) - 1) & 0x08fdcc39
r ^= (((xn >> 6) & 1) - 1) & 0x11fb9872
r ^= (((xn >> 7) & 1) - 1) & 0x23f730e4
r ^= (((xn >> 8) & 1) - 1) & 0x5fb92521
r ^= (((xn >> 9) & 1) - 1) & 0xbf724a42
r ^= (((xn >> 10) & 1) - 1) & 0x57ee6948
r ^= (((xn >> 11) & 1) - 1) & 0xafdcd290
r ^= (((xn >> 12) & 1) - 1) & 0x76b358ec
r ^= (((xn >> 13) & 1) - 1) & 0xf531f531
r ^= (((xn >> 14) & 1) - 1) & 0xc36917ae
r ^= (((xn >> 15) & 1) - 1) & 0xb78f9679
r ^= (((xn >> 16) & 1) - 1) & 0x4615d13e
r ^= (((xn >> 17) & 1) - 1) & 0x947ce695
r ^= (((xn >> 18) & 1) - 1) & 0x19a4740f
r ^= (((xn >> 19) & 1) - 1) & 0x2b1facf7
r ^= (((xn >> 20) & 1) - 1) & 0x4e681d07
r ^= (((xn >> 21) & 1) - 1) & 0x84877ee7
r ^= (((xn >> 22) & 1) - 1) & 0x385344eb
r ^= (((xn >> 23) & 1) - 1) & 0x70a689d6
r ^= (((xn >> 24) & 1) - 1) & 0xf91a5745
r ^= (((xn >> 25) & 1) - 1) & 0xc36917af
r ^= (((xn >> 26) & 1) - 1) & 0xb78f967b
r ^= (((xn >> 27) & 1) - 1) & 0x4615d13a
r ^= (((xn >> 28) & 1) - 1) & 0x8c2ba274
r ^= (((xn >> 29) & 1) - 1) & 0x290afdcd
r ^= (((xn >> 30) & 1) - 1) & 0x4a42bf73
r ^= (((xn >> 31) & 1) - 1) & 0x94857ee6
return r
最快的版本可能是这个版本,一次按 16 位分组,使用 2 × 216 大小的查找表(或类似的四个查找到 4 × 28 大小的表)。
sigma0_16bit_inverse_lo = [inv_sigma0(x) for x in range(2**16)]
sigma0_16bit_inverse_hi = [inv_sigma0(x << 16) for x in range(2**16)]
def fast_inv_sigma0(x):
return (sigma0_16bit_inverse_lo[x & 0xffff] ^
sigma0_16bit_inverse_hi[(x >> 16) & 0xffff])
【讨论】:
这太不可思议了。我要告诉你你的答案是多么不可思议。首先,我从来不知道 s0(x ^ y) = s0(x) ^ s0(y),现在感谢你。其次,我一直在互联网上搜索 z3 库之类的东西,从未怀疑过存在如此庞大而强大的工具。第三,您以 100% 可行的解决方案回答了我的问题,详细说明了每个步骤,值得单独感谢,因为我在昨天之前从未听说过 GF(2)。你应该得到超过 1 票。 @MaxSeid 不要忘记查看我更新的答案,底部的超快版本只有 1 个 XOR 和两次查找到价值约 500KiB 的查找表。 @MaxSeid 是的,z3
是一个非常强大的工具,有时很神奇,但它可以很快从“解决我的 z3 问题”变成“z3 对我的问题没用”,有时您在尝试求解器之前不知道它是哪个。如果您在矩阵上使用了高斯消元法并将它们逐行读取为二进制整数,您会发现与我使用 z3
找到的整数相同。
是的,我知道如果我用高斯消元法解决它,我会想出那些整数。问题是我以前从未听说过这种方法,我试图手动解决上面的矩阵但没有成功,只是答案中显示了它(math.stackexchange.com/questions/169921/…)。我只是相信一定有一些特殊的方法可以解决消除矩阵,但是我搜索了所有内容都提到了伽罗瓦域,这是一个复杂而庞大的主题,我至今没有在一天内设法学习。
@GauthamJ sigma0(a ^ b) = sigma0(a) ^ sigma0(b)
持有。当我们使用 XOR 作为加法时,它是线性的(就像在 GF(2^32) 中一样)。以上是关于在 O(n) 的复杂度内反转 SHA-256 sigma0 函数?的主要内容,如果未能解决你的问题,请参考以下文章
链表内指定区间反转(NC21/考察次数Top55/难度中等)
链表内指定区间反转(NC21/考察次数Top55/难度中等)
在优于 O(K*lg N) 的运行时间内反转保序最小完美散列函数