DES加解密的python实现

代码是从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. 首先第一步我们要从文件中读取明文;(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轮,因此我们只需要明确一轮即可:

即密钥子密钥KiRi-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上,需要自提:传送门

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

 

 

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

加油!

  • 21
    点赞
  • 146
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 19
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kinnisoy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值