近期研究格密码过程中遇到的一个很好的题目,记录一下。
题目源码:
from Crypto.Util.number import *
from Crypto.Cipher import AES
from secret import flag
class LCG:
def __init__(self, bit_length):
m = getPrime(bit_length)
a = getRandomRange(2, m)
b = getRandomRange(2, m)
seed = getRandomRange(2, m)
self._key = {'a':a, 'b':b, 'm':m}
self._state = seed
def next(self):
self._state = (self._key['a'] * self._state + self._key['b']) % self._key['m']
return self._state
def export_key(self):
return self._key
def gen_lcg():
rand_iter = LCG(128)
key = rand_iter.export_key()
f = open("key", "w")
f.write(str(key))
return rand_iter
def leak_data(rand_iter):
f = open("old", "w")
for i in range(20):
f.write(str(rand_iter.next() >> 64) + "\n")
return rand_iter
def encrypt(rand_iter):
f = open("ct", "wb")
key = rand_iter.next() >> 64
key = (key << 64) + (rand_iter.next() >> 64)
key = long_to_bytes(key).ljust(16, b'\x00')
iv = long_to_bytes(rand_iter.next()).ljust(16, b'\x00')
cipher = AES.new(key, AES.MODE_CBC, iv)
pt = flag + (16 - len(flag) % 16) * chr(16 - len(flag) % 16)
ct = cipher.encrypt(pt.encode())
f.write(ct)
def main():
rand_iter = gen_lcg()
rand_iter = leak_data(rand_iter)
encrypt(rand_iter)
if __name__ == "__main__":
main()
输出内容:
#key
{'b': 153582801876235638173762045261195852087, 'a': 107763262682494809191803026213015101802, 'm': 226649634126248141841388712969771891297}
#old
7800489346663478448
11267068470666042741
5820429484185778982
6151953690371151688
548598048162918265
1586400863715808041
7464677042285115264
4702115170280353188
5123967912274624410
8517471683845309964
2106353633794059980
11042210261466318284
4280340333946566776
6859855443227901284
3149000387344084971
7055653494757088867
5378774397517873605
8265548624197463024
2898083382910841577
4927088585601943730
#ct十六进制显示
00000000h: E0 E0 70 2C B7 D6 FC 19 AC E7 29 BE FC A4 0A F5 ; ààp,·Öü.¬ç)¾ü¤.õ
00000010h: 01 23 F7 A6 ED E1 F3 AE 4B 3A 2A CD 7B 1D D8 37 ; .#÷¦íáó®K:*Í{.Ø7
00000020h: 14 73 10 58 86 AF D1 57 73 44 1F A0 6C 65 6A D6 ; .s.X.¯ÑWsD..lejÖ
大概分析一下逻辑
- 首先调用genlcg方法生成了一个LCG加密类,参数m为128bit随机素数,a, b, seed均为随机整数,2<a,b,seed<m,key文件中给出了a, b, m参数值,可以认为是公钥
- 然后调用leakdata方法,生成20个位数小于128bit随机数,生成算法为 s k + 1 = a s k + b m o d m s_{k+1}=as_{k}+b \mod m sk+1=ask+bmodmold文件中给出每个随机数低位64bit被隐去
- 再生成三个关键的LCG随机数,用AES加密flag的变形,key由前两个随机数组成,分别取第一个随机数高位和第二个随机数的低位拼起来,iv由第三个随机数组成
显然如果要解密出flag明文那必须要反推出最初的seed参数,将后面产生的随机数全部复原。
因为前20个随机数的高位是已知的,接下来思路是针对这个式子做变形
s
k
+
1
=
a
s
k
+
b
m
o
d
m
s_{k+1}=as_{k}+b \mod m
sk+1=ask+bmodm
将s拆分为高位h和低位l变为
h
k
+
1
+
l
k
+
1
=
a
(
h
k
+
l
k
)
+
b
m
o
d
m
h_{k+1}+l_{k+1}=a(h_{k}+l_{k})+b \mod m
hk+1+lk+1=a(hk+lk)+bmodm
⇒
l
k
+
1
=
a
l
k
+
(
a
h
k
+
b
−
h
k
+
1
)
m
o
d
m
\Rightarrow l_{k+1}=al_{k}+(ah_{k}+b-h_{k+1}) \mod m
⇒lk+1=alk+(ahk+b−hk+1)modm
由于h均为已知,那么现在找到低位l递推的关系式,又因为
l
2
=
a
l
1
+
(
a
h
1
+
b
−
h
2
)
m
o
d
m
l_{2}=al_{1}+(ah_{1}+b-h_{2}) \mod m
l2=al1+(ah1+b−h2)modm
l
3
=
a
l
2
+
(
a
h
2
+
b
−
h
3
)
m
o
d
m
l_{3}=al_{2}+(ah_{2}+b-h_{3}) \mod m
l3=al2+(ah2+b−h3)modm
⇒
l
3
=
a
2
l
1
+
a
(
a
h
1
+
b
−
h
2
)
+
(
a
h
2
+
b
−
h
3
)
m
o
d
m
\Rightarrow l_{3}=a^2l_{1}+a(ah_{1}+b-h_{2})+(ah_{2}+b-h_{3}) \mod m
⇒l3=a2l1+a(ah1+b−h2)+(ah2+b−h3)modm
则可以推出所有低位l与l1的关系式,简写为
l
k
+
1
=
A
k
l
1
+
B
k
m
o
d
m
l_{k+1}=A_{k}l_{1}+B_{k} \mod m
lk+1=Akl1+Bkmodm
⇒
l
k
+
1
=
A
k
l
1
+
B
k
+
z
k
m
\Rightarrow l_{k+1}=A_{k}l_{1}+B_{k} + z_km
⇒lk+1=Akl1+Bk+zkm
其中A,B均可通过a,b,h推出。现在将以上关系式写成矩阵形式。
构造lattice,最后一项取264的原因是为了保持向量各个数值bit位保持一致(关于格密码基础概念推荐这篇Lattice学习笔记)
M
=
[
m
m
⋱
m
A
1
A
2
⋯
A
19
1
B
1
A
3
⋯
B
19
0
2
64
]
M= \begin{bmatrix} m \\ & m \\ & & \ddots \\ & & & m \\ A_1 & A_2 & \cdots & A_{19} & 1 \\ B_1 & A_3 & \cdots & B_{19} & 0 & 2^{64} \\ \end{bmatrix}
M=⎣⎢⎢⎢⎢⎢⎢⎡mA1B1mA2A3⋱⋯⋯mA19B1910264⎦⎥⎥⎥⎥⎥⎥⎤
有
[
z
1
z
2
⋯
z
19
l
1
1
]
M
=
[
l
2
l
3
⋯
l
20
l
1
2
64
]
\begin{bmatrix} z_1 & z_2 & \cdots & z_{19} & l_1 & 1\end{bmatrix} M =\begin{bmatrix} l_2 & l_3 & \cdots & l_{20} & l_1 & 2^{64}\end{bmatrix}
[z1z2⋯z19l11]M=[l2l3⋯l20l1264]
于是对M使用LLL算法求出随机数低位,sage exp:
#sage 9.4
a=107763262682494809191803026213015101802
b=153582801876235638173762045261195852087
m=226649634126248141841388712969771891297
h = [0,7800489346663478448,11267068470666042741,5820429484185778982,6151953690371151688,548598048162918265,1586400863715808041,7464677042285115264,4702115170280353188,5123967912274624410,8517471683845309964,2106353633794059980,11042210261466318284,4280340333946566776,6859855443227901284,3149000387344084971,7055653494757088867,5378774397517873605,8265548624197463024,2898083382910841577,4927088585601943730]
for i in range(len(h)):
h[i] <<= 64
A = [1]
B = [0]
for i in range(1, len(h)-1):
A.append(a*A[i-1] % m)
B.append((a*B[i-1]+a*h[i]+b-h[i+1]) % m)
A = A[1:]
B = B[1:]
M = matrix(ZZ, 21, 21)
for i in range(19):
M[i, i] = m
M[19, i] = A[i]
M[20, i] = B[i]
M[i, 19] = M[i, 20] = 0
M[19, 19] = 1
M[20, 20] = 2^64
M[19, 20]= 0
ll = M.LLL()[0]
l1 = ll[-2]
h1 = h[1]
s1 = l1+h1
#for s1=a*seed+b%m
seed = ((s1-b)*inverse_mod(a,m))%m
print(seed)
#73991202721494681711496408225724067994
得到seed之后就比较简单了,修改一下加密逻辑及得到解密后的结果:
from Crypto.Util.number import *
from Crypto.Cipher import AES
class LCG:
def __init__(self, bit_length):
b = 153582801876235638173762045261195852087
a = 107763262682494809191803026213015101802
m = 226649634126248141841388712969771891297
seed = 73991202721494681711496408225724067994
self._key = {'a':a, 'b':b, 'm':m}
self._state = seed
def next(self):
self._state = (self._key['a'] * self._state + self._key['b']) % self._key['m']
return self._state
def export_key(self):
return self._key
def gen_lcg():
rand_iter = LCG(128)
key = rand_iter.export_key()
f = open("key", "w")
f.write(str(key))
return rand_iter
def leak_data(rand_iter):
f = open("old", "w")
for i in range(20):
f.write(str(rand_iter.next() >> 64) + "n")
return rand_iter
def decrypt(rand_iter):
#from ct
flag=b'\xe0\xe0p,\xb7\xd6\xfc\x19\xac\xe7)\xbe\xfc\xa4\n\xf5\x01#\xf7\xa6\xed\xe1\xf3\xaeK:*\xcd{\x1d\xd87\x14s\x10X\x86\xaf\xd1WsD\x1f\xa0lej\xd6'
key = rand_iter.next() >> 64
key = (key << 64) + (rand_iter.next() >> 64)
key = long_to_bytes(key).ljust(16, b'\x00')
iv = long_to_bytes(rand_iter.next()).ljust(16, b'\x00')
cipher = AES.new(key, AES.MODE_CBC, iv)
ct = cipher.decrypt(flag)
print(ct)
def main():
rand_iter = gen_lcg()
rand_iter = leak_data(rand_iter)
decrypt(rand_iter)
if __name__ == "__main__":
main()