MTP-多次加密

简述多次加密法(MTP)

原理

MTP(Many-Time-Pad),其加密方式和OTP是一样的,都是使用简单的异或操作。

不同之处在于OTP是用不同的key异或不同的明文。而MTP是用同一个key去异或多个明文

详细见:Many-Time-Pad 攻击 (ruanx.net)

脚本

先附上脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import Crypto.Util.strxor as xo
import libnum, codecs, numpy as np

def isChr(x):
if ord('a') <= x and x <= ord('z'): return True
if ord('A') <= x and x <= ord('Z'): return True
return False


def infer(index, pos):
if msg[index, pos] != 0:
return
msg[index, pos] = ord(' ')
for x in range(len(c)):
if x != index:
msg[x][pos] = xo.strxor(c[x], c[index])[pos] ^ ord(' ')

def know(index, pos, ch):
msg[index, pos] = ord(ch)
for x in range(len(c)):
if x != index:
msg[x][pos] = xo.strxor(c[x], c[index])[pos] ^ ord(ch)


dat = []

def getSpace():
for index, x in enumerate(c):
res = [xo.strxor(x, y) for y in c if x!=y]
f = lambda pos: len(list(filter(isChr, [s[pos] for s in res])))
cnt = [f(pos) for pos in range(len(x))]
for pos in range(len(x)):
dat.append((f(pos), index, pos))

#这里传密文
c = [codecs.decode(x.strip().encode(), 'hex') for x in open('Problem.txt', 'r').readlines()]

msg = np.zeros([len(c), len(c[0])], dtype=int)

getSpace()

dat = sorted(dat)[::-1]
for w, index, pos in dat:
infer(index, pos)

know(10, 21, 'y') #如果需要修正, 传入所在被改字母所在行和列,再输入替代的字母,例如alwa s 改为always
know(8, 14, 'n')

print('\n'.join([''.join([chr(c) for c in x]) for x in msg]))

key = xo.strxor(c[0], ''.join([chr(c) for c in msg[0]]).encode())
print(key)

例题 BUUCTF—AFCTF2018—你听过一次一密吗?

例题中改动的数据来源于Many-Time-Pad 攻击 (ruanx.net)

1
2
3
4
5
6
7
8
9
10
11
12
(原题有bug, 笔者有少量改动)
25030206463d3d393131555f7f1d061d4052111a19544e2e5d54
0f020606150f203f307f5c0a7f24070747130e16545000035d54
1203075429152a7020365c167f390f1013170b1006481e13144e
0f4610170e1e2235787f7853372c0f065752111b15454e0e0901
081543000e1e6f3f3a3348533a270d064a02111a1b5f4e0a1855
0909075412132e247436425332281a1c561f04071d520f0b1158
4116111b101e2170203011113a69001b47520601155205021901
041006064612297020375453342c17545a01451811411a470e44
021311114a5b0335207f7c167f22001b44520c15544801125d40
06140611460c26243c7f5c167f3d015446010053005907145d44
0f05110d160f263f3a7f4210372c03111313090415481d49530f

$$
上述的每个字符串C_i都是某个密钥key\oplus 明文M_i得到的。
$$

我们的目标是获取这个key
$$
\because C_1 = M_1 \oplus key
$$
$$
C_2 = M_2 \oplus key
$$

$$
\therefore C_1 \oplus C_2 = (M_1 \oplus key)\oplus (M_2 \oplus key)=M_1 \oplus M_2
$$

这表明,两个密文的异或,就等于对应明文的异或。这是很危险的性质,高明的攻击者可以通过频率分析,来破译这些密文。

我们来看字符串 C1异或上其他密文会得到什么东西。以下只保留了英文字符,其余字符以 . 代替

1
2
3
4
5
6
7
8
9
10
....S....N.U.....A..M.N...
...Ro..I...I....SE....P.I.
.E..H...IN..H...........TU
..A.H.R.....E....P......E.
...RT...E...M....M....A.L.
d...V..I..DNEt........K.DU
.......I....K..I.ST...TiS.
.....f...N.I........M.O...
.........N.I...I.S.I..I...
....P....N.OH...SA....Sg..

可以观察到,有些列上有大量的英文字符,有些列一个英文字符都没有。这是偶然现象吗?

ASCII

对照ASCII码表

因为空格的ASCII码值是32,再观察大写字母和小写字母

我们可以发现小写字母 xor 空格,会得到对应的大写字母;大写字母 xor 空格,会得到小写字母!

所以,如果x xor y 得到一个英文字母,那么,x,y中某一个很大概率是空格
$$
回头看上面C_1 \oplus 其他密文——也就等于 M_1 \oplus 其他明文的表
$$

