DES是一种将64比特的明文加密成64比特的密文对称密码算法。DES是以64比特的明文为一个单位来进行加密的,这个64比特的单位成为分组,所以DES密码又称为分组密码
DES的基本结构是由Horst Feistel 设计的,因此也称为Feistel网络或Feistel结构,在Feistel网络中加密的哥哥步骤成为轮(round),整个加密过程就是进行若干次轮的循环。如下图所示(来自百度百科):
- 下面用具体的代码实现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次迭代
E扩展置换
- 要先实现明文拓展的功能,这里我们使用E盒,将明文的部分bit重复排列,造成冗余,来实现拓展。
- E扩展置换的目的有两个:
- 生成与密钥相同长度的数据以进行异或运算
- 提供更长的结果,再后续的替代运算中可以进行压缩。
# 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网络中的一轮
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中的密文)