python(N1CTF) 100详解

N1es.py
challenge.py
题中有两个文件,一个是封装好的N1ES.py,另一个是challenge.py,通过调用n1se实现加密操作。
逐步分析其加密生成方式:

1.首先给出了key

key=“wxy191iss00000000000cute”

先调用的是string_to_bits函数

o = string_to_bits(self.key)

def string_to_bits(data):
    data = [ord(c) for c in data]
    l = len(data) * 8
    result = [0] * l
    pos = 0
    for ch in data:
        for i in range(0,8):
            result[(pos<<3)+i] = (ch>>i) & 1
        pos += 1
    return result
  • ord()函数是chr()的配对函数,以一个字符为参数返回ASCII数值.data是key的情况下,遍历其中的每个字符将其转化为ASCII码
  • 要将其转化为二进制,每个字符是要占据八个位置。
  • <<左移操作符 >> 右移操作符,例:a=60=0011 1100,a<<2=1111 0000=240。二进位全部左移两位,低位用0补齐。
  • result[(pos<<3)+i] = (ch>>i) & 1,是将字符的二进制逐个填入result的操作,例:
    ch=w(119,0111 0111),i=3,pos=0,pos<<3+3=3,ch>>3=0000 1110&1=0,最后得出result。在这里插入图片描述
    前八位为1110 1110,正好是w字符二进制的翻转,每八位代表着一个字符,这就完成了字符串到二进制的转换。
2.在循环中用到了generate()

o = generate(o)#此处o是之前经过处理的result

def permutate(table, block):
	return list(map(lambda x: block[x], table))
	s_box = [54, 132, 138, 83, 16, 73, 187, 84, 146, 30, 95, 21, 148, 63, 65, 189, 188, 151, 72, 161, 116, 63, 161, 91, 37, 24, 126, 107, 87, 30, 117, 185, 98, 90, 0, 42, 140, 70, 86, 0, 42, 150, 54, 22, 144, 153, 36, 90, 149, 54, 156, 8, 59, 40, 110, 56,1, 84, 103, 22, 65, 17, 190, 41, 99, 151, 119, 124, 68, 17, 166, 125, 95, 65, 105, 133, 49, 19, 138, 29, 110, 7, 81, 134, 70, 87, 180, 78, 175, 108, 26, 121, 74, 29, 68, 162, 142, 177, 143, 86, 129, 101, 117, 41, 57, 34, 177, 103, 61, 135, 191, 74, 69, 147, 90, 49, 135, 124, 106, 19, 89, 38, 21, 41, 17, 155, 83, 38, 159, 179, 19, 157, 68, 105, 151, 166, 171, 122, 179, 114, 52, 183, 89, 107, 113, 65, 161, 141, 18, 121, 95, 4, 95, 101, 81, 156, 17, 190, 38, 84, 9, 171, 180, 59, 45, 15, 34, 89, 75, 164, 190, 140, 6, 41, 188, 77, 165, 105, 5, 107, 31, 183, 107, 141, 66, 63, 10, 9, 125, 50, 2, 153, 156, 162, 186, 76, 158, 153, 117, 9, 77, 156, 11, 145, 12, 169, 52, 57, 161, 7, 158, 110, 191, 43, 82, 186, 49, 102, 166, 31, 41, 5, 189, 27]
def generate(o):
    k = permutate(s_box,o)
    b = []
    for i in range(0, len(k), 7):
        b.append(k[i:i+7] + [1])
    c = []
    for i in range(32):
		pos = 0
		x = 0
		for j in b[i]:
			x += (j<<pos)
			pos += 1
		c.append((0x10001**x) % (0x7f))
    return c

  • permutate从字面来看是置换的意思,这个函数的作用在与重新打乱之前得到的result结果,按照s_box列表中给出的顺序进行重新排序。如第一个数字是54,意味着o[54]排在第一个位置。将重排后的列表命名为k。
  • 第一个for循环含义在于将列表k每7个一组分开,每组都在后面添上一个数1,构成一个八个数字的数组。将其组合在一起形成b。
    b=[[1, 1, 0, 0, 1, 0, 0, 1], ……,[0, 1, 0, 0, 1, 1, 0, 1]]
  • b中有32组元素,第二个嵌套循环便是在这32组,每组分别进行操作,每组中有八个元素,按位置进行左移操作,第一个左移0位,第二个左移1位,……一直到第八个左移七位。
  • 整组全部元素位移操作的结果进行累加得到x,再进行运算
    (0x10001**x) % (0x7f),将其结果放入c中
  • 再对第二组重复操作,将结果放入c中,直到32组结果都运算完成。generate()函数运算结果:
> o=[126L, 119L, 89L, 107L, 107L, 2L, 5L, 5L, 119L, 100L, 125L, 20L, 63L, 19L, 4L, 87L, 108L, 54L, 8L, 4L, 19L, 51L, 25L, 16L, 8L, 80L, 50L, 2L, 47L, 87L, 47L, 87L]
3.N1ES.gen_subkey
3.1gen_subkey中第一个for
for i in range(8):
	o = generate(o)
    k.extend(o)
    o = string_to_bits([chr(c) for c in o[0:24]])
  • 对o进行generate
  • 将得到的结果添加到k中
  • 将o中的前24个元素都变成字符,这个24是按照key的字符个数来的。