$$
如果第col列存在大量的英文字母,我们可以猜测M_1[col]是一个空格。
$$

$$
哪一列英文字母越多,把握越大。
$$

$$
\color{red}知道M_1的第col位是空格有什么用?
$$

$$
\because 一个数异或0还是等于本身,即0 = a \oplus a
$$

$$
\therefore M_i[col] = M_i[col] \oplus 0 = M_i[col] \oplus M_1[col] \oplus M_1[col]=M_i[col] \oplus M_1[col] \oplus 32
$$

攻击

攻击原理还是没理解很明白

攻击过程显而易见:对于每一条密文Ci,拿去异或其他所有密文。然后去数每一列有多少个英文字符,作为“Mi

在这一位是空格“的评分。

上面的事情做完时候,依据评分从大到小排序,依次利用 “某个明文的某一位是空格” 这种信息恢复出所有明文的那一列。如果产生冲突,则舍弃掉评分小的。

下面附上大佬写的脚本以及思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import Crypto.Util.strxor as xo
import libnum, codecs, numpy as np

def isChr(x):
if ord('a') <= x and x <= ord('z'): return True
if ord('A') <= x and x <= ord('Z'): return True
return False

def infer(index, pos):
if msg[index, pos] != 0:
return
msg[index, pos] = ord(' ')
for x in range(len(c)):
if x != index:
msg[x][pos] = xo.strxor(c[x], c[index])[pos] ^ ord(' ')

dat = []

def getSpace():
for index, x in enumerate(c):
res = [xo.strxor(x, y) for y in c if x!=y]
f = lambda pos: len(list(filter(isChr, [s[pos] for s in res])))
cnt = [f(pos) for pos in range(len(x))]
for pos in range(len(x)):
dat.append((f(pos), index, pos))

#c = 密文
#c = [codecs.decode(x.strip().encode(), 'hex') for x in open('Problem.txt', 'r').readlines()]

msg = np.zeros([len(c), len(c[0])], dtype=int)

getSpace()

dat = sorted(dat)[::-1]
for w, index, pos in dat:
infer(index, pos)

print('\n'.join([''.join([chr(c) for c in x]) for x in msg]))

执行代码,得到的结果是:

1
2
3
4
5
6
7
8
9
10
11
Dear Friend, T%is tim< I u
nderstood my m$stake 8nd u
sed One time p,d encr ptio
n scheme, I he,rd tha- it
is the only en.ryptio7 met
hod that is ma9hemati:ally
proven to be #ot cra:ked
ever if the ke4 is ke)t se
cure, Let Me k#ow if ou a
gree with me t" use t1is e
ncryption sche e alwa s...

显然这不是最终结果,我们得修正几项。把 k#now 修复成 know,把 alwa s 修复成 always. 代码如下:

1
2
3
4
5
6
7
8
9
10
def know(index, pos, ch):
msg[index, pos] = ord(ch)
for x in range(len(c)):
if x != index:
msg[x][pos] = xo.strxor(c[x], c[index])[pos] ^ ord(ch)

know(10, 21, 'y') #这里传入需要改的字母所在行和列,例如k#now在第9行第15列
know(8, 14, 'n')

print('\n'.join([''.join([chr(c) for c in x]) for x in msg]))

注意:空格也占位

结果得到:

1
2
3
4
5
6
7
8
9
10
11
Dear Friend, This time I u
nderstood my mistake and u
sed One time pad encryptio
n scheme, I heard that it
is the only encryption met
hod that is mathematically
proven to be not cracked
ever if the key is kept se
cure, Let Me know if you a
gree with me to use this e
ncryption scheme always...

例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from secret import secret, key
from Crypto.Util.strxor import strxor


