使用求余运算解简单流密码
k
e
y
w
o
r
d
s
:
keywords:
keywords: DASCTF 2022 三月春季赛 flower cipher
,流密码
,求余运算
P r o b l e m Problem Problem
# python3
from secret import flag
import random
# flag = b'flag{%s}' % md5(something).hexdigest()
# note that md5 only have characters 'abcdef' and digits
def Flower(x, key):
flower = random.randint(0, 4096)
return x * (key ** 3 + flower)
flag = flag[5:-1]
rounds = len(flag)
L, R = 1, 0
for i in range(rounds):
L, R = R + Flower(L, flag[i]), L
print(L, R)
'''
15720197268945348388429429351303006925387388927292304717594511259390194100850889852747653387197205392431053069043632340374252629529419776874410817927770922310808632581666181899 139721425176294317602347104909475448503147767726747922243703132013053043430193232376860554749633894589164137720010858254771905261753520854314908256431590570426632742469003
'''
A n a l y s i s Analysis Analysis
首先flag
的明文空间是由小写字母以及数字构成的,因为flag
内容是一个md5
值,当然我们不需要hash
值的原始明文,求得hash
值即可;也相当于给出了flag
的长度为
32
32
32(md5
值均是
32
32
32位)
加密过程是将flag
的每位字符传入一个类似流密码的加密过程中
def Flower(x, key):
flower = random.randint(0, 4096)
return x * (key ** 3 + flower)
L, R = 1, 0
for i in range(len(flag[5:-1])):
L, R = R + Flower(L, flag[i]), L
加密过程中使用的Flower
函数相当于Flower(L, flag[i]) = L * (flag[i] ** 3 + random)
,那么括号里面的东西才是我们想要知道的,如果能知道括号里面的总值,那么我们也能通过爆破random
的大小范围(random.randint(0,4096)
生成的随机数大小范围是在
0
0
0到
4096
4096
4096之间,很好爆破)并且判断总值减去random
的值是否能开三次方根(因为key ** 3
),如果能那么开三次方根之后就等于真实的flag[i]
那么我们现在需要知道Flower
函数括号里的总值,姑且将其设为
k
k
k
查看加密过程中for
循环的作用
L
0
=
1
;
R
0
=
0
L
1
=
R
0
+
L
0
⋅
k
0
;
R
1
=
L
0
L
2
=
R
1
+
L
1
⋅
k
1
;
R
2
=
L
1
⋯
L
k
=
R
k
−
1
+
L
k
−
1
⋅
k
l
a
s
t
;
R
k
=
L
k
−
1
L_0 = 1;~R_0=0\\ L_1 = R_0 + L_0\cdot k_0;~R_1=L_0\\ L_2 = R_1 + L_1 \cdot k_1;~R_2=L_1\\ \cdots\\ L_k = R_{k-1} + L_{k-1}\cdot k_{last};~R_{k} = L_{k-1}
L0=1; R0=0L1=R0+L0⋅k0; R1=L0L2=R1+L1⋅k1; R2=L1⋯Lk=Rk−1+Lk−1⋅klast; Rk=Lk−1
实际上这里的
R
R
R的作用相当于保存上一次计算的
L
L
L,所以我们可以这样来写
L
0
=
1
L
1
=
L
0
⋅
k
0
L
2
=
L
0
+
L
1
⋅
k
1
L
3
=
L
1
+
L
2
⋅
k
2
⋯
L
k
=
L
k
−
2
+
L
k
−
1
⋅
k
l
a
s
t
L_0 = 1\\ L_1 = L_0\cdot k_0\\ L_2 = L_0 + L_1\cdot k_1\\ L_3 = L_1 + L_2\cdot k_2\\ \cdots\\ L_k = L_{k-2} + L_{k-1}\cdot k_{last}
L0=1L1=L0⋅k0L2=L0+L1⋅k1L3=L1+L2⋅k2⋯Lk=Lk−2+Lk−1⋅klast
这里
k
k
k的下标代表着flag[i]
的位数i
我们可以清晰地发现每次加密需要的是 L i − 2 L_{i-2} Li−2和 L i − 1 L_{i-1} Li−1;在解密过程中如果我们已知一个等式的 L i , L i − 1 , L i − 2 L_i,L_{i-1},L_{i-2} Li,Li−1,Li−2这三个数我们就可以求得 k i k_i ki;
此时整个加密过程阶数之后我们已知的有 L k L_k Lk和 R k R_k Rk(相当于 L k − 1 L_{k-1} Lk−1),也就是说最后一个加密过程的等式 L k = L k − 2 + L k − 1 ⋅ k l a s t L_k=L_{k-2}+L_{k-1}\cdot k_{last} Lk=Lk−2+Lk−1⋅klast里面我们呢已知 L k , L k − 1 L_k,L_{k-1} Lk,Lk−1,那就想办法知道 L k − 2 L_{k-2} Lk−2的大小
求余运算,由于已知的只有两个值,分别在等式的两侧,如果我们计算 L k % L k − 1 L_k\% L_{k-1} Lk%Lk−1,那么其结果是不是正好是 L k − 2 L_{k-2} Lk−2呢?这个求余的方法可以应用在该加密过程的所有已知 L i L_i Li和 L i − 1 L_{i-1} Li−1的等式中
当最后一个等式的 k k k值求解出来之后,再到倒数第二个等式中正好已知的就有 L i L_i Li和 L i − 1 L_{i-1} Li−1,用同样的办法求解 L i − 2 L_{i-2} Li−2即可,以此类推,就可以把所有的 k k k值求出
S o l v i n g c o d e Solving~code Solving code
from Crypto.Util.number import *
import gmpy2
L_k = 15720197268945348388429429351303006925387388927292304717594511259390194100850889852747653387197205392431053069043632340374252629529419776874410817927770922310808632581666181899
R_k = 139721425176294317602347104909475448503147767726747922243703132013053043430193232376860554749633894589164137720010858254771905261753520854314908256431590570426632742469003
flag = ""
while L_k != 1:
L_k1 = L_k % R_k
temp = (L_k - L_k1) // R_k
for i in range(4096):
if gmpy2.iroot(temp - i,3)[1]:
flag += chr(gmpy2.iroot(temp - i,3)[0])
break
L_k = R_k
R_k = L_k1
print(flag[::-1])