DES是一种将64比特的明文加密成64比特的密文对称密码算法。DES是以64比特的明文为一个单位来进行加密的,这个64比特的单位成为分组,所以DES密码又称为分组密码

DES的基本结构是由Horst Feistel 设计的,因此也称为Feistel网络Feistel结构,在Feistel网络中加密的各个步骤成为轮(round),整个加密过程就是进行若干次轮的循环。如下图所示(来自百度百科):
des加密流程图

  • 下面用具体的代码实现DES的加密,解密的过程完全一样,不过是密钥逆用。

1.首先将将明文和密钥转化为二进制,以64位分一组,最后不足的用0补齐

# 将明文转化为二进制
def str2bin(message):
    res = ''
    for i in message:
        tmp = bin(ord(i))[2:]  # 将每个字符转化成二进制
        tmp = str('0' * (8 - len(tmp))) + tmp  # 补齐8位
        res += tmp
    if len(res) % 64 != 0:
        count = 64 - len(res) % 64  # 不够64位补充0
    else:
        count = 0
    res += '0' * count
    return res


# 将密钥转化为二进制
def key2bin(key):
    res = ''
    for i in key:
        tmp = bin(ord(i))[2:]  # 将每个字符转化成二进制
        tmp = str('0' * (8 - len(tmp))) + tmp  # 补齐8位
        res += tmp
    if len(res) < 64:
        count = 64 - len(res) % 64  # 不够64位补充0
        res += '0' * count
    else:
        res = res[:64]
    return res

2.然后进行ip盒置换

IP置换的目的是将转化后的64位比特数据按位重新组合,并把输出分为L0和R0两部分,各部分长32位。
例如ip盒中的第一个数是58,那么就是把原数据中的第58位放到新数据的第1位

# IP盒处理
def ip_change(str_bin):
    res = ''
    for i in IP_table:
        res += str_bin[i - 1]
    return res

str_left = str_bin[:32]  # L0位新数据的左32位
str_right = str_bin[32:]  # # R0位新数据的右32位

3.密钥置换

子密钥产生示意图
这个流程图可以让我们很直观的了解到密钥产生的过程

  • 首先对给定的64位密钥K,应用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
  • 然后将56位密钥分为左边和右边两部分,对该28位,进行重复左移。
  • 这里将循环左移的位数,写在一个列表SHIFT中,方便调用,每一轮左移,都会得到一个56位子密钥
  • 然后再通过PC-2选择出最终的48比特的子密钥
# 生成子密钥
def gen_key(bin_key):
    key_list = []
    key1 = change_key1(bin_key)  # 秘钥的PC-1置换
    key_C0 = key1[0:28]
    key_D0 = key1[28:]
    for i in SHIFT:  # shift左移位数
        key_c = key_C0[i:] + key_C0[:i]  # 左移操作
        key_d = key_D0[i:] + key_D0[:i]
        key_output = change_key2(key_c + key_d)  # 秘钥的PC-2置换
        key_list.append(key_output)
    return key_list


# 秘钥的PC-2置换
def change_key2(my_key):
    res = ""
    for i in PC_2:
        res += my_key[i - 1]
    return res  # 这里置换玩密钥从56位变为48位

通过上述操作,我们就得到了16组48位的子密钥,然后接下来进行DES的核心环节

4.轮函数f运算

有了每一轮的加密密钥Ki,就可以进行16次迭代

函数f迭代过程

E扩展置换

  • 要先实现明文拓展的功能,这里我们使用E盒,将明文的部分bit重复排列,造成冗余,来实现拓展。
  • E扩展置换的目的有两个:
  1. 生成与密钥相同长度的数据以进行异或运算
  2. 提供更长的结果,再后续的替代运算中可以进行压缩。
# E盒置换
def e_change(str_left):
    res = ""
    for i in E:
        res += str_left[i - 1]
    return res
  • 然后将48位结果与子密钥Ki进行异或(xor)
def xor_change(str1, str2):
    res = ""
    for i in range(0, len(str1)):
        xor_res = int(str1[i], 10) ^ int(str2[i], 10)  # 进行xor操作
        if xor_res == 1:
            res += '1'
        if xor_res == 0:
            res += '0'
    return res

S盒代替

  • 异或运算结束后,我们需要将48bit缩减位32bit,分组使用S盒置换,。
  • 替代由8个不同的S盒完成,每个S盒有6个输入4个输出。
  • 48位输入分为8个6位分组,每个分组对应一个S盒,对应的S盒对各组进行代替操作。
  • 替代过程中产生的8个4位的分组,组合在一起形成32位数据

例如S盒8的输入位110011,第1位和第6位组合为11,对应S盒8的第3行,第2位到第5位为1001,对应于S盒8的地9列。S盒的第3行第9列的数字为12,那么就用1100来代替110011。

