代码是从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的密文

流程图:

DES加解密的python实现_字符串

二、具体实现

下面给出加密思路:


  1. 首先第一步我们要从文件中读取明文;(1)
  2. 对明文进行字符转换成比特流,每64位分一组,最后不足的用0补齐;(2)
  3. 对每组的64位比特进行IP置换,分成L,R两组;(3)
  4. 对得到的L,R进行16轮的Feistel变换;(4)
  5. 再使用IP-1函数将L,R合并为密文比特流;(5)
  6. 最后将密文比特流转换成密文字符保存。(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


三、结果测试


运行程序,测试其可行性与正确性,如下:

DES加解密的python实现_加解密_02

可以看出已经可以实现我们的实验要求功能,并能够将密文,输出到文本文件,便于下次读取,不用重复输入。



修改后的放在自己的Github上,需要自提:​​传送门​

附:原作者的循环左移函数本人认为有些问题,遂做了简单修改。



还是要一如既往的努力,没有富婆能看穿你的逞强,明天的砖只会比今天的更烫手!!!

加油!