streamgame124最大的特点在于明文空间很小,例如1,密钥长度是19位二进制数,即有52万个可能的结果,对于现代计算机来说,完全可以在短时间内爆破完成,相比思考问题的解法,直接爆破显得更加经济。
stream3就不一样了,一打开发现密钥是18位十六进制,这时候任何爆破都不能奏效了,就得考虑更合适的解法。
这里用的是correlation attack,当一串密钥流是通过多个lfsr独立生成后再通过代数操作结合在一起的时候就可以用这种攻击方式。本质上是一种分治的操作,即把各个密钥单独算出来,首先得观察三个密钥流的结合操作:
def single_round(R1, R1_mask, R2, R2_mask, R3, R3_mask):(R1_NEW, x1) = lfsr(R1, R1_mask)(R2_NEW, x2) = lfsr(R2, R2_mask)(R3_NEW, x3) = lfsr(R3, R3_mask)return R1_NEW, R2_NEW, R3_NEW, (x1*x2)^((x2^1)*x3)# x1 x2 x3 out# 0 0 0 0# 0 0 1 1# 0 1 0 0# 0 1 1 0# 1 0 0 0# 1 0 1 1# 1 1 0 1# 1 1 1 1# out is more related to x1 and x3, but not x2
从下面的表格中可以看出,x1、x3和out更加有关联,表现在x1和out同时相等的频率有6/8,而x2只有4/8,约等于随机的二进制组合。因此,r1和r3可以单独通过correlation attack求出来,最后再用brute force找出r2。
# from flag import flag# assert flag.startswith("flag{")# assert flag.endswith("}")# assert len(flag)==24def lfsr(R, mask):output = (R << 1) & 0xfffff
考虑到明文空间很小,直接爆破就行,大概用了1分钟就找到秘钥了。
# from flag import flag# assert flag.startswith("flag{")# assert flag.endswith("}")# assert len(flag) == 25def lfsr(R, mask):output = (R << 1) & 0xffffffi = R & mask & 0xfffffflastbit = 0while i != 0:lastbit ^= (i & 1)i = i >> 1output ^= lastbitreturn (output, lastbit)# R = int(flag[5:-1], 2) # flag is 01 string, len 19mask = 0b1010011000100011100 # len mask == len flagdef encrypt(R):ret = ''for i in range(12):tmp = 0for j in range(8):(R, out) = lfsr(R, mask)tmp = (tmp << 1) ^ outret += chr(tmp)return retdef brute_force():with open('key', 'rb') as r:cipher = r.read()for i in range(2**18, 2**19):print iif encrypt(i) == cipher:raw_input()
flag{1110101100001101011}
直接爆破
# from flag import flag# assert flag.startswith("flag{")# assert flag.endswith("}")# assert len(flag) == 27def lfsr(R, mask):output = (R << 1) & 0xffffffi = R & mask & 0xfffffflastbit = 0while i != 0:lastbit ^= (i & 1)i = i >> 1output ^= lastbitreturn (output, lastbit)# R = int(flag[5:-1], 2)mask = 0x100002def encrypt(R):ret = ''for i in range(12):tmp = 0for j in range(8):(R, out) = lfsr(R, mask)tmp = (tmp << 1) ^ outret += chr(tmp)return retdef brute_force():with open('key', 'rb') as r:cipher = r.read()for i in range(2**20, 2**21):print iif encrypt(i) == cipher:raw_input()
flag{110111100101001101001}