def ENC(m, k):
c = []
for i in range(len(m) // len(k)):
tmp = m[i * len(k):(i + 1) * len(k)]
c.append(strxor(tmp, key))
return c


print(ENC(secret, key))
# [b'sO$\x03T\x10\x02\x10SR\x03\x0c\x11\x08B\x0c\x01\x00\x01\x0cGET\tE\x1dM(L\x08O\x1dF\x1aD\x10\x15\x1d\x17MI\x12H\x04\x02E\x19\x1c\n\x1e\x16\x00\x06\x04\x12\x0bG\x0f\x01\x15G\x01\x1d\x01K', b'CT \tN\nAUf\x1e\x04\nE\x04\x11E<O\x1a0gN\x1b\x1f(:pg\x00=\x00\x1b\x07\x17H\x02\x0f\x15\x00\x00\r\x0fR\x00\x15\x11\x1b\x16\x01N\x07U\x11L\x07\rEF\x1c\x13\t\x01\x07\x07A', b'RMi\x05H\x18\x1c\x10SR\x1c\x02\x10CB<\x1dUM\x11YR\x1aH\x04\tA NHO\x0cR\x00\x00\x17\t\x17ES\x1d\tR\x08V\x04\x16\x13\x1a\x1d\x11SKL<\x13E\x14O\x13\t\x01T\x1cX', b'ERi\x1fO\x0cO\x05L\x13\x1cM\x11\x05\x0b\x16RO\x18\x11\x00\x00\x18\x01\x0e\x0b\x00:O\t\nNH\x19I\r\x0e\x07\x16\x00\r\x07N\x06\x13E\x05\x10\x1b\x06ED\x00\r\x07\r\x00\x0c\x1a\x01\x13E\x16\x16H', b"OR,FD\x18\x18\x1b\x0eR2\x05\x1cRB'\x17C\x0c\x10_ET\x1c\r\x07SiS\x10\x00\x1cJTI\x10\x0fU\x11\x00\x1a\tM\x00\x02\r\x1b\x17\x08N\x11H\x04\x18S\x07L\x03\x18R\x0e\x0bT\x15\\", b'OMi\x00A\x0bO\x14W\x13\x1cAE\x1e\r\x08\x17T\x05\x0cBGT\x1c\r\x0fTiH\x05\x1cNI\x1bT\x0b\x08\x1c\x02\x00\x1d\t\x00\x01\x19E\x05\x10\x1b\x06EY\n\x19]Et\x0e\x06\x01G\x16\x00\x1c\\', b'M\x00 \x15\x00\x00\x00\x00\x0eR6\x02\x08\x08\x16\r\x1bN\nEEN\x07\x01\x01\x0b\x00&FD\x16\x01RZ\x000\x0eR\x04L\x05FY\n\x03E\x11\x18\x01N\x01OE\x05\x00EG\x0f\x19\x17G\x0c\x1aSZ', b'O\x00 \x12\x0cY\x1c\x01E\x02E\x1f\x0c\n\n\x11RI\x03\x16ED\x11H\x11\x06EiS\x10\x00\x1cJX\x00\x00\r\x1d\x16I\x07\x01\x00\x1c\x19\x10\x00Y\n\x17\x00SE\r\x1d\x01\x00\x16\x03\x07\x00\x02\x1d\x1dI', b'\x00U9FY\x16\x1a\x07\x00\x17\x04\x1f\x16M\x11\nRT\x05\x00\x0cS\x15\x06\x01ND&E\x17\x01ISTG\x06\x15R\x0cNEFA\x0b\x12E\x05\x18\x03\x05ET\r\x1e\x1c\x10G\x0eO\x1b\x13IT\x00Z', b'EPi\x04YY\x1c\x01E\x02KM1\x05\x07\x17\x17\x07\x1eEBOT\x1b\x10\x00\x00=H\x01\x1d\x0b\x0bTN\x0cA\x1f\nO\x07J\x00\x0b\x19E\x16\x10\x1d\x0b\x06T\x0c\x03\x1dI\x00\x08\x00R\x14\x00\x1a\x00K', b'\x00O/FT\x10\x02\x10\x0eR/\x18\x16\x19B\x03\x1bN\x08E[H\x1d\x1c\x00NS(N\x00O\x1dP\x1dR\x0f\x08\x1c\x02\x00\x1c\x16\x00\x0c\x18\x11\x1dY\x1b\x06\x00\x00\x16\x07\nEL\x0f\x04\x17G\x15\x01\x1fX', b"ER \x1cE\x1dO\x17O\x1c\x00\x1eKM6\r\x13TJ\x16\x0cT\x1c\rE\x05I'DD\x00\x08\x07\x07A\r\x05\x01\x11O\x1b\x0b\x00\x1c\x19\x10R\x17\n\x0b\x01\x00\x11\x03S\x0cM\x07\x08\x1b\t\x00ZSo", b'NDi\tN\x1a\nUT\x1a\x00M\x16\x19\r\x17\x1f\x00\x04\x16\x0cO\x02\r\x17B\x000O\x11O\x19H\x1a\x07\x17A\x00\x00M\x0c\x0bB\x00\x04E\x1a\x16\x18N\x1cO\x10L\x1e\x04D\x03O\x1b\x13E\x00\x1b\\', b'OU.\x0e\x0cY\x07\x1aWR\x1c\x02\x10M\x0f\x04\x1cA\n\x00H\x00\x00\x07E\x1dU;V\r\x19\x0b\tTy\x0c\x14R\x12O\x07ATE\x13\x13\x17\x17O\x0c\x00\x00\x16\x19\x01\x00\x0cF\x18\x1a\x02\x11\x1c\x16\\', b'\x00T!\x03\x00\n\x1b\x1aR\x1fE\x04\x16M\x10\x00\x13L\x01\x1c\x0cO\x02\r\x17@\x00\x0bU\x10O\x01I\x11\x00\x17\t\x1b\x0bGI\x0fSE\x15\x00\x00\r\x0e\x07\x0b\x0eE;\x1b\x00NF\x16\x1d\x12E\x17\x1cC', b'E\x00&\x13TY\x00\x13\x00\x06\r\x08E\x1e\x16\n\x00MAEUO\x01H\x12\x01NnTD\r\x0b\x07\x00H\x06A\x01\x04M\x0cFP\x00\x04\x16\x1d\x17O\x19\rOE\x1b\x12\tK\x03\x0bR\x0e\x0bZSz']
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import Crypto.Util.strxor as xo
import libnum, codecs, numpy as np

def isChr(x):
if ord('a') <= x and x <= ord('z'): return True
if ord('A') <= x and x <= ord('Z'): return True
return False


def infer(index, pos):
if msg[index, pos] != 0:
return
msg[index, pos] = ord(' ')
for x in range(len(c)):
if x != index:
msg[x][pos] = xo.strxor(c[x], c[index])[pos] ^ ord(' ')

def know(index, pos, ch):
msg[index, pos] = ord(ch)
for x in range(len(c)):
if x != index:
msg[x][pos] = xo.strxor(c[x], c[index])[pos] ^ ord(ch)


dat = []

def getSpace():
for index, x in enumerate(c):
res = [xo.strxor(x, y) for y in c if x!=y]
f = lambda pos: len(list(filter(isChr, [s[pos] for s in res])))
cnt = [f(pos) for pos in range(len(x))]
for pos in range(len(x)):
dat.append((f(pos), index, pos))

#这里传密文
# c = [codecs.decode(x.strip().encode(), 'hex') for x in open('Problem.txt', 'r').readlines()]
c = []
msg = np.zeros([len(c), len(c[0])], dtype=int)

getSpace()

dat = sorted(dat)[::-1]
for w, index, pos in dat:
infer(index, pos)

#know(10, 21, 'y') #如果需要修正, 传入所在被改字母所在行和列,再输入替代的字母,例如alwa s 改为always
#know(8, 14, 'n')

print('\n'.join([''.join([chr(c) for c in x]) for x in msg]))

#key = xo.strxor(c[0], ''.join([chr(c) for c in msg[0]]).encode())
#print(key)

这里是未修正的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Sometimes fate is like a small tand6torm that keeps changing dir1
ctions. Flag is NowUKnowMTP. Yor ch$nge direction but the sandst;
rm chases you. You turn again, eut 1he storm adjusts. Over and o"
er you play this out, like some'omi+ous dance with death just be2
ore dawn. Why? Because this stoum i6n't something that blew in f&
om far away, something that has'not-ing to do with you. This sto&
m is you. Something inside of yhu. o all you can do is give in
o it, step right inside the stoum, &losing your eyes and pluggin3
up your ears so the sand doesn t g t in, and walk through it, s
ep by step. There's no sun therb, n* moon, no direction, no sens1
of time. Just fine white sand twir)ing up into the sky like pul"
erized bones. That's the kind oa sa+dstorm you need to imagine.
nd once the storm is over, you pon'1 remember how you made it th&
ough, how you managed to survivb. Y*u won't even be sure, whethe&
the storm is really over. But hne 1hing is certain. When you co9
e out of the storm, you won't bb th same person who walked in.

修正后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Sometimes fate is like a small tandstorm that keeps changing dir1
ctions. Flag is NowUKnowMTP. Yor change direction but the sandst;
rm chases you. You turn again, eut the storm adjusts. Over and o"
er you play this out, like some'ominous dance with death just be2
ore dawn. Why? Because this stoum isn't something that blew in f&
om far away, something that has'nothing to do with you. This sto&
m is you. Something inside of yhu. So all you can do is give in
o it, step right inside the stoum, closing your eyes and pluggin3
up your ears so the sand doesn t get in, and walk through it, s
ep by step. There's no sun therb, no moon, no direction, no sens1
of time. Just fine white sand twirling up into the sky like pul"
erized bones. That's the kind oa sandstorm you need to imagine.
nd once the storm is over, you pon't remember how you made it th&
ough, how you managed to survivb. You won't even be sure, whethe&
the storm is really over. But hne thing is certain. When you co9
e out of the storm, you won't bb the same person who walked in.

flag{NowUKnowMTP}

-------------已经到底啦!-------------