['~', 'w', 'Y', 'k', 'k', '\x02', '\x05', '\x05', 'w', 'd', '}', '\x14', '?', '\x13', '\x04', 'W', 'l', '6', '\x08', '\x04', '\x13', '3', '\x19', '\x10']
  • 将这些字符都转为二进制,重新赋值给o
  • 重复以上操作八次。
    每次输入32个长整型数,八次后,k中一共有32*8=256个数。
3.2 gen_subkey中第二个for
self.Kn = []
for i in range(32):
	self.Kn.append(map(chr, k[i * 8: i * 8 + 8]))
  • 将整个k分成了32个组来进行加密,每次都把八个值转化为字符串,最后得到self.kn:
    在这里插入图片描述
4.N1ES.encrypt
def round_add(a, b):
    f = lambda x, y: x + y - 2 * (x & y)
    res = ''
    for i in range(len(a)):
        res += chr(f(ord(a[i]), ord(b[i])))
    return res
  • 定义一个函数 f(x,y)=x+y-2*(x&y)
  • 以第一个参数长度为限进行循环,将a[i],b[i]转换为ascii码带入f进行运算,再转换为字符串
  • 循环完毕这些字符串组合起来就是res。
res=''
for i in range(len(plaintext) / 16):
	block = plaintext[i * 16:(i + 1) * 16]
    L = block[:8]
	R = block[8:]
    for round_cnt in range(32):
    	L, R = R, (round_add(L, self.Kn[round_cnt]))
    L, R = R, L
    res += L + R

明文flag的长度一定要是16的倍数,每16个字符为一组分区,这16个中,前八个分为L(left),后八个R(right)。

  • 第一次循环,L1=R,R1=E1,E1代表经过一次编码后的字符串。
  • 第二次循环,L2=E1,R2=E2.
  • 32次循环后,L32=E31,R32=E32.
  • 循环结束后得到res=E32+E31。

发现只有这段才是真正的加密过程(Feistel加密结构),之前的所有都是为了产生self.Kn,为这次加密助力。

最后一次加密是在challenge.py文件中的base64加密。

至此全部的加密过程已经明晰,接下来就是解密了。

真正的加密只在这一个公式:
f = lambda x, y: x + y - 2 * (x & y)
而Feistel解密只要将循环式改为:

for round_cnt in range(32):
    	L, R = R, (round_add(L, self.Kn[31-round_cnt]))

利用原式就能进行解密,举个实例演示一下加密和解密:

  • 假设这是最后一次加密循环

假设L31=110(0110 1110),Kn[31]=126(0111 1110)
R32=E32=L31+Kn[31]-2*(L31&Kn[31])=110+126-2*(110&126)=16(0001 0000)
(其中L31=E30)
加密后得L32=R31=E31,R32=E32

  • 这是第一次解密循环

L0=E32,R0=E31(因为之前加密后还有一次互换的操作)
L1=R0=E31
R1=L0+Kn[31]-2*(L0&Kn[31])
现在已知的是Kn[31]=126,因为整个Kn表都可得出。以及L0,这里L0是flag加密后的左边部分,题目已给出。L0=E32=16.
R1=16+126-2*(16&126)=110
解密后的结果正好就是L31,即E30
这次解密后得到
L1=E31,R1=E30

由结果可看出,只要逆一下加密的kn顺序就变成了解密的kn顺序,这是feistel神奇的地方。
最后修改一下challenge.py变成decode.py

from N1ES import N1ES
import base64
key = "wxy191iss00000000000cute"
n1es = N1ES(key)
cipher = "HRlgC2ReHW1/WRk2DikfNBo1dl1XZBJrRR9qECMNOjNHDktBJSxcI1hZIz07YjVx"
cipher =base64.b64decode(cipher)
flag =n1es.decrypt(cipher)#这个decrypt只是把之前文件里的encrypt改变后改的名字
print flag

得到flag
N1CTF{F3istel_n3tw0rk_c4n_b3_ea5i1y_s0lv3d_/–/}

刚开始分析的时候一步步搞清楚了每个函数是干嘛的,然而根本没什么用,只是浪费了时间,真正加密过程只在encrypt函数中,这是经验不足的原因,以后看到类似的题绝对先找encrypt函数。
之后虽然搞清楚了加密原理,但之前没有接触过feistel加密结构,还想着用穷举法把它解出来,穷举法应该也是能做的,但是我并没有做成功,py2版本只在虚拟机上有,上面没有调试工具,没找到错误原因。后来看了一下别的writeup才发现如此简单。所以基本的密码学加密模式要去了解一下,因为前辈已经给出了解密方法,就不用自己的笨办法去想了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值