feat: add new translate
This commit is contained in:
@@ -1,19 +1,17 @@
|
||||
# 挑战七:AES ECB模式解密
|
||||
# 挑战7:ECB模式下的AES
|
||||
|
||||
## 题目描述
|
||||
[此文件](https://cryptopals.com/static/challenge-data/7.txt) 中经过Base64编码的内容已通过AES-128 ECB模式使用密钥加密:
|
||||
|
||||
使用AES-128-ECB模式解密给定的Base64编码数据。
|
||||
|
||||
**数据文件:** `7.txt`
|
||||
|
||||
该文件包含Base64编码的AES-ECB加密数据,例如:
|
||||
```
|
||||
CRIwqt4+szDbqkNY+I0qbDe3LQz0wiw0SuxBQtAM5TDdMbjCMD/venUDW9BL
|
||||
PEXODbk6a48oMbAY6DDZsuLbc0uR9cp9hQ0QQGATyyCESq2NSsvhx5zKlLtz
|
||||
dsnfK5ED5srKjK7Fz4Q38/ttd+stL/9WnDzlJvAo7WBsjI5YJc2gmAYayNfm
|
||||
...
|
||||
"YELLOW SUBMARINE"
|
||||
```
|
||||
|
||||
**密钥:** `YELLOW SUBMARINE`
|
||||
(区分大小写,不带引号;恰好16个字符;我喜欢"YELLOW SUBMARINE"因为它恰好16字节长,现在你也会喜欢的)。
|
||||
|
||||
**原始数据来源:** [https://cryptopals.com/static/challenge-data/7.txt](https://cryptopals.com/static/challenge-data/7.txt)
|
||||
解密它。毕竟,你知道密钥。
|
||||
|
||||
最简单的方法:使用OpenSSL::Cipher,将AES-128-ECB作为密码器。
|
||||
|
||||
## 用代码实现
|
||||
|
||||
你显然可以使用OpenSSL命令行工具解密这个,但我们让你在代码中实现ECB是有原因的。你后面会经常需要它,不仅仅是用来攻击ECB。
|
||||
|
||||
@@ -1 +1,17 @@
|
||||
# Challenge 9: Implement PKCS#7 padding
|
||||
# 挑战9:实现PKCS#7填充
|
||||
|
||||
分组密码将固定大小的块(通常是8或16字节)的明文转换为密文。但我们几乎从不想转换单个块;我们加密不规则大小的消息。
|
||||
|
||||
我们处理不规则大小消息的一种方法是通过填充,创建一个块大小的偶数倍的明文。最流行的填充方案称为PKCS#7。
|
||||
|
||||
所以:通过在块末尾附加填充字节数来将任何块填充到特定的块长度。例如:
|
||||
|
||||
```
|
||||
"YELLOW SUBMARINE"
|
||||
```
|
||||
|
||||
...填充到20字节将是:
|
||||
|
||||
```
|
||||
"YELLOW SUBMARINE\x04\x04\x04\x04"
|
||||
```
|
||||
|
||||
@@ -1 +1,15 @@
|
||||
# Challenge 10: Implement CBC mode
|
||||
# 挑战10:实现CBC模式
|
||||
|
||||
CBC模式是一种分组密码模式,允许我们加密不规则大小的消息,尽管分组密码本身只转换单个块。
|
||||
|
||||
在CBC模式中,每个密文块在下一次调用密码核心之前被添加到下一个明文块。
|
||||
|
||||
第一个明文块没有关联的前一个密文块,被添加到称为*初始化向量*(IV)的"伪第0个密文块"中。
|
||||
|
||||
通过获取你之前编写的ECB函数,让它*加密*而不是*解密*(通过解密你加密的任何内容来验证测试),并使用你在之前练习中的XOR函数来组合它们,手动实现CBC模式。
|
||||
|
||||
[这个文件](https://cryptopals.com/static/challenge-data/10.txt) 在使用"YELLOW SUBMARINE"和全为ASCII 0的IV(\x00\x00\x00等)进行CBC解密时是可读的(某种程度上)。
|
||||
|
||||
## 不要作弊
|
||||
|
||||
不要使用OpenSSL的CBC代码来做CBC模式,甚至不要用来验证你的结果。如果你不打算从中学习,那做这些东西有什么意义呢?
|
||||
|
||||
@@ -1 +1,20 @@
|
||||
# Challenge 11: An ECB/CBC detection oracle
|
||||
# 挑战11:ECB/CBC检测预言机
|
||||
|
||||
现在你有了ECB和CBC工作:
|
||||
|
||||
编写一个函数来生成随机的AES密钥;那就是16个随机字节。
|
||||
|
||||
编写一个在未知密钥下加密数据的函数——也就是说,一个生成随机密钥并在其下加密的函数。
|
||||
|
||||
函数应该看起来像:
|
||||
|
||||
```
|
||||
encryption_oracle(your-input)
|
||||
=> [MEANINGLESS JIBBER JABBER]
|
||||
```
|
||||
|
||||
在底层,让函数在明文*之前*附加5-10个字节(随机选择计数),在明文*之后*附加5-10个字节。
|
||||
|
||||
现在,让函数选择在1/2的时间内使用ECB加密,另一半时间使用CBC加密(每次CBC都使用随机IV)。使用rand(2)来决定使用哪个。
|
||||
|
||||
检测函数每次使用的分组密码模式。你最终应该得到一段代码,当指向一个可能使用ECB或CBC加密的黑盒时,告诉你正在发生哪种情况。
|
||||
|
||||
@@ -1 +1,15 @@
|
||||
# Challenge 14: Byte-at-a-time ECB decryption (Harder)
|
||||
# 挑战14:逐字节ECB解密(更难)
|
||||
|
||||
从[#12](challenge_12.html)获取你的预言机函数。现在生成随机数量的随机字节,并将此字符串前置到每个明文。你现在在做:
|
||||
|
||||
```
|
||||
AES-128-ECB(random-prefix || attacker-controlled || target-bytes, random-key)
|
||||
```
|
||||
|
||||
同样的目标:解密目标字节。
|
||||
|
||||
## 停下来思考一下
|
||||
|
||||
比挑战#12更难的是什么?你将如何克服这个障碍?提示是:你在使用你已经拥有的所有工具;不需要疯狂的数学。
|
||||
|
||||
想想"刺激"和"响应"。
|
||||
|
||||
@@ -1 +1,27 @@
|
||||
# Challenge 15: PKCS#7 padding validation
|
||||
# 挑战15:PKCS#7填充验证
|
||||
|
||||
编写一个函数,接受明文,确定它是否具有有效的PKCS#7填充,并剥离填充。
|
||||
|
||||
字符串:
|
||||
|
||||
```
|
||||
"ICE ICE BABY\x04\x04\x04\x04"
|
||||
```
|
||||
|
||||
...具有有效的填充,并产生结果"ICE ICE BABY"。
|
||||
|
||||
字符串:
|
||||
|
||||
```
|
||||
"ICE ICE BABY\x05\x05\x05\x05"
|
||||
```
|
||||
|
||||
...没有有效的填充,以下字符串也没有:
|
||||
|
||||
```
|
||||
"ICE ICE BABY\x01\x02\x03\x04"
|
||||
```
|
||||
|
||||
如果你使用有异常的语言编写,如Python或Ruby,让你的函数在错误填充时抛出异常。
|
||||
|
||||
密码学专家知道我们要去哪里。耐心点。
|
||||
|
||||
@@ -1 +1,38 @@
|
||||
# Challenge 16: CBC bitflipping attacks
|
||||
# 挑战16:CBC位翻转攻击
|
||||
|
||||
生成随机AES密钥。
|
||||
|
||||
结合你的填充代码和CBC代码来编写两个函数。
|
||||
|
||||
第一个函数应该接受任意输入字符串,前置字符串:
|
||||
|
||||
```
|
||||
"comment1=cooking%20MCs;userdata="
|
||||
```
|
||||
|
||||
...并附加字符串:
|
||||
|
||||
```
|
||||
";comment2=%20like%20a%20pound%20of%20bacon"
|
||||
```
|
||||
|
||||
函数应该转义";"和"="字符。
|
||||
|
||||
然后函数应该将输入填充到16字节AES块长度,并在随机AES密钥下加密它。
|
||||
|
||||
第二个函数应该解密字符串并查找字符";admin=true;"(或者,等价地,解密,用";"分割字符串,将每个结果字符串转换为2元组,并查找"admin"元组)。
|
||||
|
||||
根据字符串是否存在返回true或false。
|
||||
|
||||
如果你正确编写了第一个函数,应该*不*可能向其提供会生成第二个函数正在寻找的字符串的用户输入。我们必须破解密码才能做到这一点。
|
||||
|
||||
相反,修改密文(不知道AES密钥)来完成这个目标。
|
||||
|
||||
你依赖的事实是,在CBC模式中,密文块中的1位错误:
|
||||
|
||||
- 完全打乱发生错误的块
|
||||
- 在下一个密文块中产生相同的1位错误(/编辑)
|
||||
|
||||
## 停下来思考一下
|
||||
|
||||
在你实施这个攻击之前,回答这个问题:为什么CBC模式具有这个属性?
|
||||
|
||||
@@ -1 +1,46 @@
|
||||
# Challenge 17: The CBC padding oracle
|
||||
# 挑战17:CBC填充预言机
|
||||
|
||||
这是对现代分组密码密码学最著名的攻击。
|
||||
|
||||
结合你的填充代码和CBC代码来编写两个函数。
|
||||
|
||||
第一个函数应该从以下10个字符串中随机选择一个:
|
||||
|
||||
```
|
||||
MDAwMDAwTm93IHRoYXQgdGhlIHBhcnR5IGlzIGp1bXBpbmc=
|
||||
MDAwMDAxV2l0aCB0aGUgYmFzcyBraWNrZWQgaW4gYW5kIHRoZSBWZWdhJ3MgYXJlIHB1bXBpbic=
|
||||
MDAwMDAyUXVpY2sgdG8gdGhlIHBvaW50LCB0byB0aGUgcG9pbnQsIG5vIGZha2luZw==
|
||||
MDAwMDAzQ29va2luZyBNQydzIGxpa2UgYSBwb3VuZCBvZiBiYWNvbg==
|
||||
MDAwMDA0QnVybmluZyAnZW0sIGlmIHlvdSBhaW4ndCBxdWljayBhbmQgbmltYmxl
|
||||
MDAwMDA1SSBnbyBjcmF6eSB3aGVuIEkgaGVhciBhIGN5bWJhbA==
|
||||
MDAwMDA2QW5kIGEgaGlnaCBoYXQgd2l0aCBhIHNvdXBlZCB1cCB0ZW1wbw==
|
||||
MDAwMDA3SSdtIG9uIGEgcm9sbCwgaXQncyB0aW1lIHRvIGdvIHNvbG8=
|
||||
MDAwMDA4b2xsaW4nIGluIG15IGZpdmUgcG9pbnQgb2g=
|
||||
MDAwMDA5aXRoIG15IHJhZy10b3AgZG93biBzbyBteSBoYWlyIGNhbiBibG93
|
||||
```
|
||||
|
||||
...生成随机AES密钥(应该为所有未来的加密保存),将字符串填充到16字节AES块大小,并在该密钥下CBC加密它,向调用者提供密文和IV。
|
||||
|
||||
第二个函数应该消费第一个函数产生的密文,解密它,检查其填充,并根据填充是否有效返回true或false。
|
||||
|
||||
## 你在这里做什么
|
||||
|
||||
这对函数近似于在web应用程序中服务器端部署的AES-CBC加密;第二个函数模拟服务器对加密会话令牌的消费,就像它是一个cookie一样。
|
||||
|
||||
结果表明,可以解密第一个函数提供的密文。
|
||||
|
||||
这里的解密依赖于解密函数的侧信道泄漏。泄漏是填充有效或无效的错误消息。
|
||||
|
||||
你可以找到100个关于这个攻击如何工作的网页,所以我不会重新解释它。我要说的是:
|
||||
|
||||
这个攻击背后的基本洞察是,字节01h是有效的填充,并且在通过解密被篡改的密文产生的"随机化"明文的1/256试验中出现。
|
||||
|
||||
02h单独*不是*有效的填充。
|
||||
|
||||
02h 02h *是*有效的填充,但比01h随机出现的可能性要小得多。
|
||||
|
||||
03h 03h 03h更不可能。
|
||||
|
||||
所以你可以假设,如果你破坏了解密并且它有有效的填充,你就知道那个填充字节是什么。
|
||||
|
||||
容易被绊倒的是CBC明文被"填充"的事实。*填充预言机与CBC明文上的实际填充无关。*这是一个针对处理解密的特定代码位的攻击。你可以在*任何CBC块*上挂载填充预言机,无论它是否被填充。
|
||||
|
||||
@@ -1 +1,51 @@
|
||||
# Challenge 18: Implement CTR, the stream cipher mode
|
||||
# 挑战18:实现CTR流密码模式
|
||||
|
||||
字符串:
|
||||
|
||||
```
|
||||
L77na/nrFsKvynd6HzOoG7GHTLXsTVu9qvY/2syLXzhPweyyMTJULu/6/kXX0KSvoOLSFQ==
|
||||
```
|
||||
|
||||
...在CTR模式下解密为近似英语的内容,CTR是一种将AES分组密码转换为流密码的AES分组密码模式,参数如下:
|
||||
|
||||
```
|
||||
key=YELLOW SUBMARINE
|
||||
nonce=0
|
||||
format=64位无符号小端nonce,
|
||||
64位小端块计数(字节计数 / 16)
|
||||
```
|
||||
|
||||
CTR模式非常简单。
|
||||
|
||||
CTR模式不是加密明文,而是加密运行计数器,产生16字节的密钥流块,与明文进行XOR。
|
||||
|
||||
例如,对于具有这些参数的消息的前16字节:
|
||||
|
||||
```
|
||||
keystream = AES("YELLOW SUBMARINE",
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||
```
|
||||
|
||||
...对于下一个16字节:
|
||||
|
||||
```
|
||||
keystream = AES("YELLOW SUBMARINE",
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00")
|
||||
```
|
||||
|
||||
...然后:
|
||||
|
||||
```
|
||||
keystream = AES("YELLOW SUBMARINE",
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00")
|
||||
```
|
||||
|
||||
CTR模式不需要填充;当你用完明文时,你只需停止XOR密钥流并停止生成密钥流。
|
||||
|
||||
解密与加密相同。生成相同的密钥流,XOR,并恢复明文。
|
||||
|
||||
解密此函数顶部的字符串,然后使用你的CTR函数来加密和解密其他东西。
|
||||
|
||||
## 这是好代码中唯一重要的分组密码模式
|
||||
|
||||
大多数现代密码学依靠CTR模式将分组密码适配为流密码,因为我们想要加密的大部分内容更好地描述为流而不是块序列。Daniel Bernstein曾经对Phil Rogaway开玩笑说,好的密码系统不需要"解密"变换。像CTR这样的构造就是他所说的。
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
# Challenge 21: Implement the MT19937 Mersenne Twister RNG
|
||||
# Challenge 21: 实现 MT19937 Mersenne Twister 随机数生成器
|
||||
|
||||
你可以从 Wikipedia 获取相关的伪代码。
|
||||
|
||||
如果你使用 Python、Ruby 或(天哪)PHP,你的编程语言可能已经将 MT19937 作为 "rand()" 提供给你;**不要使用 rand()**。请自己实现这个随机数生成器。
|
||||
|
||||
@@ -1 +1,35 @@
|
||||
# Challenge 35: Implement DH with negotiated groups, and break with malicious "g" parameters
|
||||
# Challenge 35: 实现带协商群组的 DH,并使用恶意的 "g" 参数进行破解
|
||||
|
||||
协议流程:
|
||||
|
||||
**A->B**
|
||||
发送 "p", "g"
|
||||
|
||||
**B->A**
|
||||
发送 ACK
|
||||
|
||||
**A->B**
|
||||
发送 "A"
|
||||
|
||||
**B->A**
|
||||
发送 "B"
|
||||
|
||||
**A->B**
|
||||
发送 AES-CBC(SHA1(s)[0:16], iv=random(16), msg) + iv
|
||||
|
||||
**B->A**
|
||||
发送 AES-CBC(SHA1(s)[0:16], iv=random(16), A's msg) + iv
|
||||
|
||||
再次进行中间人攻击,但这次操控 "g"。尝试以下情况会发生什么:
|
||||
|
||||
```
|
||||
g = 1
|
||||
g = p
|
||||
g = p - 1
|
||||
```
|
||||
|
||||
为每种情况编写攻击代码。
|
||||
|
||||
## 这种情况何时会发生?
|
||||
|
||||
老实说,在真实世界的系统中并不常见。如果你能操控 "g",很可能你能操控更严重的东西。大多数系统会预先约定一个静态的 DH 群组。但是相同的构造存在于椭圆曲线 Diffie-Hellman 中,在那里这变得更加相关。
|
||||
|
||||
@@ -1 +1,46 @@
|
||||
# Challenge 36: Implement Secure Remote Password (SRP)
|
||||
# Challenge 36: 实现安全远程密码协议 (SRP)
|
||||
|
||||
要理解 SRP,请先看看你是如何从 DH 生成 AES 密钥的;现在,注意到你可以做"相反"的操作,从哈希生成一个数值参数。然后:
|
||||
|
||||
将 A 和 B 替换为 C 和 S(客户端和服务器)
|
||||
|
||||
**C & S**
|
||||
约定 N=[NIST Prime], g=2, k=3, I (email), P (password)
|
||||
|
||||
**S**
|
||||
1. 生成 salt 作为随机整数
|
||||
2. 生成字符串 xH=SHA256(salt|password)
|
||||
3. 以某种方式将 xH 转换为整数 x(在十六进制摘要前加上 0x)
|
||||
4. 生成 v=g**x % N
|
||||
5. 保存除 x, xH 之外的所有内容
|
||||
|
||||
**C->S**
|
||||
发送 I, A=g**a % N(类似 Diffie Hellman)
|
||||
|
||||
**S->C**
|
||||
发送 salt, B=kv + g**b % N
|
||||
|
||||
**S, C**
|
||||
计算字符串 uH = SHA256(A|B), u = uH 的整数值
|
||||
|
||||
**C**
|
||||
1. 生成字符串 xH=SHA256(salt|password)
|
||||
2. 以某种方式将 xH 转换为整数 x(在十六进制摘要前加上 0x)
|
||||
3. 生成 S = (B - k * g**x)**(a + u * x) % N
|
||||
4. 生成 K = SHA256(S)
|
||||
|
||||
**S**
|
||||
1. 生成 S = (A * v**u) ** b % N
|
||||
2. 生成 K = SHA256(S)
|
||||
|
||||
**C->S**
|
||||
发送 HMAC-SHA256(K, salt)
|
||||
|
||||
**S->C**
|
||||
如果 HMAC-SHA256(K, salt) 验证通过,发送 "OK"
|
||||
|
||||
你可能需要在某种 REPL 中进行这个操作;可能需要尝试几次。
|
||||
|
||||
无论你如何从整数转换为字符串或从字符串转换为整数(当数据进入或离开 SHA256 时),只要保持一致即可。我通过使用整数的 ASCII 十进制表示作为 SHA256 的输入,并在处理其输出时将十六进制摘要转换为整数来进行测试。
|
||||
|
||||
这基本上是 Diffie Hellman 的一个变种,将密码混合到公钥中。服务器还采取了额外的步骤来避免存储容易被破解的密码等价物。
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
# Challenge 37: Break SRP with a zero key
|
||||
# Challenge 37: 使用零密钥破解 SRP
|
||||
|
||||
让你的 SRP 在实际的客户端-服务器设置中运行。使用协议和有效密码"登录"。
|
||||
|
||||
现在通过让客户端发送 0 作为其 "A" 值来在没有密码的情况下登录。这对双方计算的 "S" 值有什么影响?
|
||||
|
||||
现在通过让客户端发送 N, N*2 等来在没有密码的情况下登录。
|
||||
|
||||
## 密码分析 MVP 奖
|
||||
|
||||
Trevor Perrin 和 Nate Lawson 在 7 年前教会了我们这种攻击。这是**极其出色的**。对 DH 的攻击很难"操作化"。但这种攻击使用了相同的概念,并导致认证绕过。我们见过的几乎每个 SRP 实现都有这个缺陷;如果你看到一个新的实现,去寻找这个漏洞。
|
||||
|
||||
@@ -1 +1,44 @@
|
||||
# Challenge 38: Offline dictionary attack on simplified SRP
|
||||
# Challenge 38: 对简化 SRP 的离线字典攻击
|
||||
|
||||
**S**
|
||||
```
|
||||
x = SHA256(salt|password)
|
||||
v = g**x % n
|
||||
```
|
||||
|
||||
**C->S**
|
||||
```
|
||||
I, A = g**a % n
|
||||
```
|
||||
|
||||
**S->C**
|
||||
```
|
||||
salt, B = g**b % n, u = 128 bit random number
|
||||
```
|
||||
|
||||
**C**
|
||||
```
|
||||
x = SHA256(salt|password)
|
||||
S = B**(a + ux) % n
|
||||
K = SHA256(S)
|
||||
```
|
||||
|
||||
**S**
|
||||
```
|
||||
S = (A * v ** u)**b % n
|
||||
K = SHA256(S)
|
||||
```
|
||||
|
||||
**C->S**
|
||||
发送 HMAC-SHA256(K, salt)
|
||||
|
||||
**S->C**
|
||||
如果 HMAC-SHA256(K, salt) 验证通过,发送 "OK"
|
||||
|
||||
注意在这个协议中,服务器的 "B" 参数不依赖于密码(它只是一个 Diffie Hellman 公钥)。
|
||||
|
||||
确保协议在给定有效密码时能正常工作。
|
||||
|
||||
现在,作为中间人攻击者运行协议:伪装成服务器,并对 b、B、u 和 salt 使用任意值。
|
||||
|
||||
从 A 的 HMAC-SHA256(K, salt) 中破解密码。
|
||||
|
||||
@@ -1 +1,23 @@
|
||||
# Challenge 39: Implement RSA
|
||||
# Challenge 39: 实现 RSA
|
||||
|
||||
实现 RSA 有两个烦人的地方。它们都涉及密钥生成;RSA 中实际的加密/解密是微不足道的。
|
||||
|
||||
首先,你需要生成随机素数。你不能像在 DH 中那样提前约定一个素数。你可以自己编写这个算法,但我只是作弊,使用 OpenSSL 的 BN 库来完成这项工作。
|
||||
|
||||
第二个是你需要一个"invmod"操作(乘法逆元),这不是你的编程语言内置的操作。算法只有几行,但我总是会花费一个小时才能让它工作。
|
||||
|
||||
我建议你不要费心处理素数生成,但确实花时间让你自己的 EGCD 和 invmod 算法工作。
|
||||
|
||||
现在:
|
||||
|
||||
- 生成 2 个随机素数。我们将使用小数字开始,所以你可以从素数表中挑选它们。称它们为 "p" 和 "q"。
|
||||
- 让 n 为 p \* q。你的 RSA 数学是模 n 的。
|
||||
- 让 et 为 (p-1)\*(q-1)("欧拉函数值")。你只在密钥生成时需要这个值。
|
||||
- 让 e 为 3。
|
||||
- 计算 d = invmod(e, et)。invmod(17, 3120) 是 2753。
|
||||
- 你的公钥是 [e, n]。你的私钥是 [d, n]。
|
||||
- 加密:c = m^e%n。解密:m = c^d%n
|
||||
- 用一个数字测试这个,比如 "42"。
|
||||
- 用大数素数重复(保持 e=3)。
|
||||
|
||||
最后,要加密一个字符串,做一些简陋的事情,比如将字符串转换为十六进制并在前面加上"0x"将其转换为数字。数学不在乎你如何愚蠢地喂给它字符串。
|
||||
|
||||
@@ -1 +1,34 @@
|
||||
# Challenge 40: Implement an E=3 RSA Broadcast attack
|
||||
# Challenge 40: 实现 E=3 RSA 广播攻击
|
||||
|
||||
假设你是一个 Javascript 程序员。也就是说,你在使用一个天真的手工制作的 RSA 来加密而不进行填充。
|
||||
|
||||
假设你可以被强制在三个不同的公钥下三次加密相同的明文。你可以;这已经发生过。
|
||||
|
||||
然后攻击者可以轻易地解密你的消息,通过:
|
||||
|
||||
1. 捕获任何3个密文及其对应的公钥
|
||||
2. 使用中国剩余定理 (CRT) 求解由三个密文表示的数字(它们是各自公钥模的余数)
|
||||
3. 取结果数字的立方根
|
||||
|
||||
CRT 说你可以取任何数字并将其表示为一系列模数的一系列余数的组合。在三余数情况下,你有:
|
||||
|
||||
```
|
||||
result =
|
||||
(c_0 * m_s_0 * invmod(m_s_0, n_0)) +
|
||||
(c_1 * m_s_1 * invmod(m_s_1, n_1)) +
|
||||
(c_2 * m_s_2 * invmod(m_s_2, n_2)) mod N_012
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
```
|
||||
c_0, c_1, c_2 是三个各自的余数模
|
||||
n_0, n_1, n_2
|
||||
|
||||
m_s_n(对于 n 在 0, 1, 2 中)是除了 n_n 之外的模数的乘积
|
||||
--- 即,m_s_1 是 n_0 * n_2
|
||||
|
||||
N_012 是所有三个模数的乘积
|
||||
```
|
||||
|
||||
要使用简单的立方根解密 RSA,省略最终的模运算;只取原始累积结果并开立方根。
|
||||
|
||||
@@ -1 +1,79 @@
|
||||
# Challenge 43: DSA key recovery from nonce
|
||||
# Challenge 43: 从随机数恢复 DSA 密钥
|
||||
|
||||
**步骤 1**:搬迁,使你远离我们容易到达的距离。
|
||||
|
||||
**步骤 2**:实现 DSA,包括签名和验证,包括参数生成。
|
||||
|
||||
*哈哈你离得太远了,不能来揍我们。*
|
||||
|
||||
*开玩笑的*,如果你想的话,你可以跳过参数生成部分;如果你这样做,使用这些参数:
|
||||
|
||||
```
|
||||
p = 800000000000000089e1855218a0e7dac38136ffafa72eda7
|
||||
859f2171e25e65eac698c1702578b07dc2a1076da241c76c6
|
||||
2d374d8389ea5aeffd3226a0530cc565f3bf6b50929139ebe
|
||||
ac04f48c3c84afb796d61e5a4f9a8fda812ab59494232c7d2
|
||||
b4deb50aa18ee9e132bfa85ac4374d7f9091abc3d015efc87
|
||||
1a584471bb1
|
||||
|
||||
q = f4f47f05794b256174bba6e9b396a7707e563c5b
|
||||
|
||||
g = 5958c9d3898b224b12672c0b98e06c60df923cb8bc999d119
|
||||
458fef538b8fa4046c8db53039db620c094c9fa077ef389b5
|
||||
322a559946a71903f990f1f7e0e025e2d7f7cf494aff1a047
|
||||
0f5b64c36b625a097f1651fe775323556fe00b3608c887892
|
||||
878480e99041be601a62166ca6894bdd41a7054ec89f756ba
|
||||
9fc95302291
|
||||
```
|
||||
|
||||
("但我想要更小的参数!"那么自己生成它们。)
|
||||
|
||||
DSA 签名操作生成一个随机子密钥 "k"。你知道这个,因为你实现了 DSA 签名操作。
|
||||
|
||||
这是关于 DSA "k" 子密钥的两个挑战中第一个也是较容易的一个。
|
||||
|
||||
给定一个已知的 "k",恢复 DSA 私钥 "x" 是微不足道的:
|
||||
|
||||
```
|
||||
(s * k) - H(msg)
|
||||
x = ---------------- mod q
|
||||
r
|
||||
```
|
||||
|
||||
做几次这个来证明你理解了它。将它捕获在某种函数中。
|
||||
|
||||
现在。我使用了上面的参数。我生成了一个密钥对。我的公钥是:
|
||||
|
||||
```
|
||||
y = 84ad4719d044495496a3201c8ff484feb45b962e7302e56a392aee4
|
||||
abab3e4bdebf2955b4736012f21a08084056b19bcd7fee56048e004
|
||||
e44984e2f411788efdc837a0d2e5abb7b555039fd243ac01f0fb2ed
|
||||
1dec568280ce678e931868d23eb095fde9d3779191b8c0299d6e07b
|
||||
bb283e6633451e535c45513b2d33c99ea17
|
||||
```
|
||||
|
||||
我签署了
|
||||
|
||||
```
|
||||
For those that envy a MC it can be hazardous to your health
|
||||
So be friendly, a matter of life and death, just like a etch-a-sketch
|
||||
```
|
||||
|
||||
(我对这个字符串的 SHA1 是 *d2d0714f014a9784047eaeccf956520045c45265*;我不知道 NIST 希望你做什么,但当我将该哈希转换为整数时,我得到:*0xd2d0714f014a9784047eaeccf956520045c45265*)。
|
||||
|
||||
我得到:
|
||||
|
||||
```
|
||||
r = 548099063082341131477253921760299949438196259240
|
||||
s = 857042759984254168557880549501802188789837994940
|
||||
```
|
||||
|
||||
我用一个破损的 DSA 实现签署了这个字符串,该实现生成的 "k" 值在 0 和 2^16 之间。我的私钥是什么?
|
||||
|
||||
它的 SHA-1 指纹(转换为十六进制后)是:
|
||||
|
||||
```
|
||||
0954edd5e0afe5542a4adf012611a91912a3ec16
|
||||
```
|
||||
|
||||
显然,它也为该字符串生成相同的签名。
|
||||
|
||||
@@ -1 +1,42 @@
|
||||
# Challenge 44: DSA nonce recovery from repeated nonce
|
||||
# Challenge 44: 从重复随机数中恢复 DSA 随机数
|
||||
|
||||
## 密码分析 MVP 奖
|
||||
|
||||
这种攻击(在椭圆曲线群中)破解了 PS3。这是一个伟大、伟大的攻击。
|
||||
|
||||
在这个文件中找到 DSA 签名消息的集合。(注意:每个消息都有一个尾随空格。)
|
||||
|
||||
这些消息是在以下公钥下签名的:
|
||||
|
||||
```
|
||||
y = 2d026f4bf30195ede3a088da85e398ef869611d0f68f07
|
||||
13d51c9c1a3a26c95105d915e2d8cdf26d056b86b8a7b8
|
||||
5519b1c23cc3ecdc6062650462e3063bd179c2a6581519
|
||||
f674a61f1d89a1fff27171ebc1b93d4dc57bceb7ae2430
|
||||
f98a6a4d83d8279ee65d71c1203d2c96d65ebbf7cce9d3
|
||||
2971c3de5084cce04a2e147821
|
||||
```
|
||||
|
||||
(使用与前一个练习相同的域参数)
|
||||
|
||||
找到我们意外使用重复 "k" 的消息应该不难。给定一对这样的消息,你可以使用以下公式发现我们使用的 "k":
|
||||
|
||||
```
|
||||
(m1 - m2)
|
||||
k = --------- mod q
|
||||
(s1 - s2)
|
||||
```
|
||||
|
||||
## 九年级数学:学习它!
|
||||
|
||||
如果你想解开这个谜题,从原始 DSA 方程推导出该方程式。
|
||||
|
||||
## 基本循环群数学运算想要搞砸你
|
||||
|
||||
记住所有这些数学都是模 q 的;例如,s2 可能大于 s1,如果你在做模 q 的减法这不是问题。如果你像我一样,你肯定会因为忘记一个括号或一个 mod q 而浪费一个小时。(不要忘记那个模逆函数!)
|
||||
|
||||
我的私钥是什么?它的 SHA-1(来自十六进制)是:
|
||||
|
||||
```
|
||||
ca8f6f7c66fa362d40760d135b763eb8527d3d52
|
||||
```
|
||||
|
||||
@@ -1 +1,19 @@
|
||||
# Challenge 45: DSA parameter tampering
|
||||
# Challenge 45: DSA 参数篡改
|
||||
|
||||
取用你之前练习中的 DSA 代码。将其想象为算法的一部分,其中允许客户端提出域参数(p 和 q 模数,以及 g 生成元)。
|
||||
|
||||
这会很糟糕,因为攻击者可能欺骗受害者接受错误的参数。Vaudenay 给出了两个错误生成元参数的例子:模 p 等于 0 的生成元,和模 p 等于 1 的生成元。
|
||||
|
||||
使用前一个练习中的参数,但将 "g" 替换为 0。生成一个签名。你会注意到一些糟糕的事情。验证签名。现在验证任何其他字符串的任何其他签名。
|
||||
|
||||
现在,尝试将 (p+1) 作为 "g"。使用这个 "g",你可以为任何 DSA 公钥生成一个魔术签名 s, r,它将对任何字符串验证有效。对于任意的 z:
|
||||
|
||||
```
|
||||
r = ((y**z) % p) % q
|
||||
|
||||
r
|
||||
s = --- % q
|
||||
z
|
||||
```
|
||||
|
||||
签名 "Hello, world"。以及 "Goodbye, world"。
|
||||
|
||||
@@ -1 +1,37 @@
|
||||
# Challenge 46: RSA parity oracle
|
||||
# Challenge 46: RSA 奇偶性预言机
|
||||
|
||||
## 这种情况何时会发生?
|
||||
|
||||
这有点像一个玩具问题,但对于理解 RSA 在做什么非常有帮助(也解释了为什么纯数论加密是可怕的)。相信我们,你想在尝试下一个挑战之前做这个。另外,它很有趣。
|
||||
|
||||
生成一个 1024 位的 RSA 密钥对。
|
||||
|
||||
编写一个预言机函数,使用私钥回答问题"这条消息的明文是偶数还是奇数"(消息的最后一位是 0 还是 1)。例如,想象一个服务器接受 RSA 加密的消息并检查其解密的奇偶性以验证它们,如果它们的奇偶性错误就输出错误。
|
||||
|
||||
无论如何:函数根据解密的明文是偶数还是奇数返回 true 或 false,没有别的。
|
||||
|
||||
取以下字符串并在你的代码中对其进行 un-Base64(不要看!)并用公钥加密它,创建密文:
|
||||
|
||||
```
|
||||
VGhhdCdzIHdoeSBJIGZvdW5kIHlvdSBkb24ndCBwbGF5IGFyb3VuZCB3aXRoIHRoZSBGdW5reSBDb2xkIE1lZGluYQ==
|
||||
```
|
||||
|
||||
使用你的预言机函数,你可以轻易地解密消息。
|
||||
|
||||
原因如下:
|
||||
|
||||
- RSA 密文只是数字。你可以对它们进行简单的数学运算。例如,你可以将密文乘以另一个数字的 RSA 加密;相应的明文将是这两个数字的乘积。
|
||||
- 如果你将密文加倍(乘以 (2**e)%n),结果明文将(显然)是偶数或奇数。
|
||||
- 如果加倍后的明文是偶数,加倍明文*没有绕过模数* --- 模数是一个素数。这意味着明文小于模数的一半。
|
||||
|
||||
你可以重复应用这个启发式方法,每次消息的每一位,每次检查你的预言机函数。
|
||||
|
||||
你的解密函数从明文的边界 [0,n] 开始。
|
||||
|
||||
解密的每次迭代将边界减半;要么上界减半,要么下界减半。
|
||||
|
||||
经过 log2(n) 次迭代后,你就有了消息的解密。
|
||||
|
||||
在每次迭代时将消息的上界作为字符串打印;你会看到消息以"好莱坞风格"解密。
|
||||
|
||||
解密上面的字符串(在将其加密到隐藏的私钥后)。
|
||||
|
||||
@@ -1 +1,38 @@
|
||||
# Challenge 47: Bleichenbacher's PKCS 1.5 Padding Oracle (Simple Case)
|
||||
# 挑战47:Bleichenbacher的PKCS 1.5填充预言机(简单情况)
|
||||
|
||||
## 难度等级:中等
|
||||
|
||||
接下来的两个挑战是整个系列中最困难的。
|
||||
|
||||
让我们为你谷歌这个:["基于RSA加密标准的协议的选择密文攻击"](http://lmgtfy.com/?q=%22Chosen+ciphertext+attacks+against+protocols+based+on+the+RSA+encryption+standard%22)
|
||||
|
||||
这是来自CRYPTO '98的Bleichenbacher论文;我在第一个搜索页面上得到一堆.ps版本。
|
||||
|
||||
阅读这篇论文。它描述了对PKCS#1v1.5的填充预言机攻击。这个攻击在精神上类似于你之前构建的CBC填充预言机;它是一个"适应性选择密文攻击",这意味着你从一个有效的密文开始,反复破坏它,将被篡改的密文弹回目标以了解原始信息。
|
||||
|
||||
这是现代使用RSA的密码系统中的常见缺陷。
|
||||
|
||||
这也是构建密码攻击最有趣的方法。它涉及9年级数学,但也让你实现一个复杂程度与寻找最小成本生成树相当的算法。
|
||||
|
||||
设置:
|
||||
|
||||
- 构建一个预言机函数,就像你在上一个练习中做的那样,但让它检查plaintext[0] == 0和plaintext[1] == 2
|
||||
- 生成256位密钥对(即,p和q各为128位素数),[n, e, d]
|
||||
- 将d和n插入你的预言机函数
|
||||
- PKCS1.5填充一个短消息,如"kick it, CC",称为"m"。加密以获得"c"
|
||||
- 使用你的填充预言机解密"c"
|
||||
|
||||
对于这个挑战,我们使用了一个不可持续的小RSA模数(你可以立即分解这个密钥对)。这是因为这个练习针对Bleichenbacher论文中的特定步骤——步骤2c,它实现了对明文的快速、近似O(log n)搜索。
|
||||
|
||||
当你阅读论文时要记住的事情:
|
||||
|
||||
- RSA密文只是数字
|
||||
- RSA对乘法是"同态的",这意味着你可以将c * RSA(2)相乘得到c',它将解密为plaintext * 2。这令人震惊但如果你在代码中玩弄它很容易看到——尝试将密文与你知道的数字的RSA加密相乘,这样你就能理解它
|
||||
- 你需要为这个挑战理解的是,Bleichenbacher在密文上使用乘法的方式就像CBC预言机使用随机块的XOR一样
|
||||
- 符合PKCS#1v1.5的明文,以00:02开头的明文,必须是02:00:00...00和02:FF:FF..FF之间的数字——换句话说,2B和3B-1,其中B是模数的位大小减去前16位。当你看到2B和3B时,这就是论文在玩弄的想法
|
||||
|
||||
要解密"c",你需要论文中的步骤2a(搜索第一个"s",当加密并与密文相乘时,产生符合要求的明文)、步骤2c(快速O(log n)搜索)和步骤3。
|
||||
|
||||
你的步骤3代码可能不需要处理多个范围。
|
||||
|
||||
我们建议你只使用论文中的原始数学(检查,检查,再次检查你到代码的翻译),不要花太多时间试图理解数学是如何工作的。
|
||||
|
||||
@@ -1 +1,22 @@
|
||||
# Challenge 48: Bleichenbacher's PKCS 1.5 Padding Oracle (Complete Case)
|
||||
# 挑战48:Bleichenbacher的PKCS 1.5填充预言机(完整情况)
|
||||
|
||||
## 密码分析MVP奖
|
||||
|
||||
这是一个极其有用的攻击。PKCS#1v15填充,尽管完全不安全,*是RSA实现使用的默认填充*。替代它的OAEP标准没有被广泛实现。这个攻击经常破坏SSL/TLS。
|
||||
|
||||
这是挑战#47的延续;它实现了完整的BB'98攻击。
|
||||
|
||||
按照你在#47中的方式设置自己,但这次生成768位模数。
|
||||
|
||||
要使攻击在现实的RSA密钥对上工作,你需要重现论文中的步骤2b,你的步骤3实现需要处理多个范围。
|
||||
|
||||
完整的Bleichenbacher攻击基本上是这样工作的:
|
||||
|
||||
- 从可能产生大于2B的明文的最小's'开始,迭代搜索产生符合要求的明文的's'
|
||||
- 对于我们已知的's1'和'n',求解m1=m0s1-rn(再次:只是模乘法的定义)来得到'r',我们绕过模数的次数
|
||||
- 'm0'和'm1'是未知的,但我们知道两者都是符合PKCS#1v1.5的明文,因此在[2B,3B]之间
|
||||
- 我们用已知的边界替换两者,只留下'r'自由,并求解可能的'r'值范围。这个范围应该很小!
|
||||
- 再次求解m1=m0s1-rn,但这次求解'm0',插入我们在上一步中生成的每个'r'值。这给我们新的区间来工作。排除任何在2B,3B之外的区间
|
||||
- 对连续更高的's'值重复这个过程。最终,这个过程将使我们减少到只有一个区间,此时我们回到练习#47
|
||||
|
||||
当我们减少到一个区间时会发生什么,我们停止盲目地递增's';相反,我们开始快速增长'r'并通过求解m1=m0s1-rn来得到's'而不是'r'或'm0'来支持它。这么多代数!让你的青少年儿子为你做吧!*注意:在实践中效果不佳*
|
||||
|
||||
@@ -1 +1,54 @@
|
||||
# Challenge 52: Iterated Hash Function Multicollisions
|
||||
# Challenge 52: 迭代哈希函数多碰撞
|
||||
|
||||
既然我们在讨论哈希函数...
|
||||
|
||||
你在哈希函数中想要的主要特性是抗碰撞性。也就是说,生成碰撞应该是困难的,为给定哈希生成碰撞应该是*真正*困难的(*也就是*原像攻击)。
|
||||
|
||||
迭代哈希函数有一个问题:生成*大量*碰撞的努力呈亚线性增长。
|
||||
|
||||
什么是迭代哈希函数?就所有意图和目的而言,我们谈论的是 Merkle-Damgård 构造。它看起来像这样:
|
||||
|
||||
```
|
||||
function MD(M, H, C):
|
||||
for M[i] in pad(M):
|
||||
H := C(M[i], H)
|
||||
return H
|
||||
```
|
||||
|
||||
对于消息 M,初始状态 H 和压缩函数 C。
|
||||
|
||||
这应该看起来非常熟悉,因为 SHA-1 和 MD4 都属于这一类。很酷的是,你可以使用这个公式从你手头的一些备用密码原语构建一个临时哈希函数(例如 C = AES-128)。
|
||||
|
||||
回到任务:碰撞的成本呈亚线性增长。这意味着什么?如果找到一个碰撞是可行的,那么找到很多碰撞可能也是可行的。
|
||||
|
||||
怎么做?对于给定状态 H,找到两个碰撞的块。现在将此碰撞产生的哈希作为你的新 H 并重复。认识到每次迭代中,你实际上可以通过为该槽位替换两个块中的任一个来使你的碰撞数量翻倍。
|
||||
|
||||
这意味着如果找到两个碰撞消息需要 2^(b/2) 的工作量(其中 b 是哈希函数的位大小),那么找到 2^n 个碰撞消息只需要 n*2^(b/2) 的工作量。
|
||||
|
||||
让我们测试一下。首先,构建你自己的 MD 哈希函数。我们将生成大量碰撞,所以不要过度努力。事实上,特意让它变糟。这里有一种方法:
|
||||
|
||||
1. 采用一个快速块密码并将其用作 C。
|
||||
2. 让 H 相当小。如果只有 16 位,我不会看不起你。选择一些初始 H。
|
||||
3. H 将是来自 C 的输入密钥和输出块。这意味着你需要在输入时填充它,在输出时丢弃位。
|
||||
|
||||
现在编写函数 f(n),它将在此哈希函数中生成 2^n 个碰撞。
|
||||
|
||||
为什么这很重要?一个原因是人们试图通过将哈希函数级联在一起来加强它们。我的意思是:
|
||||
|
||||
1. 采用哈希函数 f 和 g。
|
||||
2. 构建 h,使得 h(x) = f(x) || g(x)。
|
||||
|
||||
这个想法是,如果 f 中的碰撞成本为 2^(b1/2),g 中的碰撞成本为 2^(b2/2),那么 h 中的碰撞应该达到 2^((b1+b2)/2) 的高价。
|
||||
|
||||
但现在我们知道这不是真的!
|
||||
|
||||
这里是想法:
|
||||
|
||||
1. 选择"更便宜"的哈希函数。假设是 f。
|
||||
2. 在 f 中生成 2^(b2/2) 个碰撞消息。
|
||||
3. 你的消息池很可能在 g 中有碰撞。
|
||||
4. 找到它。
|
||||
|
||||
如果没有,继续生成便宜的碰撞,直到找到它。
|
||||
|
||||
通过构建一个更昂贵(但不*太*昂贵)的哈希函数与你刚刚使用的函数配对来证明这一点。找到一对在两个函数下都碰撞的消息。测量对碰撞函数的总调用次数。
|
||||
|
||||
@@ -1 +1,38 @@
|
||||
# Challenge 53: Kelsey and Schneier's Expandable Messages
|
||||
# 挑战53:Kelsey和Schneier的可扩展消息
|
||||
|
||||
我们用来判断密码散列函数的基本标准之一是其对第二原像攻击的抗性。这意味着如果我给你x和y使得H(x) = y,你应该很难找到x'使得H(x') = H(x) = y。
|
||||
|
||||
有多难?暴力破解难。对于2^b散列函数,我们希望第二原像攻击花费2^b操作。
|
||||
|
||||
结果表明,对于非常长的消息,情况并非如此。
|
||||
|
||||
考虑我们试图解决的问题:我们想找到一个在最后一个块中与H(x)碰撞的消息。但有大量的中间块,每个都有自己的中间散列状态。
|
||||
|
||||
如果我们能碰撞到其中一个呢?然后我们可以从原始消息附加所有后续块来产生原始的H(x)。几乎。
|
||||
|
||||
我们不能完全这样做,因为填充会搞砸事情。
|
||||
|
||||
我们需要的是*可扩展消息*。
|
||||
|
||||
在上一个问题中,我们使用多重碰撞以n*2^(b/2)的努力产生2^n个碰撞消息。我们可以使用相同的原理为给定的k产生长度为(k, k + 2^k - 1)的消息集。
|
||||
|
||||
方法如下:
|
||||
|
||||
1. 从散列函数的初始状态开始,找到单块消息和2^(k-1)+1块消息之间的碰撞。不要每次都散列整个长消息。选择2^(k-1)个虚拟块,散列它们,然后专注于最后一个块。
|
||||
|
||||
2. 从第一步获取输出状态。将此作为你的新初始状态,并找到单块消息和2^(k-2)+1块消息之间的另一个碰撞。
|
||||
|
||||
3. 重复这个过程k次。你的最后一个碰撞应该在单块消息和2^0+1 = 2块消息之间。
|
||||
|
||||
现在你可以通过从每对中选择适当的消息(短或长)来制作(k, k + 2^k - 1)块中任何长度的消息。
|
||||
|
||||
现在我们准备攻击2^k块的长消息M。
|
||||
|
||||
1. 使用上述策略生成长度为(k, k + 2^k - 1)的可扩展消息
|
||||
2. 散列M并生成中间散列状态到它们对应的块索引的映射
|
||||
3. 从你的可扩展消息的最终状态,找到到你映射中中间状态的单块"桥接"。注意它映射到的索引i
|
||||
4. 使用你的可扩展消息生成正确长度的前缀,使得*len(prefix || bridge || M[i..])* = *len(M)*
|
||||
|
||||
最终块中的填充现在应该是正确的,你的伪造应该散列到与M相同的值。
|
||||
|
||||
这里的难度将在2^(b-k)左右。通过在树生成阶段增加或减少k,你可以调整这一步的难度。提前做更多工作可能是有意义的,因为事件过去后人们会等待你提供消息。预测愉快!
|
||||
|
||||
@@ -1 +1,37 @@
|
||||
# Challenge 54: Kelsey and Kohno's Nostradamus Attack
|
||||
# 挑战54:Kelsey和Kohno的诺查丹玛斯攻击
|
||||
|
||||
散列函数有时用作秘密预测的证明。
|
||||
|
||||
例如,假设你想预测一个赛季中每场美国职业棒球大联盟比赛的比分。(总共2,430场。)你可能担心发布你的预测会影响结果。
|
||||
|
||||
所以你写下所有比分,散列文档,并发布散列值。一旦赛季结束,你发布文档。然后每个人都可以散列文档来验证你的预言能力。
|
||||
|
||||
但是如果你不能准确预测2.4k场棒球比赛的比分怎么办?不要担心——在这个方案下伪造预测归结为另一个第二原像攻击。
|
||||
|
||||
我们可以应用上一个问题的长消息攻击,但它看起来很可疑。你会相信一个预测消息结果是2^50字节长的人吗?
|
||||
|
||||
结果表明我们可以用更短的后缀运行成功的攻击。检查方法:
|
||||
|
||||
1. 生成大量初始散列状态。比如,2^k。
|
||||
|
||||
2. 将它们配对并生成单块碰撞。现在你有2^k个散列状态碰撞到2^(k-1)个状态。
|
||||
|
||||
3. 重复这个过程。配对2^(k-1)个状态并生成碰撞。现在你有2^(k-2)个状态。
|
||||
|
||||
4. 继续这样做直到你有一个状态。这是你的预测。
|
||||
|
||||
5. 嗯,某种程度上。你需要承诺在填充中编码的某个长度。确保它足够长以容纳你的实际消息、这个后缀和一点胶水来连接它们。使用步骤4的状态散列这个填充块——这是你的预测。
|
||||
|
||||
你刚刚构建了什么?它基本上是一个将许多初始状态映射到公共最终状态的漏斗。关键的是我们现在有一个2^k状态的大字段可以尝试碰撞,但实际后缀只有k+1块长。
|
||||
|
||||
其余的是琐碎的:
|
||||
|
||||
1. 等待棒球赛季结束。(这可能需要一些时间。)
|
||||
|
||||
2. 写下比赛结果。或者,你知道,任何其他东西。我不太挑剔。
|
||||
|
||||
3. 生成足够的胶水块来获得正确的消息长度。最后一个块应该碰撞到你树中的一个叶子。
|
||||
|
||||
4. 沿着从叶子一直到根节点的路径,使用沿途的消息块构建你的后缀。
|
||||
|
||||
这里的难度将在2^(b-k)左右。通过在树生成阶段增加或减少k,你可以调整这一步的难度。提前做更多工作可能是有意义的,因为事件过去后人们会等待你提供消息。预测愉快!
|
||||
|
||||
@@ -1 +1,30 @@
|
||||
# Challenge 55: MD4 Collisions
|
||||
# Challenge 55: MD4 碰撞
|
||||
|
||||
MD4 是一个 128 位的加密散列函数,理论上需要大约 2^64 的工作量才能找到碰撞。
|
||||
|
||||
事实证明我们可以做得更好。
|
||||
|
||||
Wang 等人的论文 "Cryptanalysis of the Hash Functions MD4 and RIPEMD" 详细描述了一种密码分析攻击,让我们可以在 2^8 或更少的工作量内找到碰撞。
|
||||
|
||||
给定一个消息块 M,Wang 概述了一种策略来寻找姊妹消息块 M',仅在几个位上有所不同,但会与 M 产生碰撞。只要 M 满足一组短小的条件。
|
||||
|
||||
什么样的条件?在中间散列函数状态中简单的位级别等式,例如 a[1][6] = b[0][6]。这应该被理解为:"a[1] 的第六位(零索引)(即对 'a' 的第一次更新)应该等于 b[0] 的第六位(即 'b' 的初始值)"。
|
||||
|
||||
事实证明,很多这些条件都很容易满足。要了解原因,请看看 MD4 压缩函数中第一轮(共三轮)。在这一轮中,我们按顺序遍历消息块中的每个字并将其混合到状态中。所以我们可以通过这样做来确保我们所有的第一轮条件都成立:
|
||||
|
||||
```
|
||||
# 以正常方式计算 a[1] 的新值
|
||||
a[1] = (a[0] + f(b[0], c[0], d[0]) + m[0]).lrot(3)
|
||||
|
||||
# 纠正错误的位
|
||||
a[1] ^= ((a[1][6] ^ b[0][6]) << 6)
|
||||
|
||||
# 使用代数来纠正第一个消息块
|
||||
m[0] = a[1].rrot(3) - a[0] - f(b[0], c[0], d[0])
|
||||
```
|
||||
|
||||
简单确保所有第一轮条件将我们很好地置于生成碰撞的范围内,但我们可以通过纠正第二轮中的一些额外条件来做得更好。这有点棘手,因为我们需要小心不要破坏任何第一轮条件。
|
||||
|
||||
一旦你充分调整了 M,你可以简单地通过翻转几个位来生成 M' 并测试碰撞。不能保证碰撞,因为我们没有确保每个条件。但希望我们得到了足够多的条件,我们可以找到一个合适的 (M, M') 对而无需太多努力。
|
||||
|
||||
实现 Wang 的攻击。
|
||||
|
||||
@@ -1 +1,49 @@
|
||||
# Challenge 56: RC4 Single-Byte Biases
|
||||
# Challenge 56: RC4 单字节偏差
|
||||
|
||||
RC4 是一个流行的流密码,以在 TLS、WPA、RDP 等协议中的使用而著称。
|
||||
|
||||
它也易受到显著的单字节偏差影响,特别是在密钥流的早期。这意味着什么?
|
||||
|
||||
简单地说:对于密钥流中的给定位置,某些字节比其他字节更(或更不)可能出现。给定足够多的给定明文的加密,攻击者可以使用这些偏差来恢复整个明文。
|
||||
|
||||
现在,在线搜索 ["On the Security of RC4 in TLS and WPA"](http://lmgtfy.com/?q=On+the+Security+of+RC4+in+TLS+and+WPA)。这个网站是您获取 RC4 信息的一站式商店。
|
||||
|
||||
点击右侧的 "RC4 biases"。
|
||||
|
||||
这些是每个单字节偏差的图表(每页一个)。特别注意 z16、z32、z48 等上的巨大峰值。(注意:这些是一索引的,所以 z16 = keystream[15]。)
|
||||
|
||||
这些偏差有多有用?
|
||||
|
||||
点击进入研究论文并向下滚动到仿真结果。(顺便说一下,如果您有一些空闲时间,整篇论文都很值得一读。)我们从 2^26 次迭代的清晰峰值开始,但我们恢复前 256 字节中每一个的机会在我们接近 2^32 时接近 1。
|
||||
|
||||
有两种方法可以利用这些偏差。第一种方法非常简单:
|
||||
|
||||
1. 全面了解密钥流偏差。
|
||||
2. 在不同密钥下加密未知明文 2^30+ 次。
|
||||
3. 将密文偏差与密钥流偏差进行比较。
|
||||
|
||||
这样做需要对密钥流的每个字节的偏差有深入了解。但事实证明,如果我们对明文有一些控制权,我们只需要几个有用的偏差就可以做得很好。
|
||||
|
||||
如何?通过使用对单个偏差的了解作为明文的窥视孔。
|
||||
|
||||
解码这个秘密:
|
||||
|
||||
```
|
||||
QkUgU1VSRSBUTyBEUklOSyBZT1VSIE9WQUxUSU5F
|
||||
```
|
||||
|
||||
并将其称为 cookie。不要偷看!
|
||||
|
||||
现在使用它来构建这个加密预言机:
|
||||
|
||||
```
|
||||
RC4(your-request || cookie, random-key)
|
||||
```
|
||||
|
||||
在每次调用时使用新的 128 位密钥。
|
||||
|
||||
想象这种场景:您想窃取用户的安全 cookie。您可以生成任意请求(来自恶意插件或类似的东西)并监控网络流量。(好吧,这是不现实的 - cookie 不会像那样就在请求的开头 - 这只是一个例子!)
|
||||
|
||||
您可以通过请求 "/"、"/A"、"/AA" 等来控制 cookie 的位置。
|
||||
|
||||
为几个选定的索引(z16 和 z32 是好的)构建偏差映射并解密 cookie。
|
||||
|
||||
@@ -1 +1,124 @@
|
||||
# Challenge 58: Pollard's Method for Catching Kangaroos
|
||||
# Challenge 58: Pollard 的捕捉袋鼠方法
|
||||
|
||||
上一个问题有点人为。它只有效是因为我有意将那些破损的群参数强加给 Alice 和 Bob。虽然现实世界的群可能包含一些小子群,但在随机生成的群中找到这么多是不太可能的。
|
||||
|
||||
那么如果我们只能恢复 Bob 的秘钥的一部分呢?感觉应该有某种方法可以利用这些知识来恢复其余部分。确实有:Pollard 的袋鼠算法。
|
||||
|
||||
这是一种通用攻击,用于计算已知位于某个连续范围 [a, b] 内的离散对数(或"指数")。它的工作因子大约是范围大小的平方根。
|
||||
|
||||
基本策略是试图在两个伪随机元素序列之间找到碰撞。一个将从已知指数的元素开始,另一个将从我们想要找到其指数的元素 y 开始。
|
||||
|
||||
理解这些序列是如何生成的很重要。基本上,我们只是定义一些函数 f,将群元素(如生成元 g,或公钥 y)映射到标量(秘密指数,如 x),即:
|
||||
|
||||
```
|
||||
f(y) = <some x>
|
||||
```
|
||||
|
||||
现在不要担心 f 是如何实现的。只需知道它是一个映射函数,从我们所在的位置(某个 y)到我们下一步要采取的跳跃(某个 x)。而且它是确定性的:对于给定的 y,它应该始终返回相同的 x。
|
||||
|
||||
然后我们执行这样的循环:
|
||||
|
||||
```
|
||||
y := y * g^f(y)
|
||||
```
|
||||
|
||||
这里的关键是我们采取的下一步是一个函数,其唯一输入是当前元素。这意味着如果我们的两个序列碰巧访问相同的元素 y,它们将从那里开始同步进行。
|
||||
|
||||
好的,让我们更具体一些。我提到我们将以这种方式生成两个序列。第一个是我们的控制序列。这是 Pollard 例子中的驯服袋鼠。我们做这样的事情:
|
||||
|
||||
```
|
||||
xT := 0
|
||||
yT := g^b
|
||||
|
||||
for i in 1..N:
|
||||
xT := xT + f(yT)
|
||||
yT := yT * g^f(yT)
|
||||
```
|
||||
|
||||
回忆 b 是 y 指数的上界。所以我们从那个范围的最末端开始驯服袋鼠的运行。然后我们只是跳跃 N 次,并在 xT 中累积我们总共走过的距离。在循环结束时,yT = g^(b + xT)。这在后面会很重要。
|
||||
|
||||
注意这个算法不需要我们构建一个像 Shanks 的小步大步算法那样的大查找表,所以它的空间复杂度是常数。这有点巧妙。
|
||||
|
||||
现在:让我们捕捉那只野袋鼠。我们将做一个类似的循环,这次从 y 开始。我们希望在某个时刻我们会与驯服袋鼠的路径碰撞。如果我们这样做,我们最终会到达同一个地方。所以在每次迭代中,我们会检查我们是否到了那里。
|
||||
|
||||
```
|
||||
xW := 0
|
||||
yW := y
|
||||
|
||||
while xW < b - a + xT:
|
||||
xW := xW + f(yW)
|
||||
yW := yW * g^f(yW)
|
||||
|
||||
if yW = yT:
|
||||
return b + xT - xW
|
||||
```
|
||||
|
||||
花点时间思考循环条件。那个关系检查的是我们是否已经超过了 yT 并错过了它。换句话说,我们没有碰撞。这是一个概率算法,所以不保证会工作。
|
||||
|
||||
还要确保你理解返回语句。如果你思考我们如何得出 yW 和 yT 的最终值,应该清楚这个值是输入 y 的指数。
|
||||
|
||||
我们忽略了几个实现细节——特别是函数 f 和迭代计数 N。我做这样的事情:
|
||||
|
||||
```
|
||||
f(y) = 2^(y mod k)
|
||||
```
|
||||
|
||||
对于某个 k,你可以试验一下。让 k 更大将允许你在每次循环迭代中跳跃更大的距离。
|
||||
|
||||
然后 N 从 f 推导出来——取 f 所有可能输出的平均值并乘以一个小常数,例如 4。你可以让常数更大以增加找到碰撞的机会,代价是显而易见的额外计算。N 需要依赖 f 的原因是 f 控制着我们能够进行的跳跃大小。如果跳跃更大,我们需要更大的跑道来着陆,否则我们有跳过它的风险。
|
||||
|
||||
实现 Pollard 的袋鼠算法。这里是一些(不那么宽容的)群参数:
|
||||
|
||||
```
|
||||
p = 11470374874925275658116663507232161402086650258453896274534991676898999262641581519101074740642369848233294239851519212341844337347119899874391456329785623
|
||||
q = 335062023296420808191071248367701059461
|
||||
j = 34233586850807404623475048381328686211071196701374230492615844865929237417097514638999377942356150481334217896204702
|
||||
g = 622952335333961296978159266084741085889881358738459939978290179936063635566740258555167783009058567397963466103140082647486611657350811560630587013183357
|
||||
```
|
||||
|
||||
这里是一个样本 y:
|
||||
|
||||
```
|
||||
y = 7760073848032689505395005705677365876654629189298052775754597607446617558600394076764814236081991643094239886772481052254010323780165093955236429914607119
|
||||
```
|
||||
|
||||
y 的指数在范围 [0, 2^20] 中。用袋鼠算法找到它。
|
||||
|
||||
等等,那足够小到可以暴力破解。这里是一个指数在 [0, 2^40] 中的:
|
||||
|
||||
```
|
||||
y = 9388897478013399550694114614498790691034187453089355259602614074132918843899833277397448144245883225611726912025846772975325932794909655215329941809013733
|
||||
```
|
||||
|
||||
也找到那一个。可能需要几分钟。
|
||||
|
||||
~~ 稍后 ~~
|
||||
|
||||
关于袋鼠的内容够了,让我们回到 Bob。假设我们知道 Bob 的秘钥 x = n mod r 对于某个 r < q。实际上如何将此算法应用于获取其余部分并不完全明显!因为我们只有:
|
||||
|
||||
```
|
||||
x = n mod r
|
||||
```
|
||||
|
||||
这意味着:
|
||||
|
||||
```
|
||||
x = n + m*r
|
||||
```
|
||||
|
||||
对于某个未知的 m。这个关系定义了一组在 r 的间隔上分布的值,但 Pollard 的袋鼠需要一个连续范围!
|
||||
|
||||
实际上,这不是大问题。因为看——我们可以应用以下变换:
|
||||
|
||||
```
|
||||
x = n + m*r
|
||||
y = g^x = g^(n + m*r)
|
||||
y = g^n * g^(m*r)
|
||||
y' = y * g^-n = g^(m*r)
|
||||
g' = g^r
|
||||
y' = (g')^m
|
||||
```
|
||||
|
||||
现在简单地搜索 y' 相对于基元素 g' 的指数 m。注意我们对 m 有一个粗略的界限:[0, (q-1)/r]。找到 m 后,你可以将其插入你现有的 x 知识中以恢复秘密的其余部分。
|
||||
|
||||
取上述群参数并为 Bob 生成一个密钥对。使用你上一个问题中的子群限制攻击来恢复尽可能多的 Bob 的秘密。你将能够得到其中的很大一部分,但不是全部。然后使用袋鼠算法来追踪剩余的位。
|
||||
|
||||
@@ -1 +1,174 @@
|
||||
# Challenge 60: Single-Coordinate Ladders and Insecure Twists
|
||||
# Challenge 60: 单坐标梯子和不安全的扭曲
|
||||
|
||||
我们所有的辛勤工作即将带来一些回报。以下是完成这个挑战后你将能够使用的酷孩子术语列表:
|
||||
|
||||
* Montgomery 曲线
|
||||
* 单坐标梯子
|
||||
* 同构
|
||||
* 双有理等价
|
||||
* 二次扭曲
|
||||
* Frobenius 迹
|
||||
|
||||
你不会全部理解;确实不会。但你至少能够在 Twitter 上让加密爱好者们闭嘴。
|
||||
|
||||
现在,回到手头的任务。在上一个问题中,我们使用短 Weierstrass 曲线形式实现了 ECDH,如下所示:
|
||||
|
||||
```
|
||||
y^2 = x^3 + a*x + b
|
||||
```
|
||||
|
||||
很长时间以来,这一直是最受欢迎的曲线形式。90年代标准化的 NIST P 曲线看起来就是这样。这是你在大多数椭圆曲线教程中首先看到的(包括这个)。
|
||||
|
||||
我们可以做得更好。认识一下 Montgomery 曲线:
|
||||
|
||||
```
|
||||
B*v^2 = u^3 + A*u^2 + u
|
||||
```
|
||||
|
||||
虽然它几乎与 Weierstrass 形式一样古老,但直到最近它一直埋在文献中。Montgomery 曲线有一个杀手级特性,以计算标量乘法的简单高效算法的形式:Montgomery 梯子。
|
||||
|
||||
这里是梯子:
|
||||
|
||||
```
|
||||
function ladder(u, k):
|
||||
u2, w2 := (1, 0)
|
||||
u3, w3 := (u, 1)
|
||||
for i in reverse(range(bitlen(p))):
|
||||
b := 1 & (k >> i)
|
||||
u2, u3 := cswap(u2, u3, b)
|
||||
w2, w3 := cswap(w2, w3, b)
|
||||
u3, w3 := ((u2*u3 - w2*w3)^2,
|
||||
u * (u2*w3 - w2*u3)^2)
|
||||
u2, w2 := ((u2^2 - w2^2)^2,
|
||||
4*u2*w2 * (u2^2 + A*u2*w2 + w2^2))
|
||||
u2, u3 := cswap(u2, u3, b)
|
||||
w2, w3 := cswap(w2, w3, b)
|
||||
return u2 * w2^(p-2)
|
||||
```
|
||||
|
||||
你不应该理解这个。
|
||||
|
||||
不,真的!大多数人都不理解它。相反,他们访问显式公式数据库(https://www.hyperelliptic.org/EFD/),这是最先进的 ECC 实现技术的一站式商店。这就像椭圆曲线的作弊码。仅仅为了参考书目就值得访问。
|
||||
|
||||
话虽如此,我们应该试着稍微解开这个谜。这里是悬崖笔记:
|
||||
|
||||
1. Montgomery 曲线上的点是 (u, v) 对,但这个函数只接受 u 作为输入。仅给定点 P 的 u 坐标,这个函数计算 k*P 的*仅仅* u 坐标。因为我们只关心 u,这是一个单坐标梯子。
|
||||
|
||||
2. 那么 w 到底是什么?它是备用点表示的一部分。我们有一个坐标 u/w,而不是坐标 u。将其视为推迟昂贵的除法(读作:求逆)操作直到最后的方法。
|
||||
|
||||
3. cswap 是一个函数,根据其第三个参数是一还是零来交换其前两个参数(或不交换)。挑剔的实现者选择 cswap 的算术实现,而不是分支实现。
|
||||
|
||||
4. 内循环的核心是差分加法,然后是加倍操作。差分加法意味着我们只有在已经知道 P - Q 的情况下才能加两个点 P 和 Q。我们将这个差值作为输入 u 并在整个梯子中将其维持为不变量。确实,我们的两个初始点是:
|
||||
|
||||
```
|
||||
u2, w2 := (1, 0)
|
||||
u3, w3 := (u, 1)
|
||||
```
|
||||
|
||||
代表恒等式和输入 u。
|
||||
|
||||
5. 返回语句使用费马小定理的技巧执行模逆:
|
||||
|
||||
```
|
||||
a^p = a mod p
|
||||
a^(p-1) = 1 mod p
|
||||
a^(p-2) = a^-1 mod p
|
||||
```
|
||||
|
||||
6. Montgomery 梯子的一个后果是我们混淆 (u, v) 和 (u, -v)。但这种编码也混淆了零和无穷大。两者都表示为零。注意通常的异常情况,其中 w = 0 得到了优雅的处理:我们用指数进行求逆的技巧按预期输出零。
|
||||
|
||||
这很好:我们仍在素数阶的子群中工作。
|
||||
|
||||
继续实现梯子。记住所有计算都在 GF(233970423115425145524320034830162017933) 中。
|
||||
|
||||
哦,是的,曲线参数。你可能认为既然我们切换到新的曲线格式,我们也需要选择一条全新的曲线。但你完全错了!事实证明,一些短 Weierstrass 曲线可以转换为 Montgomery 曲线。
|
||||
|
||||
这是因为所有具有相等元素数量的有限循环群都共享一种我们称为"同构"的等价关系。如果你想一想,这是有道理的——如果阶是相同的,所有相同的子群都将存在,并且比例相同。
|
||||
|
||||
所以我们需要做的就是:
|
||||
|
||||
1. 找到一个与我们的曲线具有相等阶的 Montgomery 曲线。
|
||||
2. 弄清楚如何在曲线之间来回映射点。
|
||||
|
||||
你可以代数地执行这种转换。但这有点痛苦,所以给你:
|
||||
|
||||
```
|
||||
v^2 = u^3 + 534*u^2 + u
|
||||
```
|
||||
|
||||
通过狡猾和远见,我专门选择了这条曲线,在 Weierstrass 和 Montgomery 形式之间有一个非常简单的映射。这里是:
|
||||
|
||||
```
|
||||
u = x - 178
|
||||
v = y
|
||||
```
|
||||
|
||||
这使我们的基点:
|
||||
|
||||
```
|
||||
(4, 85518893674295321206118380980485522083)
|
||||
```
|
||||
|
||||
或者,你知道。就是 4。
|
||||
|
||||
无论如何,实现梯子。验证 ladder(4, n) = 0。在你的 Weierstrass 和 Montgomery 表示之间来回映射一些点并验证它们。
|
||||
|
||||
Montgomery 梯子的一个好处是它缺乏特殊情况。具体来说,没有特殊处理:P1 = O;P2 = O;P1 = P2;或 P1 = -P2。将其与我们的 Weierstrass 加法函数及其 if 语句营相比较。
|
||||
|
||||
还有一个安全好处:通过忽略 v 坐标,我们削弱了攻击者的很多余地。回想一下,选择任意 (x, y) 对的能力让他们从他们能想到的任何曲线中精挑细选点。单坐标梯子剥夺了攻击者的那种自由。
|
||||
|
||||
但等一下!试试这个:
|
||||
|
||||
```
|
||||
ladder(76600469441198017145391791613091732004, 11)
|
||||
```
|
||||
|
||||
什么鬼?这里发生了什么?
|
||||
|
||||
让我们做一个快速理智检查。这里再次是曲线方程:
|
||||
|
||||
```
|
||||
v^2 = u^3 + 534*u^2 + u
|
||||
```
|
||||
|
||||
插入 u 并取平方根以恢复 v。
|
||||
|
||||
你应该检测到有些非常错误的地方。这个 u 不代表我们曲线上的点!并不是每个 u 都代表。
|
||||
|
||||
这意味着即使我们只能提交一个坐标,我们仍然有一点余地来找到无效点。具体来说,使得 u^3 + 534*u^2 + u 不是二次剩余的输入 u 永远不能代表我们曲线上的点。那么我们到底在哪里?
|
||||
|
||||
我们所在的另一条曲线是一条叫做"二次扭曲"或简单地称为"扭曲"的姐妹曲线。实际上有整个二次扭曲家族对应我们的曲线,但它们都彼此同构。记住这意味着它们有相同数量的点,相同的子群等等。所以使用哪个特定的扭曲并不重要;事实上,我们甚至不需要选择一个。
|
||||
|
||||
我们主要对扭曲上存在的子群感兴趣,这意味着我们需要知道它包含多少个点。幸运的是,同时计算曲线及其扭曲上的组合点集更容易。让我们做:
|
||||
|
||||
1. 对于模数 p 的每个非零 u,如果 u^3 + A*u^2 + u 在 GF(p) 中是平方,则原始曲线上有两个点。
|
||||
|
||||
2. 如果上述和在 GF(p) 中是非平方,则扭曲曲线上有两个点。
|
||||
|
||||
应该清楚这些加起来总共有 2*(p-1) 个点,因为在 GF(p) 中有 p-1 个非零整数,每个有两个点。让我们继续:
|
||||
|
||||
3. 原始曲线和其扭曲都有一个点 (0, 0)。这只是一个常规点,不是群恒等式。
|
||||
|
||||
4. 原始曲线和其扭曲都有一个抽象的无穷远点,它作为群恒等式。
|
||||
|
||||
所以我们在两条曲线上总共有 2*p + 2 个点。由于我们已经知道原始曲线上有多少个点,我们可以轻易计算扭曲的阶。
|
||||
|
||||
如果 Alice 选择了一条具有不安全扭曲的曲线,即具有部分光滑阶的曲线,那么一些门会重新为 Eve 打开。她可以在扭曲曲线上选择低阶点,将它们发送给 Alice,并像以前一样执行无效曲线攻击。
|
||||
|
||||
唯一的警告是她无法使用曲线外的点恢复完整的秘密,只能恢复一部分。但我们知道如何处理这个问题。
|
||||
|
||||
所以:
|
||||
|
||||
1. 计算扭曲的阶并找到其小因子。这个应该有一堆在 2^24 以下。
|
||||
|
||||
2. 找到具有这些阶的点。这很简单:
|
||||
|
||||
a. 选择一个随机 u mod p 并验证 u^3 + A*u^2 + u 在 GF(p) 中是非平方。
|
||||
|
||||
b. 称扭曲的阶为 n。要找到阶 q 的元素,计算 ladder(u, n/q)。
|
||||
|
||||
3. 将这些点发送给 Alice 以恢复她的秘密的部分。
|
||||
|
||||
4. 当你用尽了扭曲中的所有小子群后,用袋鼠攻击恢复 Alice 秘密的剩余部分。
|
||||
|
||||
提示:你可能会注意到 k*u = -k*u,导致潜在 CRT 输出的组合爆炸。尝试发送额外的查询来缩小可能性范围。
|
||||
|
||||
@@ -1 +1,112 @@
|
||||
# Challenge 61: Duplicate-Signature Key Selection in ECDSA (and RSA)
|
||||
# Challenge 61: ECDSA(和 RSA)中的重复签名密钥选择
|
||||
|
||||
假设你有一个消息-签名对。如果我给你一个验证签名的公钥,你能相信我是作者吗?
|
||||
|
||||
你不应该。事实证明,在各种数字签名方案中解决这个问题是相当容易的。如果你在选择公钥方面有一点灵活性的话。
|
||||
|
||||
让我们考虑 ECDSA 的情况。
|
||||
|
||||
首先,实现 ECDSA。如果你仍然有旧的 DSA 实现,这应该是直接的。尽管如此,如果你需要的话,这里是一个复习:
|
||||
|
||||
```
|
||||
function sign(m, d):
|
||||
k := random_scalar(1, n)
|
||||
r := (k * G).x
|
||||
s := (H(m) + d*r) * k^-1
|
||||
return (r, s)
|
||||
|
||||
function verify(m, (r, s), Q):
|
||||
u1 := H(m) * s^-1
|
||||
u2 := r * s^-1
|
||||
R := u1*G + u2*Q
|
||||
return r = R.x
|
||||
```
|
||||
|
||||
记住所有标量运算都是模 n,基点 G 的阶。(d, Q) 是签名者的密钥对。H(m) 是消息的散列。
|
||||
|
||||
注意验证函数需要任意点加法。这意味着你的 Montgomery 梯子(只执行标量乘法)在这里不起作用。这没什么大不了的;只需回到你旧的 Weierstrass 实现。
|
||||
|
||||
一旦你实现了这个,为 Alice 生成一个密钥对并使用它来签名某个消息 m。
|
||||
|
||||
如果所有域参数都固定,Eve 很难找到一个 Q' 来验证这个签名。但域参数可能不固定 - 一些协议让用户将它们作为公钥的一部分来指定。
|
||||
|
||||
让我们重新整理一些术语。考虑这个等式:
|
||||
|
||||
```
|
||||
R = u1*G + u2*Q
|
||||
```
|
||||
|
||||
让我们做一些重新组合:
|
||||
|
||||
```
|
||||
R = u1*G + u2*(d*G)
|
||||
R = (u1 + u2*d)*G
|
||||
```
|
||||
|
||||
考虑 R、u1 和 u2 是固定的。这留下了 Alice 的秘密 d 和基点 G。由于我们不知道 d,我们需要选择一对新的值使等式成立。我们可以从秘密开始反向工作。
|
||||
|
||||
1. 选择一个随机的 d' mod n。
|
||||
|
||||
2. 计算 t := u1 + u2*d'。
|
||||
|
||||
3. 计算 G' := t^-1 * R。
|
||||
|
||||
4. 计算 Q' := d' * G'。
|
||||
|
||||
5. Eve 的公钥是 Q' 与域参数 (E(GF(p)), n, G')。E(GF(p)) 是 Alice 最初选择的椭圆曲线。
|
||||
|
||||
注意 Eve 的公钥是完全有效的:基点和她的公点都是素数阶 n 子群的成员。由于 E(GF(p)) 和 n 与 Alice 的公钥相同,它们应该通过相同的验证规则。
|
||||
|
||||
扮演 Eve 的角色,推导一个公钥和域参数来验证 Alice 对消息的签名。
|
||||
|
||||
让我们对 RSA 做同样的事情。相同的设置:我们有一些消息和对它的签名。我们如何制作一个公钥来验证签名?
|
||||
|
||||
好吧,首先让我们复习一下 RSA。签名验证看起来像这样:
|
||||
|
||||
```
|
||||
s^e = pad(m) mod N
|
||||
```
|
||||
|
||||
其中 (m, s) 是消息-签名对,(e, N) 是 Alice 的公钥。
|
||||
|
||||
所以我们真正寻找的是对 (e', N') 使那个等式成立。如果这开始看起来有点熟悉,它应该是:我们在这里做的是寻找以 s 为底的 pad(m) 的离散对数。
|
||||
|
||||
我们知道在具有许多小子群的群中,离散对数很容易用 Pohlig-Hellman 解决。群的选择取决于我们,所以我们不能失败!
|
||||
|
||||
但我们应该小心一些。如果我们错误地选择素数,离散对数就不存在。
|
||||
|
||||
好的,检查方法:
|
||||
|
||||
1. 选择一个素数 p。这里是 p 的一些条件:
|
||||
|
||||
a. p-1 应该是光滑的。多光滑取决于你,但你将需要在每个这些子群中找到离散对数。你可以使用像 Shanks 或 Pollard's rho 这样的算法在平方根时间内计算这些。
|
||||
|
||||
b. s 不应该在 pad(m) 不在的任何子群中。如果是这样,离散对数就不存在。最简单的做法是确保它们都是原始根。要检查一个元素 g 是否是模 p 的原始根,检查:
|
||||
|
||||
```
|
||||
g^((p-1)/q) != 1 mod p
|
||||
```
|
||||
|
||||
对于 p-1 的每个因子 q。
|
||||
|
||||
2. 现在选择一个素数 q。确保与以前相同的条件,但添加这些:
|
||||
|
||||
a. 除了 2 之外,不要重复使用 p-1 的任何因子。可以使重复因子工作,但这是一个巨大的头痛。最好就避免它。
|
||||
|
||||
b. 确保 p*q 大于 Alice 的模数 N。这只是为了确保签名和填充消息适合你的新模数。
|
||||
|
||||
3. 使用 Pohlig-Hellman 导出 ep = e' mod p 和 eq = e' mod q。
|
||||
|
||||
4. 使用中国剩余定理将 ep 和 eq 放在一起:
|
||||
|
||||
```
|
||||
e' = crt([ep, eq], [p-1, q-1])
|
||||
```
|
||||
|
||||
5. 你的公模数是 N' = p * q。
|
||||
|
||||
6. 你可以以正常方式导出 d'。
|
||||
|
||||
简单如馅饼。e' 将比典型的公指数大得多,但这仍然是合法的。
|
||||
|
||||
由于 RSA 签名和解密是等价的运算,你可以使用这同样的技术获得其他令人惊讶的结果。尝试生成一个随机(或选定的)密文并创建一个密钥来将其解密为你选择的明文!
|
||||
|
||||
@@ -1 +1,258 @@
|
||||
# Challenge 62: Key-Recovery Attacks on ECDSA with Biased Nonces
|
||||
# Challenge 62: 对有偏差 nonce 的 ECDSA 密钥恢复攻击
|
||||
|
||||
回到第 6 组,我们看到 "nonce" 对于 DSA 中的 k 值来说有点名不副实。它更像是一个临时密钥。令人担忧的是,你的长期私钥的安全性依赖于它。
|
||||
|
||||
nonce 泄露?恭喜,你刚刚泄露了你的秘密密钥。
|
||||
|
||||
可预测的 nonce?同样如此。
|
||||
|
||||
甚至通过重复一个 nonce 你就失去了一切。
|
||||
|
||||
我们能把这个推进到多远?事实证明,相当远:即使 nonce 生成中的轻微偏差也足以让攻击者恢复你的私钥。让我们看看如何。
|
||||
|
||||
首先,让我们澄清我们所说的"有偏差" nonce 是什么意思。这种攻击真正需要的只是对 nonce 的几个位的了解。为了简单起见,假设每个 nonce 的低字节为零。所以拿你用于 nonce 生成的任何代码,只需屏蔽掉最低有效的八位。
|
||||
|
||||
这如何帮助我们?让我们回顾签名算法:
|
||||
|
||||
```
|
||||
function sign(m, d):
|
||||
k := random_scalar(1, q)
|
||||
r := (k * G).x
|
||||
s := (H(m) + d*r) * k^-1
|
||||
return (r, s)
|
||||
```
|
||||
|
||||
(快速说明:之前我们使用 "n" 来表示基点的阶。在这个问题中,我将使用 "q" 以避免命名冲突。处理它。)
|
||||
|
||||
专注于 s 计算。观察到如果 k 的低 l 位偏向于某个常数 c,我们可以将 k 重写为 b*2^l + c。在我们的情况下,c = 0,所以我们将 k 重写为 b*2^l。这意味着我们可以这样关联公共的 r 和 s 值:
|
||||
|
||||
```
|
||||
s = (H(m) + d*r) * (b*2^l)^-1
|
||||
```
|
||||
|
||||
一些直接的代数让我们从这里到这里:
|
||||
|
||||
```
|
||||
d*r / (s*2^l) = (H(m) / (-s*2^l)) + b
|
||||
```
|
||||
|
||||
记住这些计算都是模 q,基点的阶。现在,让我们定义一些替代品:
|
||||
|
||||
```
|
||||
t = r / ( s*2^l)
|
||||
u = H(m) / (-s*2^l)
|
||||
```
|
||||
|
||||
现在我们上面的方程可以写成这样:
|
||||
|
||||
```
|
||||
d*t = u + b
|
||||
```
|
||||
|
||||
记住 b 是小的。而 t、u 和秘密密钥 d 都大致是 q 的大小,b 大致是 q/2^l。这是一个舍入误差。由于 b 如此小,我们基本上可以忽略它并说:
|
||||
|
||||
```
|
||||
d*t ~ u
|
||||
```
|
||||
|
||||
换句话说,u 是 d*t mod q 的近似。让我们进一步处理这些数字。由于这是模 q,我们可以说:
|
||||
|
||||
```
|
||||
d*t ~ u + m*q
|
||||
0 ~ u + m*q - d*t
|
||||
```
|
||||
|
||||
那个和不会真的是零 - 它只是一个近似。但它会小于某个界限,比如 q/2^l。关键是它相对于其他相关项会非常小。
|
||||
|
||||
如果我们有足够的 (u, t) 对,我们可以使用这个性质来恢复 d。但要做到这一点,我们需要了解一点线性代数。不会太多,我保证。
|
||||
|
||||
线性代数是关于向量的。向量可以是几乎任何东西,但为了简单起见,我们将说向量是一个固定长度的数字序列。我们可以对向量做两件主要的事情:我们可以相加它们,可以用标量乘以它们。要相加两个向量,只需将它们的对应分量相加。要用标量 k 乘以向量,只需将它加到自身 k 次。(等价地,将它的每个元素乘以标量。)这些运算一起被称为线性组合。
|
||||
|
||||
如果我们有一组向量,我们说它们张成一个向量空间。向量空间就是我们可以通过对集合中的向量进行加法和缩放生成的所有可能向量的完整范围。我们称最小张成集为向量空间的基。"最小"意味着从集合中删除我们的任何向量都会导致更小的向量空间。添加的向量要么是冗余的(即,它们可以定义为现有向量的和),要么它们会给我们一个更大的向量空间。所以你可以认为基对于它张成的向量空间来说是"恰到好处"的。
|
||||
|
||||
我们只使用整数作为我们的标量。仅使用整数标量生成的向量空间称为格。最好在二维平面上想象这个。假设我们的向量集是 {(3, 4), (2, 1)}。格包括这两对的所有整数组合。你可以在纸上画出来以了解想法;你应该得到某种波尔卡圆点图案。
|
||||
|
||||
我们说基对于它张成的向量空间来说是恰到好处的大小,但这不应该被理解为暗示唯一性。确实,我们将关心的任何格都有无限可能的基。唯一的要求是基张成空间,并且基的大小是最小的。在这个意义上,给定格的所有基都是相等的。
|
||||
|
||||
但有些基比其他基"更相等"。在实践中,人们喜欢使用由较短向量组成的基。这里"较短"大致意思是"平均包含较小的分量"。这里一个方便的测量棒是欧几里得范数:简单地取向量与自身的点积并取平方根。或者不取平方根,我不在乎。它不会影响排序。
|
||||
|
||||
为什么人们喜欢这些较小的基?主要是因为它们对计算更有效。老实说,为什么人们喜欢它们并不太重要。重要的是我们有相对有效的方法来"归约"基。给定一个输入基,我们可以产生一个等价但向量短得多的基。短多少?好吧,可能不是最短可能的,但相当短。
|
||||
|
||||
这暗示了一种非常巧妙的问题解决方法:
|
||||
|
||||
1. 将你的问题空间编码为一组向量,形成格的基。你选择的格应该包含你正在寻找的解作为短向量。你不需要知道这个向量(显然,因为你在寻找它),你只需要知道它作为你的基向量的某种整数组合存在。
|
||||
|
||||
2. 导出格的归约基。我们稍后再回到这个。
|
||||
|
||||
3. 从归约基中找出你的解向量。
|
||||
|
||||
4. 就这样。
|
||||
|
||||
等等,就这样?是的,你没听错 - 格基归约是一种极其强大的技术。它在 80 年代单枪匹马地粉碎了背包密码系统,从那时起就获得了大量奖杯。只要你能定义一个包含编码问题解的短向量的格,你就可以让它为你工作。
|
||||
|
||||
显然,定义格是棘手的部分。我们如何编码 ECDSA?好吧,当我们离开时,我们有以下近似:
|
||||
|
||||
```
|
||||
0 ~ u + m*q - d*t
|
||||
```
|
||||
|
||||
假设我们收集了一堆签名。然后那一个近似变成许多:
|
||||
|
||||
```
|
||||
0 ~ u1 + m1*q - d*t1
|
||||
0 ~ u2 + m2*q - d*t2
|
||||
0 ~ u3 + m3*q - d*t3
|
||||
0 ~ u4 + m4*q - d*t4
|
||||
0 ~ u5 + m5*q - d*t5
|
||||
0 ~ u6 + m6*q - d*t6
|
||||
...
|
||||
0 ~ un + mn*q - d*tn
|
||||
```
|
||||
|
||||
每个 u 的系数总是 1,t 的系数总是秘密密钥 d。所以很自然,我们应该将它们排列在两个向量中:
|
||||
|
||||
```
|
||||
bt = [ t1 t2 t3 t4 t5 t6 ... tn ]
|
||||
bu = [ u1 u2 u3 u4 u5 u6 ... un ]
|
||||
```
|
||||
|
||||
每个近似还包含 q 的一些因子。但系数 m 每次都不同。这意味着我们需要每个都有单独的向量:
|
||||
|
||||
```
|
||||
b1 = [ q 0 0 0 0 0 ... 0 ]
|
||||
b2 = [ 0 q 0 0 0 0 ... 0 ]
|
||||
b3 = [ 0 0 q 0 0 0 ... 0 ]
|
||||
b4 = [ 0 0 0 q 0 0 ... 0 ]
|
||||
b5 = [ 0 0 0 0 q 0 ... 0 ]
|
||||
b6 = [ 0 0 0 0 0 q ... 0 ]
|
||||
... ...
|
||||
bn = [ 0 0 0 0 0 0 ... q ]
|
||||
bt = [ t0 t1 t2 t3 t4 t5 ... tn ]
|
||||
bu = [ u0 u1 u2 u3 u4 u5 ... un ]
|
||||
```
|
||||
|
||||
看到跨越我们的行向量的列如何与我们上面收集的近似匹配吗?还要注意,这个基定义的格包含至少一个我们感兴趣的相当短的向量:
|
||||
|
||||
```
|
||||
bu - d*bt + m0*b1 + m1*b2 + m2*b3 ... + mn*bn
|
||||
```
|
||||
|
||||
但我们有一个问题:即使这个向量包含在我们的归约基中,我们也无法识别它。我们可以通过添加几个新列来解决这个问题。
|
||||
|
||||
```
|
||||
b1 = [ q 0 0 0 0 0 ... 0 0 0 ]
|
||||
b2 = [ 0 q 0 0 0 0 ... 0 0 0 ]
|
||||
b3 = [ 0 0 q 0 0 0 ... 0 0 0 ]
|
||||
b4 = [ 0 0 0 q 0 0 ... 0 0 0 ]
|
||||
b5 = [ 0 0 0 0 q 0 ... 0 0 0 ]
|
||||
b6 = [ 0 0 0 0 0 q ... 0 0 0 ]
|
||||
... ...
|
||||
bn = [ 0 0 0 0 0 0 ... q 0 0 ]
|
||||
bt = [ t0 t1 t2 t3 t4 t5 ... tn ct 0 ]
|
||||
bu = [ u0 u1 u2 u3 u4 u5 ... un 0 cu ]
|
||||
```
|
||||
|
||||
我们在 bt 和 bu 中添加了两个带有哨兵值的新列。这将允许我们确定这两个向量是否包含在任何输出向量中以及以什么比例。(这不是这解决的唯一问题。我们的最后一组向量实际上不是一个基,因为我们有 n+2 个 n 度向量,所以其中显然有一些冗余。)
|
||||
|
||||
我们可以通过在我们的归约基的每个向量的最后一个槽中寻找 cu 来识别我们正在寻找的向量。我们的直觉是相邻的槽将包含 -d*ct,我们可以除以 -ct 来恢复 d。
|
||||
|
||||
好的。要进一步进行,我们需要深入了解基归约的具体细节。有不同的策略来为格找到归约基,但我们将专注于一个简单而有效的多项式时间算法:Lenstra-Lenstra-Lovasz (LLL)。
|
||||
|
||||
大多数人不实现 LLL。他们使用库,其中有几个优秀的库。NTL 是一个流行的选择。
|
||||
|
||||
仅出于教学目的,我们将编写自己的。
|
||||
|
||||
这里是一些伪代码:
|
||||
|
||||
```
|
||||
function LLL(B, delta):
|
||||
B := copy(B)
|
||||
Q := gramschmidt(B)
|
||||
|
||||
function mu(i, j):
|
||||
v := B[i]
|
||||
u := Q[j]
|
||||
return (v*u) / (u*u)
|
||||
|
||||
n := len(B)
|
||||
k := 1
|
||||
|
||||
while k < n:
|
||||
for j in reverse(range(k)):
|
||||
if abs(mu(k, j)) > 1/2:
|
||||
B[k] := B[k] - round(mu(k, j))*B[j]
|
||||
Q := gramschmidt(B)
|
||||
|
||||
if (Q[k]*Q[k]) >= (delta - mu(k, k-1)^2) * (Q[k-1]*Q[k-1]):
|
||||
k := k + 1
|
||||
else:
|
||||
B[k], B[k-1] := B[k-1], B[k]
|
||||
Q := gramschmidt(B)
|
||||
k := max(k-1, 1)
|
||||
|
||||
return B
|
||||
```
|
||||
|
||||
B 是我们的输入基。Delta 是一个参数,使得 0.25 < delta <= 1。你可以将其设置为 0.99 并忘记它。
|
||||
|
||||
Gram-Schmidt 是一个算法,将基转换为相互正交("垂直"的花哨词)向量的等价基。它非常简单:
|
||||
|
||||
```
|
||||
function proj(u, v):
|
||||
if u = 0:
|
||||
return 0
|
||||
return ((v*u) / (u*u)) * u
|
||||
|
||||
function gramschmidt(B):
|
||||
Q := []
|
||||
for i, v in enumerate(B):
|
||||
Q[i] := v - sum(proj(u, v) for u in Q[:i])
|
||||
return Q
|
||||
```
|
||||
|
||||
Proj 找到 v 在 u 上的投影。这基本上是 v 在与 u 相同"方向"上的部分。如果 u 和 v 正交,这是零向量。Gram-Schmidt 通过遍历原始基并剔除这些投影来正交化基。
|
||||
|
||||
回到 LLL。了解它如何以及为什么工作的最佳方法是实现它并在一些小例子上测试它,输出大量调试信息。但基本上:我们在基 B 中上下走动,将每个向量 b 与正交化基 Q 进行比较。每当我们找到 Q 中与 b 大部分对齐的向量 q 时,我们就剔除 q 在 b 上的投影的整数近似。记住格处理整数系数,我们也必须如此。在每次迭代后,我们使用一些启发式来决定是否应该在 B 中前进或后退,是否应该交换一些行等等。
|
||||
|
||||
还有一件事:上面的 LLL 描述非常朴素且低效。它可能对我们的目的来说不够快,所以你可能需要稍微优化它。一个好的开始是不在每次更新时重新计算整个 Q 矩阵。
|
||||
|
||||
这里是一个测试基:
|
||||
|
||||
```
|
||||
b1 = [ -2 0 2 0]
|
||||
b2 = [ 1/2 -1 0 0]
|
||||
b3 = [ -1 0 -2 1/2]
|
||||
b4 = [ -1 1 1 2]
|
||||
```
|
||||
|
||||
它归约为这个(delta = 0.99):
|
||||
|
||||
```
|
||||
b1 = [ 1/2 -1 0 0]
|
||||
b2 = [ -1 0 -2 1/2]
|
||||
b3 = [-1/2 0 1 2]
|
||||
b4 = [-3/2 -1 2 0]
|
||||
```
|
||||
|
||||
我忘记提到:你会想编写你的实现以处理有理数向量。如果你有无限精度浮点数,那些也可以工作。
|
||||
|
||||
剩下的就是整理一些松散的结尾。首先,我们如何选择我们的哨兵值 ct 和 cu?这有点像实现细节,但我们想"平衡"我们目标向量中条目的大小。由于我们期望所有其他条目大约是 q/2^l 的大小:
|
||||
|
||||
```
|
||||
ct = 1/2^l
|
||||
cu = q/2^l
|
||||
```
|
||||
|
||||
记住 ct 将被乘以 -d,而 d 大约是 q 的大小。
|
||||
|
||||
好的,你终于准备好运行攻击了:
|
||||
|
||||
1. 生成你的 ECDSA 秘密密钥 d。
|
||||
|
||||
2. 使用 d 和你的有偏差 nonce 生成器签名一堆消息。
|
||||
|
||||
3. 作为攻击者,收集你的 (u, t) 对。你可以尝试数量。使用八位 nonce 偏差,我在仅 20 个签名的情况下就得到了良好的结果。你的里程可能会有所不同。
|
||||
|
||||
4. 将你的值填入矩阵并用 LLL 归约它。考虑用一些较小的矩阵来了解这将需要运行多长时间。
|
||||
|
||||
5. 在归约基中,找到最后一项为 q/2^l 的向量。很有可能它的倒数第二项为 -d/2^l。提取 d。
|
||||
|
||||
@@ -1 +1,343 @@
|
||||
# Challenge 63: Key-Recovery Attacks on GCM with Repeated Nonces
|
||||
# Challenge 63: 对重复 Nonce 的 GCM 密钥恢复攻击
|
||||
|
||||
GCM 是认证加密和关联数据(AEAD)最广泛部署的分组密码模式。它基本上就是 CTR 模式,外面包了一个奇怪的 MAC 函数。MAC 函数通过在 GF(2^128) 上求多项式值来工作。
|
||||
|
||||
还记得重复 nonce 对 CTR 模式加密造成多大麻烦吗?同样的情况在这里也是如此:攻击者可以将密文异或在一起并使用统计方法恢复明文。
|
||||
|
||||
但 GCM 有一个更具破坏性的后果:它立即泄漏认证密钥!
|
||||
|
||||
这里是高层视图:
|
||||
|
||||
1. GCM MAC 函数(GMAC)通过构建一个多项式来工作,该多项式的系数是关联数据(AD)的块、密文(C)的块、编码 AD 和 C 长度的块,以及用于"掩码"输出的块。大概像这样:
|
||||
|
||||
```
|
||||
AD*y^3 + C*y^2 + L*y + S
|
||||
```
|
||||
|
||||
为了计算 MAC,我们将认证密钥代入 y 并求值。
|
||||
|
||||
2. AD、C 及其各自的长度是已知的。对于给定的消息,攻击者知道 MAC 多项式的一切,除了掩码块
|
||||
|
||||
3. 掩码块仅使用密钥和 nonce 生成。如果 nonce 重复,掩码是相同的。如果我们可以收集在同一 nonce 下加密的两个消息,它们将使用相同的掩码。
|
||||
|
||||
4. 在这个域中,加法是异或。我们可以将两个消息异或在一起以"洗掉"掩码并恢复一个已知多项式,该多项式以认证密钥为根。我们可以分解多项式以立即恢复认证密钥。
|
||||
|
||||
最后一步可能感觉有点神奇,但你实际上不需要理解它来实现攻击:你可以literally只需将正确的值插入像 SageMath 这样的计算代数系统并点击"分解"。
|
||||
|
||||
但这不令人满意。你没有走这么远来在简单模式下击败游戏,是吗?
|
||||
|
||||
我不这么认为。现在,让我们深入了解那个 MAC 函数。如我所说,在 GF(2^128) 上的多项式。
|
||||
|
||||
到目前为止,我们使用的所有域都是素数大小 p 的,即 GF(p)。事实证明,我们可以为任何 q = p^k 构建 GF(q),对于任何正整数 k。GF(p) = GF(p^1) 只是在密码学中常见的一种形式。另一种是 GF(2^k),在这种情况下我们有 GF(2^128)。
|
||||
|
||||
对于 GF(2^k),我们将其元素表示为 GF(2) 中系数的多项式。这只是意味着每个系数将是 0 或 1。在我们开始谈论特定域(即 k 的特定选择)之前,让我们先谈论这些多项式。这里是其中一些:
|
||||
|
||||
```
|
||||
0
|
||||
1
|
||||
x
|
||||
x + 1
|
||||
x^2
|
||||
x^2 + 1
|
||||
x^2 + x
|
||||
x^2 + x + 1
|
||||
x^3
|
||||
x^3 + 1
|
||||
x^3 + x
|
||||
x^3 + x + 1
|
||||
x^3 + x^2
|
||||
x^3 + x^2 + 1
|
||||
x^3 + x^2 + x
|
||||
x^3 + x^2 + x + 1
|
||||
...
|
||||
```
|
||||
|
||||
等等。
|
||||
|
||||
如果你眯起眼睛看,它们看起来像从零开始计数的整数的二进制展开。这很方便,因为它给我们一个在无符号整数中的明显表示选择。
|
||||
|
||||
现在我们需要一些操作这些多项式的原始函数。让我们装备工具:
|
||||
|
||||
1. GF(2) 多项式之间的加法和减法非常简单:它们都只是异或函数。
|
||||
|
||||
2. 乘法和除法有点棘手,但它们都只是近似你在小学学到的算法。这里是它们:
|
||||
|
||||
```
|
||||
function mul(a, b):
|
||||
p := 0
|
||||
|
||||
while a > 0:
|
||||
if a & 1:
|
||||
p := p ^ b
|
||||
|
||||
a := a >> 1
|
||||
b := b << 1
|
||||
|
||||
return p
|
||||
|
||||
function divmod(a, b):
|
||||
q, r := 0, a
|
||||
|
||||
while deg(r) >= deg(b):
|
||||
d := deg(r) - deg(b)
|
||||
q := q ^ (1 << d)
|
||||
r := r ^ (b << d)
|
||||
|
||||
return q, r
|
||||
```
|
||||
|
||||
deg(a) 是返回多项式次数的函数。对于多项式 x^4 + x + 1,它应该返回 4。对于 1,它应该返回 0。对于 0,它应该返回某个负值。
|
||||
|
||||
现在我们有了在 GF(2) 上处理多项式的小核心函数,让我们看看如何使用它们来表示 GF(2^k) 中的元素。为了具体起见,让我们说 k = 4。
|
||||
|
||||
我们的元素集就是上面列举的那些:
|
||||
|
||||
```
|
||||
0
|
||||
1
|
||||
x
|
||||
x + 1
|
||||
x^2
|
||||
x^2 + 1
|
||||
x^2 + x
|
||||
x^2 + x + 1
|
||||
x^3
|
||||
x^3 + 1
|
||||
x^3 + x
|
||||
x^3 + x + 1
|
||||
x^3 + x^2
|
||||
x^3 + x^2 + 1
|
||||
x^3 + x^2 + x
|
||||
x^3 + x^2 + x + 1
|
||||
```
|
||||
|
||||
加法和减法没有变化。我们仍然只是将元素异或在一起。
|
||||
|
||||
乘法不同。与大小为 p 的域一样,我们需要在每次乘法后执行模约简以保持我们的元素在范围内。我们的模数将是 x^4 + x + 1。如果这看起来有点任意,确实如此 - 我们可以使用任何在 GF(2) 上不可约的四次首一多项式。不可约多项式在这个设置中有点类似于素数。
|
||||
|
||||
这里是一个朴素的 modmul:
|
||||
|
||||
```
|
||||
function modmul(a, b, m):
|
||||
p := mul(a, b)
|
||||
q, r := divmod(p, m)
|
||||
return r
|
||||
```
|
||||
|
||||
在实践中,我们会想要更高效。所以我们将乘法的步骤与约简的步骤交错:
|
||||
|
||||
```
|
||||
function modmul(a, b, m):
|
||||
p := 0
|
||||
|
||||
while a > 0:
|
||||
if a & 1:
|
||||
p := p ^ b
|
||||
|
||||
a := a >> 1
|
||||
b := b << 1
|
||||
|
||||
if deg(b) = deg(m):
|
||||
b := b ^ m
|
||||
|
||||
return p
|
||||
```
|
||||
|
||||
你可以实现两个版本以向自己证明输出是相同的。
|
||||
|
||||
除法也不同。记住在大小为 p 的域中,我们将其定义为乘以逆元。所以你需要编写一个 modinv 函数。翻译你现有的整数 modinv 函数应该相当容易。我将把这个留给你。
|
||||
|
||||
你可能会发现自己需要在整数设置中理所当然的其他函数,例如 modexp。这些大多数应该在我们的多项式设置中有直接的等价物。做你需要做的。
|
||||
|
||||
好的,现在你是 GF(2^k) 的大师,我们终于可以谈论 GCM 了。如我所说(许多话之前):加密用 CTR 模式,在 GF(2^128) 中的奇怪 MAC。
|
||||
|
||||
这里是那个域的模数:
|
||||
|
||||
```
|
||||
x^128 + x^7 + x^2 + x + 1
|
||||
```
|
||||
|
||||
这个域的大小被非常具体地选择以匹配 128 位分组密码的宽度。我们可以很简单地将块转换为域元素;最左位是 x^0 的系数,依此类推。
|
||||
|
||||
我在非常高的层次上描述了 MAC。这里是更详细的视图:
|
||||
|
||||
1. 取你的 AES 密钥并使用它加密一个零块:
|
||||
|
||||
```
|
||||
h := E(K, 0)
|
||||
```
|
||||
|
||||
h 是你的认证密钥。将其转换为域元素。
|
||||
|
||||
2. 将关联数据(AD)的字节零填充为可被块长度整除。如果它已经在块上对齐,就不要管它。对密文做同样的事情。将它们链接在一起,这样你有类似的东西:
|
||||
|
||||
```
|
||||
a0 || a1 || c0 || c1 || c2
|
||||
```
|
||||
|
||||
3. 添加描述 AD 长度和密文长度的最后一个块。原始长度,不是填充长度;位长度,不是字节长度。像这样:
|
||||
|
||||
```
|
||||
len(AD) || len(C)
|
||||
```
|
||||
|
||||
4. 取 h 和你的块字符串并执行这个:
|
||||
|
||||
```
|
||||
g := 0
|
||||
for b in bs:
|
||||
g := g + b
|
||||
g := g * h
|
||||
```
|
||||
|
||||
当然首先将块转换为域元素。结果 g 值是输入块的键控散列。
|
||||
|
||||
5. GCM 取一个 96 位 nonce。用它做这个:
|
||||
|
||||
```
|
||||
s := E(K, nonce || 1)
|
||||
t := g + s
|
||||
```
|
||||
|
||||
从概念上讲,我们用 nonce 派生的秘密值掩码散列。稍后更多细节。
|
||||
|
||||
t 是你的标签。将其转换回块并发送。
|
||||
|
||||
实现 GCM。使用 AES-128 作为你的分组密码。你可能可以重用你之前为 CTR 模式拥有的任何东西。这里要实现的重要新东西是 MAC。上述描述简短且非正式;查看规范了解更细致的点。由于你已经有了在 GF(2^k) 中工作的工具,这不应该花太长时间。
|
||||
|
||||
好的。让我们重新思考我们对 MAC 的看法。我们将使用上面的示例负载。这里是它:
|
||||
|
||||
```
|
||||
t = (((((((((((h * a0) + a1) * h) + c0) * h) + c1) * h) + c2) * h) + len) * h) + s
|
||||
```
|
||||
|
||||
有点拗口。让我们重写它:
|
||||
|
||||
```
|
||||
t = a0*h^6 + a1*h^5 + c0*h^4 + c1*h^3 + c2*h^2 + len*h + s
|
||||
```
|
||||
|
||||
换句话说,我们通过构建这个多项式来计算 MAC:
|
||||
|
||||
```
|
||||
f(y) = a0*y^6 + a1*y^5 + c0*y^4 + c1*y^3 + c2*y^2 + len*y + s
|
||||
```
|
||||
|
||||
并计算 t = f(h)。
|
||||
|
||||
记住:作为攻击者,我们不知道整个多项式。我们知道所有 AD 和密文系数,我们知道 t = f(h),但我们不知道掩码 s。
|
||||
|
||||
如果我们重复 nonce 会发生什么?让我们假设这个额外的负载在相同 nonce 下加密:
|
||||
|
||||
```
|
||||
b0 || d0 || d1
|
||||
```
|
||||
|
||||
那是一个 AD 块和两个密文块。MAC 将看起来像这样:
|
||||
|
||||
```
|
||||
t = b0*h^4 + d0*h^3 + d1*h^2 + len*h + s
|
||||
```
|
||||
|
||||
让我们并排放置它们(并稍微重写它们):
|
||||
|
||||
```
|
||||
t0 = a0*h^6 + a1*h^5 + c0*h^4 + c1*h^3 + c2*h^2 + l0*h + s
|
||||
t1 = b0*h^4 + d0*h^3 + d1*h^2 + l1*h + s
|
||||
```
|
||||
|
||||
看到 s 掩码是相同的吗?它们仅依赖于 nonce 和加密密钥。由于我们域中的加法是异或,我们可以将这两个方程加在一起,掩码就会洗掉:
|
||||
|
||||
```
|
||||
t0 + t1 = a0*h^6 + a1*h^5 + (c0 + b0)*h^4 + (c1 + d0)*h^3 +
|
||||
(c2 + d1)*h^2 + (l0 + l1)*h
|
||||
```
|
||||
|
||||
最后,我们将所有项收集到一边:
|
||||
|
||||
```
|
||||
0 = a0*h^6 + a1*h^5 + (c0 + b0)*h^4 + (c1 + d0)*h^3 +
|
||||
(c2 + d1)*h^2 + (l0 + l1)*h + (t0 + t1)
|
||||
```
|
||||
|
||||
并将其重写为 y 的多项式:
|
||||
|
||||
```
|
||||
f(y) = a0*y^6 + a1*y^5 + (c0 + b0)*y^4 + (c1 + d0)*y^3 +
|
||||
(c2 + d1)*y^2 + (l0 + l1)*y + (t0 + t1)
|
||||
```
|
||||
|
||||
现在我们知道多项式 f(y),并且我们知道 f(h) = 0。换句话说,认证密钥是根。这意味着我们所要做的就是分解方程以获得认证密钥的极短候选列表。这证明不是那么困难,但我们需要更多工具。
|
||||
|
||||
首先,我们需要能够操作系数在 GF(2^128) 中的多项式。
|
||||
|
||||
(不要搞混:之前我们使用 GF(2) 中系数的多项式来表示 GF(2^128) 中的元素。现在我们在其上构建以处理 GF(2^128) 中系数的多项式。)
|
||||
|
||||
最简单的表示可能就是域元素的数组。算法将与上面基本相同,所以我不会在这里重申它们。唯一的区别是你需要调用你的 GF(2^k) 多项式原始函数来代替语言内置的算术运算符。
|
||||
|
||||
解决了这个问题,让我们开始分解。在有限域上分解多项式意味着将其分离为在该域上不可约的较小多项式。记住不可约多项式有点像素数。
|
||||
|
||||
要分解多项式,我们分三(好吧,四)个阶段进行:
|
||||
|
||||
0. 作为预备步骤,我们需要将多项式转换为首一多项式。那只是意味着首项系数为 1。所以取你的多项式并除以首项的系数。如果你想的话,你可以将系数保存为零次因子,但对我们的目的来说这并不重要。
|
||||
|
||||
1. 这是真正的第一步:我们执行无平方分解。我们找出任何重复因子(即"平方")并将它们分离出来。
|
||||
|
||||
2. 接下来,我们取每个无平方多项式并找到其不同次数分解。这将分离出相等次数的较小多项式的乘积的多项式。所以如果我们的多项式有三个四次不可约因子,这将分离出一个十二次多项式,它是所有这些的乘积。
|
||||
|
||||
3. 最后,我们取上一步的每个输出并执行等次数分解。这大致如其听起来。在最后那个例子中,我们取那个十二次多项式并将其分解为四次分量。
|
||||
|
||||
无平方分解和不同次数分解都很容易实现。只需在 Wikipedia 上找到它们并开始工作。
|
||||
|
||||
我想专注于等次数分解。认识 Cantor-Zassenhaus:
|
||||
|
||||
```
|
||||
function edf(f, d):
|
||||
n := deg(f)
|
||||
r := n / d
|
||||
S := {f}
|
||||
|
||||
while len(S) < r:
|
||||
h := random_polynomial(1, f)
|
||||
g := gcd(h, f)
|
||||
|
||||
if g = 1:
|
||||
g := h^((q^d - 1)/3) - 1 mod f
|
||||
|
||||
for u in S:
|
||||
if deg(u) = d:
|
||||
continue
|
||||
|
||||
if gcd(g, u) =/= 1 and gcd(g, u) =/= u:
|
||||
S := union(S - {u}, {gcd(g, u), u / gcd(g, u)})
|
||||
|
||||
return S
|
||||
```
|
||||
|
||||
这有点精彩。
|
||||
|
||||
记住我们之前说过大小为 p^k 的有限域可以表示为 GF(p) 中的多项式模任何首一、不可约的 k 次多项式?花点时间说服自己大小为 q^d 的域可以用 GF(q) 中的多项式模 d 次多项式来表示。
|
||||
|
||||
f 是 r 个 d 次多项式的乘积。它们每个都是大小为 q^d 的有限域的有效模数。每个那个大小的域都包含大小为 q^d - 1 的乘法群。由于 q^d - 1 总是能被 3 整除(在我们的情况下),那个大小的每个群都有大小为 3 的子群。它包含乘法恒等元(1)和两个其他元素。
|
||||
|
||||
我们有一个简单的技巧将元素强制到那个子群:简单地将它们提升到指数 (q^d - 1)/3。当我们将随机元素强制到那个子群时,有 1/3 的机会我们将降落在 1 上。这意味着当我们减 1 时,有 1/3 的机会我们将坐在 0 上。
|
||||
|
||||
唯一的问题是我们不知道这些模数是什么。但我们不需要!由于 f 是它们的乘积,我们可以执行这些运算 mod f 并隐式应用中国剩余定理。
|
||||
|
||||
所以我们这样做:生成一些多项式,将其提升到 (q^d - 1)/3,并减 1。(当然都是 mod f。)计算这个多项式与我们每个剩余复合数的 GCD。对于任何给定的剩余因子,我们的多项式有 1/3 的机会是倍数。换句话说,我们的因子应该相当快地显现出来。
|
||||
|
||||
只要继续这样做直到整个东西分解为不可约部分。
|
||||
|
||||
一旦多项式被分解,我们可以悠闲地挑选认证密钥。将至少有一个一次因子。像这样:
|
||||
|
||||
```
|
||||
y + c
|
||||
```
|
||||
|
||||
其中 c 是 GF(2^128) 的常数元素。它也是密钥的候选者。你可能会得到几个这样的一次因子。密钥将是其中之一。
|
||||
|
||||
如果你确实有多个候选者,有两种方法缩小列表:
|
||||
|
||||
1. 恢复在相同 nonce 下加密的另一对消息。再次执行分解并识别公共因子。密钥可能是唯一的一个。
|
||||
|
||||
2. 只是尝试用每个候选者进行伪造。这可能更容易。
|
||||
|
||||
@@ -1 +1,188 @@
|
||||
# Challenge 64: Key-Recovery Attacks on GCM with a Truncated Counter
|
||||
# Challenge 64: 对截断 MAC 的 GCM 密钥恢复攻击
|
||||
|
||||
这个是我的最爱。
|
||||
|
||||
使用截断的 MAC 标签是相当常见的。例如,你可能使用 HMAC-SHA256 进行认证并将标签缩短到 128 位。想法是你可以节省一些带宽或存储空间,仍然有可接受的安全级别。
|
||||
|
||||
这是一个完全合理的想法。
|
||||
|
||||
在一些协议中,你可能会将此推向极端。如果两方正在交换大量小数据包,并且伪造任何一个数据包的价值相当低,他们可能使用 16 位标签并期望 16 位的安全性。
|
||||
|
||||
在 GCM 中,这是一个灾难。
|
||||
|
||||
要了解如何,我们首先回顾 GCM MAC 函数。我们进行这样的计算:
|
||||
|
||||
```
|
||||
t = s + c1*h + c2*h^2 + c3*h^3 + ... + cn*h^n
|
||||
```
|
||||
|
||||
为了方便起见,我们在这里做一些记号上的改变;最明显的是,我们使用基于一的索引。c1 块编码长度,[c2, cn] 是密文块。
|
||||
|
||||
我们也会在这里忽略 AD 块的可能性,因为它们对我们的目的不太重要。
|
||||
|
||||
回想一下我们的系数(和我们的认证密钥 h)是 GF(2^128) 中的元素。我们已经看到了几种不同的表示,即 GF(2) 中的多项式和无符号整数。为此我们将添加一个:GF(2) 上的 128 次向量。它基本上就是一个位向量;与我们之前的任何表示都没有太大不同,但我们想让我们的头脑进入线性代数模式。
|
||||
|
||||
我们想在这里探索的关键概念是 GF(2^128) 中的某些运算是线性的。
|
||||
|
||||
其中之一是乘以常数。假设我们有这个函数:
|
||||
|
||||
```
|
||||
f(y) = c*y
|
||||
```
|
||||
|
||||
c 和 y 都是 GF(2^128) 的元素。这个函数在 y 的位中是线性的。这意味着这个函数是等价的:
|
||||
|
||||
```
|
||||
g(y) = c*y[0] + c*y[1] + c*y[2] + ... + c*y[127]
|
||||
```
|
||||
|
||||
任何线性函数都可以用矩阵乘法表示。所以如果我们将 y 看作向量,我们可以构造一个矩阵 Mc 使得:
|
||||
|
||||
```
|
||||
Mc*y = f(y) = c*y
|
||||
```
|
||||
|
||||
要构造 Mc,只需计算 c*1, c*x, c*x^2, ..., c*x^127,将每个乘积转换为向量,并让向量成为你的矩阵的列。你可以通过对 y 执行矩阵乘法并检查结果来验证。我假设你要么知道矩阵乘法如何工作,要么可以访问 Wikipedia 查阅。
|
||||
|
||||
平方也是线性的。这是因为在 GF(2^128) 中 (a + b)^2 = a^2 + b^2。同样,这意味着我们可以替换这个函数:
|
||||
|
||||
```
|
||||
f(y) = y^2
|
||||
```
|
||||
|
||||
用矩阵乘法。同样,计算 1^2, x^2, (x^2)^2, ..., (x^127)^2,将结果转换为向量,并使它们成为你的列。然后验证:
|
||||
|
||||
```
|
||||
Ms*y = f(y) = y^2
|
||||
```
|
||||
|
||||
好的,让我们暂时将这些矩阵放在一边。要伪造密文 c',我们将从有效的密文 c 开始并翻转一些位,希望:
|
||||
|
||||
```
|
||||
sum(ci * h^i) = sum(ci' * h^i)
|
||||
```
|
||||
|
||||
另一种写法是:
|
||||
|
||||
```
|
||||
sum((ci - ci') * h^i) = 0
|
||||
```
|
||||
|
||||
如果我们让 ei = ci - ci',我们可以简化这个:
|
||||
|
||||
```
|
||||
sum(ei * h^i) = 0
|
||||
```
|
||||
|
||||
注意如果我们不动一个块,那么 ei = ci - ci' = 0。我们将让大多数 ei = 0。实际上,我们只打算在块 di = e(2^i) 中翻转位。这些是块 d0, d1, d2, ..., dn(注意我们回到零索引),使得:
|
||||
|
||||
```
|
||||
sum(di * h^(2^i)) = 0
|
||||
```
|
||||
|
||||
我们希望它等于零,反正。也许说这个更好:
|
||||
|
||||
```
|
||||
sum(di * h^(2^i)) = e
|
||||
```
|
||||
|
||||
其中 e 是某个错误多项式。换句话说,我们的位翻转引起的 MAC 标签差异。
|
||||
|
||||
在这一点上,我们将回忆矩阵视图。回想一下乘以常数和平方运算都是线性的。这意味着我们可以将上述方程重写为 h 上的线性运算:
|
||||
|
||||
```
|
||||
sum(Mdi * Ms^i * h) = e
|
||||
sum(Mdi * Ms^i) * h = e
|
||||
|
||||
Ad = sum(Mdi * Ms^i)
|
||||
|
||||
Ad * h = e
|
||||
```
|
||||
|
||||
我们想找到一个 Ad 使得 Ad * h = 0。
|
||||
|
||||
让我们考虑向量 e 中的位是如何计算的。这只是从矩阵乘法的基本规则中得出的:
|
||||
|
||||
```
|
||||
e[0] = Ad[0] * h
|
||||
e[1] = Ad[1] * h
|
||||
e[2] = Ad[2] * h
|
||||
e[3] = Ad[3] * h
|
||||
...
|
||||
```
|
||||
|
||||
换句话说,e[i] 是 Ad 第 i 行与 h 的内积。如果我们可以强制 Ad 的行为零,我们可以强制错误多项式的项为零。我们强制为零的每一行基本上都会将我们伪造的机会翻倍。
|
||||
|
||||
假设 MAC 是 16 位。如果我们可以翻转位并强制 Ad 的八行为零,那是 MAC 的八位我们知道是对的。我们可以翻转剩下的任何位,有 2^-8 的伪造机会,比预期的 2^-16 好得多!
|
||||
|
||||
事实证明强制 Ad 的行为零真的很容易。Ad 是一堆线性运算的和。这意味着我们可以简单地确定 d0, d1, ..., dn 的哪些位影响 Ad 的哪些位并相应地翻转它们。
|
||||
|
||||
实际上,让我们让 d0 独自待着。那是编码密文长度的块。如果我们开始搞它,事情可能很快变得棘手。
|
||||
|
||||
我们仍然有 d1, ..., dn 可以玩。这意味着 n*128 位我们可以翻转。由于 Ad 的行每个都是 128 位,我们必须满足于强制其中 n-1 个为零。我们需要留一些位来玩。
|
||||
|
||||
检查策略:我们将构建一个依赖矩阵 T,有 n*128 列和 (n-1)*128 行。每列表示我们可以翻转的一位,每行表示 Ad 的一个单元(从左到右、从上到下读取)。它们相交的单元记录特定的自由位是否影响 Ad 的特定位。
|
||||
|
||||
遍历列。构建你通过仅翻转相应位会得到的假设 Ad。遍历 Ad 的前 (n-1)*128 个单元,并在 T 的这一列中设置相应的单元。
|
||||
|
||||
为每列做这个之后,T 将充满一和零。我们正在寻找将使前 n-1 行为零的位翻转集合。换句话说,我们正在寻找这个方程的解:
|
||||
|
||||
```
|
||||
T * d = 0
|
||||
```
|
||||
|
||||
其中 d 是一个向量,表示你可以玩的所有 n*128 位。
|
||||
|
||||
如果你了解一点线性代数,你会知道我们真正想要找到的是 N(T),T 的零空间的基。零空间正是解决上述方程的向量集合。正是我们正在寻找的。记住基是其线性组合张成整个空间的最小向量集。所以如果我们找到 N(T) 的基,我们可以只取其向量的随机组合来获得 d 的可行候选者。
|
||||
|
||||
找到零空间的基不太难。你想要做的是转置 T(即沿其对角线翻转它)并使用高斯消元找到简化行阶梯形式。现在对大小为 n*128 的单位矩阵执行相同的操作。对应于 T 转置的简化行阶梯形式中零行的行形成 N(T) 的基。
|
||||
|
||||
高斯消元相当简单;一旦你知道它应该做什么,你多少可以自己弄清楚。
|
||||
|
||||
现在我们有了 N(T) 的基,我们准备开始伪造消息。从 N(T) 中取一个随机向量并将其解码为对你的已知良好密文 C 的一堆位翻转。(记住你只会翻转由 h^(2*i) 对某些 i 乘以的块中的位。)将调整后的消息 C' 发送到预言机,看它是否通过认证。如果失败,生成一个新向量并再试一次。
|
||||
|
||||
如果成功,我们获得的不仅仅是一个容易的伪造。检查你的矩阵 Ad。它应该是一堆零行,后面跟着一堆非零行。我们关心对应于标签位的非零行。所以如果你的标签是 16 位,并且你强制八位为零,你应该有八个感兴趣的非零行。
|
||||
|
||||
挑出那些行并将它们塞进自己的矩阵。称它为,我不知道,K。这里是我们对 K 了解的一些巧妙的东西:
|
||||
|
||||
```
|
||||
K * h = 0
|
||||
```
|
||||
|
||||
换句话说,h 在 K 的零空间中!在我们的例子中,K 是一个 8x128 矩阵。假设它的所有行都是独立的(它们中没有一个是任何其他的组合),N(K) 是更大的 128 维空间的 120 维子空间。由于我们知道 h 在那里,h 的可能值范围从 2^128 降到 2^120。
|
||||
|
||||
2^120 仍然是很多值,但嘿 - 这是一个开始。
|
||||
|
||||
如果我们可以产生更多伪造,我们可以找到更多向量添加到 K,进一步缩小值的范围。查看这个:我们对 h 的新认识将使下一次伪造更容易。找到 N(K) 的基并将向量放在矩阵的列中。称它为 X。现在我们可以像这样重写 h:
|
||||
|
||||
```
|
||||
h = X * h'
|
||||
```
|
||||
|
||||
其中 h' 是某个未知的 120 位向量。现在不是说:
|
||||
|
||||
```
|
||||
Ad * h = e
|
||||
```
|
||||
|
||||
我们可以说:
|
||||
|
||||
```
|
||||
Ad * X * h' = e
|
||||
```
|
||||
|
||||
而 Ad 是 128x128 矩阵,X 是 128x120。Ad * X 也是 128x120。不是将 128 次行向量清零,现在我们可以清零 120 次向量。由于我们仍然有相同数量的位可以玩,我们可以(也许)比以前清零更多行。总的图片是,如果我们有 n*128 位可以玩,我们可以清零 (n*128) / (ncols(X)) 行。只要记住在每次尝试中至少留一个非零行;否则你不会学到任何新东西。
|
||||
|
||||
所以:重新开始并构建一个新的 T 矩阵,但这次是为了使 Ad * X 的行无效。伪造另一个消息并收获一些关于 h 的新线性方程。将它们塞进 K 并重新计算 X。
|
||||
|
||||
冲洗,漂洗,重复。
|
||||
|
||||
当 K 有 127 个线性独立行时,终局到来。N(K) 将是一个包含恰好一个非零向量的 1 维子空间,那个向量将是 h。
|
||||
|
||||
让我们试试:
|
||||
|
||||
1. 构建一个 32 位 MAC 的玩具系统。这是 NIST 在 GCM 规范中定义的最小标签长度。
|
||||
|
||||
2. 为攻击者生成 2^17 块的有效消息来玩。
|
||||
|
||||
3. 作为攻击者,构建你的矩阵,伪造消息,并恢复密钥。你应该能够开始时将每个标签的 16 位清零,从那里你只会获得杠杆。
|
||||
|
||||
Reference in New Issue
Block a user