def s_change(my_str):
    res = ""
    c = 0
    for i in range(0, len(my_str), 6):  # 步长为6   表示分6为一组
        now_str = my_str[i:i + 6]  # 第i个分组
        row = int(now_str[0] + now_str[5], 2)  # 第r行
        col = int(now_str[1:5], 2)  # 第c列
        # 第几个s盒的第row*16+col个位置的元素
        num = bin(S[c][row * 16 + col])[2:]  # 利用了bin输出有可能不是4位str类型的值,所以才有下面的循环并且加上字符0
        for gz in range(0, 4 - len(num)):  # 补全4位
            num = '0' + num
        res += num
        c += 1
    return res
  • 然后再进行P盒运算
def p_change(bin_str):
    res = ""
    for i in P:
        res += bin_str[i - 1]
    return res

整个轮函数f如下:

def f(str_left, key):
    e_change_output = e_change(str_left)  # E扩展置换
    xor_output = xor_change(e_change_output, key)  # 将48位结果与子密钥Ki进行异或(xor)
    s_change_output = s_change(xor_output)
    res = p_change(s_change_output)
    return res
  • 然后明文右侧与子密钥进行论函数f后的结果与明文左边的数据进行xor运算,然后交换左边和右边

至此 我们只完成了一轮的Feistel变换,之后的15轮都是这样的,但要注意,再进行最后一轮Feistel变换的时候不需要交换左边和右边

for j in range(15):  # 先循环15次 因为最后一次不需要不用换位
            f_res = f(str_right, key_lst[j])
            str_left = xor_change(f_res, str_left)
            str_left, str_right = str_right, str_left  # 左边和右边交换

        f_res = f(str_right, key_lst[15])  # 第16次

Feistel网络中的一轮
这张图即可很直观明白的了解Feistel网络中的一轮

5.IP逆置换

得到了加密后的56位比特,现在我们要使用初始置换函数的逆函数,得到正确的密文序列。也是直接从盒中置换:

# IP逆盒处理
def ip_re_change(bin_str):
    res = ""
    for i in IP_re_table:
        res += bin_str[i - 1]
    return res

6.将密文比特流转化为密文字符

# 二进制转字符串
def bin2str(bin_str):
    res = ""
    tmp = re.findall(r'.{8}', bin_str)  # 每8位表示一个字符
    for i in tmp:
        res += chr(int(i, 2))
    return res

至此所有加密结束




  • 下面是加密的代码
def encrypt():
    bin_str = str2bin(input('请输入明文:'))
    bin_key = key2bin(input('请输入密钥:'))
    tmp = re.findall(r'.{64}', bin_str)
    result = ''
    for i in tmp:
        str_bin = ip_change(i)  # IP置换
        key_lst = gen_key(bin_key)  # 生成16个子密钥
        str_left = str_bin[:32]
        str_right = str_bin[32:]
        for j in range(15):  # 先循环15次 因为最后一次不需要不用换位
            f_res = f(str_right, key_lst[j])
            str_left = xor_change(f_res, str_left)
            str_left, str_right = str_right, str_left

        f_res = f(str_right, key_lst[15])  # 第16次
        str_left = xor_change(str_left, f_res)
        fin_str = ip_re_change(str_left + str_right)  # ip的逆
        result += fin_str
    last = bin2str(result)
    print('密文为:', last)

解密的大致流程

首先上代码,可以看出,解密的脚本盒加密的脚本大同小异,只不过是再解密的时候密钥是反过来了。

def decrypt():  # 解密和加密的步骤差不多,但要注意解密时密钥是倒过来的 ,第一个的时候左右不交换
    bin_str = str2bin(input('请输入密文:'))
    bin_key = key2bin(input('请输入密钥:'))
    tmp = re.findall(r'.{64}', bin_str)
    result = ''
    for i in tmp:
        str_bin = ip_change(i)  # IP置换
        key_lst = gen_key(bin_key)  # 生成16个子密钥
        str_left = str_bin[:32]
        str_right = str_bin[32:]
        for _j in range(1, 16):
            j = 16 - _j  # 解密的时候秘钥反过来的
            f_res = f(str_right, key_lst[j])
            str_left = xor_change(f_res, str_left)
            str_left, str_right = str_right, str_left
        f_res = f(str_right, key_lst[0])
        str_left = xor_change(str_left, f_res)
        fin_str = ip_re_change(str_left + str_right)  # ip的逆
        result += fin_str
    last = bin2str(result)
    print('明文为:', last)

完整参考代码

github

(ps:有好多人反馈说输入密文解不了,这是因为des加密是在二进制层面变换的,所以转成字符串是会出现乱码或者不可见的字符,直接复制可能会导致一些问题。
所以我直接将加密后的密文保存到了secret.txt中,解密的时候输入密文为空或者直接敲回车会直接读取secret.txt中的密文)

最后修改:2022 年 03 月 02 日
如果觉得我的文章对你有用,请随意赞赏