代码是从github上找到的,也融入了自己的一些理解。
一、原理
原理:分组密码将明文分成一组一组,在密钥的控制下,经过加密变换生产一组一组的密文。具体而言,分组密码就是将明文消息列m1,m2,···,mi,···划分成等长的消息组(m1,m2,···,mn),(mn+1,mn+2,···,m2n),···, 在密钥k1,k2,···,ki的控制下按固定的加密算法一组一组进行加密,输出一组一组密文(c1,c2,···cl),(cl+1,cl+2,···,c2l),···。
下面的实验以DES算法为例。DES算法明文分组长为64bit,加密后得到64bit密文,输入初始种子密钥为64bit,第8、16、24、32、40、48、56、64为奇偶校验位,实际的密钥长为56bit。DES加密过程由三个阶段来完成:
1) 初始置换IP,用于重排明文分组的64bit数据。
2) 相同结构的16轮迭代,每轮中都有置换和代换运算,第16轮变换的输出分为左、右两半,并交换次序。
3) 逆初始置换IP-1(为IP的逆)后,产生64bit的密文
流程图:
二、具体实现
下面给出加密思路:
- 首先第一步我们要从文件中读取明文;(1)
- 对明文进行字符转换成比特流,每64位分一组,最后不足的用0补齐;(2)
- 对每组的64位比特进行IP置换,分成L,R两组;(3)
- 对得到的L,R进行16轮的Feistel变换;(4)
- 再使用IP-1函数将L,R合并为密文比特流;(5)
- 最后将密文比特流转换成密文字符保存。(6)
解密的过程完全一样,不过是密钥逆用。故不再赘述。
接下来是加密实验过程:
(1).从文件中读取明文:
Python中内置了很多的库,在这里直接调用open函数来读取文本,选择r模式,表示只读操作
下面是代码:
def read_out_file():
try:
f = open('DES.txt','r',encoding = 'utf-8')
mess = f.read()
f.close()
print("文件读取成功!")
return mess
except IOError:
print('文件加解密出错!!!')
(2).明文字符转换成比特流
每个英文字符都是有其对应的ascii码,再分组加密的时候,我们是在bit级上操作,因此先转化为bit流
下面是代码:
#字符串转化为二进制
def str2bin(message):
res = ""
for i in message: #对每个字符进行二进制转化
tmp = bin(ord(i))[2:] #字符转成ascii,再转成二进制,并去掉前面的0b
for j in range(0,8-len(tmp)): #补齐8位
tmp = '0'+ tmp #把输出的b给去掉
res += tmp
return res
(3)IP函数置换
下面进入到了核心的加密环节。第一步比较简单,将比特流进行IP置换,核心其实就是置换的IP盒,将明文比特流中的每一位,按照IP盒中的顺序,重新排序:即盒中第x位数字为y,表示将64一组的明文比特流的第y位,放在x位。
下面仅给出置换函数的核心部分,不再赘述IP盒:
#IP盒处理
def ip_change(bin_str):
res = ""
for i in IP_table:
res += bin_str[i-1] #数组下标i-1
return res
注:得到比特流后,可以进行切片操作,即可得到L,R两部分。如下:
mes_left = res[0:32] 表示切从0到32,但不包括32
mes_right = res[32:] 表示32开始,到之后全部
(4)一轮的Feistel变换
DES中最最最核心的就是此处的变换,需要重复做16轮,因此我们只需要明确一轮即可:
即密钥子密钥Ki与Ri-1进行f函数变换,得到的结果再与Li-1进行异或。
所以我们明确我们需要完成的事:
首先根据密钥生成子密钥;(①)
子密钥与R的f函数实现;(②)
异或函数的实现。(③)
- 密钥我们也是按照64位一组进行的划分,有64位,其实有效位仅为56位,需要通过PC-1盒先选择出有效的该56位:
#秘钥的PC-1置换
def change_key1(my_key):
res = ""
for i in PC_1: #PC_1盒上的元素表示位置 只循环64次
res += my_key[i-1] #将密钥按照PC_1的位置顺序排列,
return res
如明文一样,这里也需要分为左右两部分,每个子部分28位
对该28位,进行重复左移,第LS1,LS2,LS9,LS16次是循环左移1位变换,其他为循环左移2为变换
这里将循环左移的位数,写在一个列表SHIFT中,方便调用,每一轮左移,都会得到一个56位子密钥,需要再通过PC-2选择出最终的48比特的子密钥
下面给出代码实现:
for i in SHIFT: #i是左移位数 SHIIFT元素个数为循环次数
key_c = left_turn(key_C0,i) #左半部分循环左移
key_d = left_turn(key_D0,i) #右半部分循环左移
key_output = change_key2(key_c + key_d) #将左移后的密钥合并后使用PC-2盒置换出48位
key_list.append(key_output) #将第i个子密钥添加到列表保存
- 如上我们得到了16个子密钥,并存放在了列表中待用,接下来需要完成f函数的功能,这是DES的核心中的核心了,f函数实现的功能是:
先将明文32bit拓展为48bit,然后与子密钥Ki进行按位模加运算(即异或),得到的结果按每组6位分为8组,再经过S盒,从每6位中选择出4位,得到32比特,经过P盒置换后,得到最终的32位结果。
那么,我们要先实现明文拓展的功能,这里我们使用E盒,将明文的部分bit重复排列,造成冗余,来实现拓展:
#E盒置换
def e_str(bin_str):
res = ""
for i in E:
res += bin_str[i-1]
return res
现在得到了48位明文bit,可以与子密钥进行异或运算,如下:
#字符串异或操作
def str_xor(my_str1,my_str2): #str,key
res = ""
for i in range(0,len(my_str1)):
xor_res = int(my_str1[i],10)^int(my_str2[i],10) #变成10进制是转化成字符串 2进制与10进制异或结果一样,都是1,0
if xor_res == 1:
res += '1'
if xor_res == 0:
res += '0' #字符串连接
return res
异或运算结束后,我们需要将48bit缩减位32bit,分组使用S盒置换,如下:
# S盒过程
def s_box(my_str):
res = ""
c = 0
for i in range(0,len(my_str),6):#步长为6 表示分6为一组,会循环8次
now_str = my_str[i:i+6] #第i个分组,切片划入
row = int(now_str[0]+now_str[5],2) #b1b6 =r 第r行 共4行
col = int(now_str[1:5],2) #第c列 共16列
# s盒的第几个表的 第row*16+col个位置的元素
num = bin(S[c][row*16 + col])[2:] #利用了bin输出有可能不是4位str类型的值,所以才有下面的循环并且加上字符0
for gz in range(0,4-len(num)):
num = '0'+ num
res += num
c += 1 #S盒的下一个表
return res #循环结束后。返回得到的32位结果
下面将上一步得到的结果,经过P盒置换,还原出正确的序列:
#P盒置换
def p_box(bin_str):
res = ""
for i in P:
res += bin_str[i-1]
return res
- 其实在f函数中,我们已经使用了异或函数,这里我们需要再次使用其,将上一步的结果,与L异或操作,下面不再赘述。
上面我们已经完成了一轮的Feistel变换,再下一轮时,Li = Ri-1 ;Ri =Li-1⨁ f(Ri-1,Ki)即③得到的结果。
需要注意的是,在最后一轮变换后,不用交换位置了。
(5)IP-1 函数将加密后的L,R合并为密文比特流
终于,经过了前面的16轮变换,最后一轮不用交换左右两部分,直接合并比特流,得到了加密后的56位比特,现在我们要使用初始置换函数的逆函数,得到正确的密文序列。也是直接从盒中置换,不再赘述。
(6)将密文比特流转化为密文字符
这里用到了也是和之前函数相反的功能。这里将比特流转化为字符串:
#二进制转化为字符串
def bin2str(bin_str):
res = ""
tmp = re.findall(r'.{8}',bin_str) #每8位表示一个字符
for i in tmp:
res += chr(int(i,2)) #base参数的意思,将该字符串视作2进制转化为10进制
return res
将密文写回文本:
def write_in_file(str_mess):
try:
f = open('DES.txt','w',encoding='utf-8')
f.write(str_mess)
f.close()
print("文件输出成功!")
except IOError:
print('文件加解密出错!!!')
下面,来总结下我们的编码思路,看代码:(每次编码64bit即8byte)
def des_encrypt_one(bin_message,bin_key): #64位二进制加密的测试
mes_ip_bin = ip_change(bin_message) #ip转换
key_lst = gen_key(bin_key) #生成子密钥
mes_left = mes_ip_bin[0:32]
mes_right = mes_ip_bin[32:]
for i in range(0,15):
mes_tmp = mes_right #暂存右边32位
f_result = fun_f(mes_tmp,key_lst[i]) #右32位与k的f函数值
mes_right = str_xor(f_result,mes_left) #f函数的结果与左边32位异或 作为下次右边32位
mes_left = mes_tmp #上一次的右边直接放到左边
f_result = fun_f(mes_right,key_lst[15]) #第16次不用换位,故不用暂存右边
mes_fin_left = str_xor(mes_left,f_result)
mes_fin_right = mes_right
fin_message = ip_re_change(mes_fin_left + mes_fin_right) #ip的逆
return fin_message #返回单字符的加密结果
如上我们是每次只能编码64bit,所以我们还要对明文和密钥进行处理,使其成为64的倍数,保证每次都有足够的比特可操作:
#简单判断以及处理信息分组
def deal_mess(bin_mess):
ans = len(bin_mess)
if ans % 64 != 0:
for i in range( 64 - (ans%64)): #不够64位补充0
bin_mess += '0'
return bin_mess
同理:
#查看秘钥是否为64位
def input_key_judge(bin_key):
ans = len(bin_key)
if len(bin_key) < 64:
if ans % 64 != 0:
for i in range(64 - (ans % 64)): # 不够64位补充0
bin_key += '0'
else:
bin_key = bin_key[0:64] #秘钥超过64位的情况默认就是应该跟密文一样长 直接将密钥变为跟明文一样的长度,虽然安全性会有所下降
return bin_key
得到了恒为64倍数的明文,和密钥比特流后,我们可以使用下面这条正则表达式:
tmp = re.findall(r'.{64}',bin_mess)
将我们的实际很长的明文匹配位每64位一组的列表,每次对列表中的项进行加解密即可!!!
<加密完>
关于解密,因为原理完全相同,仅仅是密钥选择顺序不同,此处仅给出原理代码:
##64位二进制解密的测试,注意秘钥反过来了,不要写错了
def des_decrypt_one(bin_mess,bin_key):
mes_ip_bin = ip_change(bin_mess)
#bin_key = input_key_judge(str2bin(key))
key_lst = gen_key(bin_key)
lst = range(1,16) #循环15次
cipher_left = mes_ip_bin[0:32]
cipher_right = mes_ip_bin[32:]
#------------------------------------重点-------------------------------------
for i in lst[::-1]: #表示逆转列表调用
#----------------------------------------------------------------------------------
mes_tmp = cipher_right
cipher_right = str_xor(cipher_left,fun_f(cipher_right,key_lst[i]))
cipher_left = mes_tmp
fin_left = str_xor(cipher_left,fun_f(cipher_right,key_lst[0]))
fin_right = cipher_right
fin_output = fin_left + fin_right
bin_plain = ip_re_change(fin_output)
res = bin2str(bin_plain)
return res
三、结果测试
运行程序,测试其可行性与正确性,如下:
可以看出已经可以实现我们的实验要求功能,并能够将密文,输出到文本文件,便于下次读取,不用重复输入。
修改后的放在自己的Github上,需要自提:传送门
附:原作者的循环左移函数本人认为有些问题,遂做了简单修改。
还是要一如既往的努力,没有富婆能看穿你的逞强,明天的砖只会比今天的更烫手!!!
加油!