DES算法
DES(Data Encryption Standard)是迄今为止世界上最为广泛使用和流行的一种分组密码算法,它的分组长度为64bit,密钥长度为56bit,它是由美国IBM公司研制的,是早期的称作Lucifer密码的一种发展和修改。
1 前置知识
1.1 Feistel 网络
很多分组密码的结构从本质上说都是基于Feistel 网络结构。
Feistel 加密结构:
在Feistel 密码结构中,加密算法输入的是一个长度为 2 w 2w 2w的明文和密钥 K K K,将每组明文分为左右两半 L 0 , R 0 L_0,R_0 L0,R0,在进行完 n n n轮迭代之后,左右两半在合并到一起产生密文分组。算法的示意图如下:
其中第
i
i
i轮迭代可以表示为:
L
i
=
R
i
−
1
R
i
=
L
i
−
1
⨁
F
(
R
i
−
1
,
K
i
)
L_i = R_{i-1} \\ R_i = L_{i-1}\bigoplus F(R_{i-1}, K_i)
Li=Ri−1Ri=Li−1⨁F(Ri−1,Ki)
其中,
K
i
K_i
Ki是第i轮使用的密钥,由加密密钥
K
K
K得来。一般来说,各轮的子密钥各不相同且与
K
K
K不同。
Feistel 解密结构
Feistel 解密结构和加密结构在过程上是一致的,算法使用密文作为输入,但使用子密钥 K i K_i Ki的次序需要与加密过程相反。下图表示进行16轮Feistel 加密和解密的具体过程:
2 DES 算法加密步骤
下图是DES 算法加密的框架,其中明文是64bit,而密钥长56bit。
**明文处理过程:**首先是一个初始置换IP,用于重排明文分组的64bit数据。然后是具有相同功能的16轮变换,每轮变换中有置换和代换运算,第16轮变换的输出分为左右两半,并交换次序。最后再经过一个逆出事置换 I P − 1 IP^{-1} IP−1(为IP的逆)从而产生64bit的密文。
**密钥使用方法:**密钥首先经过一个置换函数,然后对于加密的每一轮,通过一个左循环移位和一个置换生成一个子密钥。
2.1 初始置换
下图分别是初始置换和逆初始置换的定义:
为了更好的理解,下面是一个输入数据 M M M
![在这里插入图片描述](https://img-blog.csdnimg.cn/d6abeabb4e104f6dbd6ea8d2aa08b3c0.png)
我们根据上述(a)对M进行变换,即 X = I P ( M ) X=IP(M) X=IP(M),可以得到以下的结果:
以 M 58 M_{58} M58为例子,现在它相当于是 X 1 X_1 X1,而我们再根据上述(b)对X进行逆变换,即 Y = I P − 1 ( X ) = I P − 1 ( I P ( M ) ) Y=IP^{-1}(X) = IP^{-1}(IP(M)) Y=IP−1(X)=IP−1(IP(M)),就可以看到M恢复了原本的顺序。
import numpy as np
def IP(M, reverse=False):
order = None
if reverse is False:
order = [58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7]
else:
order = [40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25]
res = np.zeros(M.shape)
for i in range(64):
res[i] = M[order[i] - 1]
return res
2.2 轮结构
下图是DES加密算法的轮结构:
在左侧中,将64bit的轮输入分为各32bit的左右两半,分别标记为
L
,
R
L,R
L,R。和Feistel 网络一样,变换可以表示为:
L
i
=
R
i
−
1
R
i
=
L
i
−
1
⨁
F
(
R
i
−
1
,
K
i
)
L_i = R_{i-1} \\ R_i = L_{i-1}\bigoplus F(R_{i-1}, K_i)
Li=Ri−1Ri=Li−1⨁F(Ri−1,Ki)
其中
K
i
K_i
Ki为48bit,函数F的具体过程如下图所示:
2.2.1 E表(扩展/置换)
轮输入的右半部分 R R R首先要被扩展为48bit,扩展的过程如下图所示:
原本数据 R R R为32位,扩展后数据 R ′ R^\prime R′为48bit,以 R ′ R^\prime R′的第一行为例子,第一行的第一个元素为 R 32 R_{32} R32,而最后一个元素为 R 5 R_5 R5 ,以此达到扩展的目的。
import numpy as np
def Expand(M):
order = [32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1]
res = np.zeros(48)
for i in range(48):
res[i] = M[order[i] - 1]
return res
2.2.2 S盒(代换/选择)
经过扩展的48bit和子密钥 K i K_i Ki异或,然后分为8组6bit的数据,分别经过S盒,最后合并输出32bit。F中的代换由8个S盒构成,每一个S盒的输出为6bit,输出为4bit,最终形成48bit输入,32bit输出。8个S盒的定义如下:
对于每一个盒 S i S_i Si,其6bit输入中,第1个bit和第6个bit形成一个2位二进制数,用来选择 S i S_i Si的行,而6bit输入的中间4bit构成一个4位的二进制数,用来选择 S i S_i Si的列。行列选定后,交叉位置的十位制数转化为4位二进制数即为 S i S_i Si的输出。
比如,如果 S 1 S_1 S1的输入是011001,那么选择第2行(01),第12列(1100)的数字9,再将其转化为二进制1001,就得到了结果。
def S(M):
order = [
[[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7],
[0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8],
[4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0],
[15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13]],
[[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10],
[3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5],
[0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15],
[13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9]],
[[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8],
[13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1],
[13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7],
[1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12]],
[[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15],
[13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9],
[10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4],
[3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14]],
[[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9],
[14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6],
[4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14],
[11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3]],
[[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11],
[10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8],
[9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6],
[4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13]],
[[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1],
[13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6],
[1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2],
[6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12]],
[[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7],
[1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2],
[7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8],
[2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]]
]
res = []
for i in range(8):
index_row = M[6*i]*2 + M[6*i + 5]*1
index_col = M[6*i + 1]*8 + M[6*i + 2]*4 + M[6*i + 3]*2 + M[6*i + 4]*1
num = order[i][index_row][index_col]
num_bin = bin(num)[2:]
for j in range(4):
res.append( int(num_bin[j]) )
res = np.array(res)
return res
2.2.3 P盒(置换)
和上述初始置换基本一致,在此只给出代换表,不再赘述。
def P(M):
order = [16, 7, 20, 21, 29, 12, 28, 17,
1, 15, 23, 26, 5, 18, 31, 10,
2, 8, 24, 14, 32, 27, 3, 9,
19, 13, 30, 6, 22, 11, 4, 25]
res = np.zeros(32)
for i in range(32):
res[i] = M[order[i] - 1]
return res
2.3 密钥生成
输入的64bit密钥先经过一个置换运算,和上述置换运算基本一致,置换表如下。在此处需要说明为什么输入的是64bit而非56bit,这是因为在一般使用情况下,密钥会带有8bit的奇偶校验位,所以输入的密钥长度为8+56=64bit,在经过PC-1置换之后得到的密钥就变成了56bit。
置换之后将56bit的密钥分为28bit的两部分 C 0 , D 0 C_0,D_0 C0,D0。在第i轮对 C i − 1 , D i − 1 C_{i-1},D_{i-1} Ci−1,Di−1进行左循环移动,具体移动位数参考下表,移位后的结果作为下一轮求解子密钥的输入,同时也作为置换选择 2 的输入。
通过置换选择2产生的48bit的 K i K_i Ki,作为 F ( R i − 1 , K i ) F(R_{i-1},K_i) F(Ri−1,Ki)输入,其中置换选择2定义为:
def PC_1(K):
PC_1 = [57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36,
63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4]
key = np.zeros(56)
for i in range(56):
key[i] = K[PC_1[i] - 1]
return key
def key_generate(K, num_round):
key_left = K[:28]
key_right = K[28:]
shift = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
num_move = shift[num_round - 1]
key_left = np.hstack((key_left[num_move:], key_left[:num_move]))
key_right = np.hstack((key_right[num_move:], key_right[:num_move]))
key = key_left + key_right
PC_2 = [14, 17, 11, 24, 1, 5, 3, 28,
15, 6, 21, 10, 23, 19, 12, 4,
26, 8, 16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55, 30, 40,
51, 45, 33, 48, 44, 49, 39, 56,
34, 53, 46, 42, 50, 36, 29, 32]
key_i = np.zeros(48)
for i in range(48):
key_i[i] = key[PC_2[i] - 1]
return key, key_i
2.4 算法实现
下面的代码实现,绝大部分的变量类型是np.array
,经过考虑之后发现str
类型应该更加方便灵活且运行速度快,所以仅仅作为思路参考,不适合使用。
import numpy as np
import math
def IP(M, reverse=False):
'''
:param M: 64bit的密文
:param reverse: 是否为逆变换,True是逆变化
:return: 经过IP变化之后的64bit密文
'''
order = None
if reverse is False:
order = [58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7]
else:
order = [40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25]
res = np.zeros(M.shape)
for i in range(64):
res[i] = M[order[i] - 1]
return res
def Expand(M):
'''
:param M: 32bit密文
:return: 经过E变化之后的48bit密文
'''
order = [32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1]
res = np.zeros(48)
for i in range(48):
res[i] = M[order[i] - 1]
return res
def Xor(a, b):
'''
a, b的长度需要相等
:return:a^b
'''
if a.shape[0] != b.shape[0]:
raise Exception('Error with : a, b are not the same length.')
res = np.zeros(a.shape)
for i in range(a.shape[0]):
res[i] = int(a[i]) ^ int(b[i])
return res
def S(M):
'''
:param M: 48bit密文
:return:经过S盒的32bit密文
'''
order = [
[[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7],
[0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8],
[4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0],
[15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13]],
[[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10],
[3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5],
[0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15],
[13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9]],
[[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8],
[13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1],
[13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7],
[1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12]],
[[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15],
[13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9],
[10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4],
[3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14]],
[[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9],
[14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6],
[4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14],
[11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3]],
[[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11],
[10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8],
[9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6],
[4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13]],
[[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1],
[13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6],
[1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2],
[6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12]],
[[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7],
[1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2],
[7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8],
[2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]]
]
res = []
for i in range(8):
index_row = int(M[6*i]*2 + M[6*i + 5]*1)
index_col = int(M[6*i + 1]*8 + M[6*i + 2]*4 + M[6*i + 3]*2 + M[6*i + 4]*1)
num = order[i][index_row][index_col]
num_bin = bin(num)[2:]
for j in range(4-len(num_bin)):
num_bin = '0' + num_bin
for j in range(4):
res.append( int(num_bin[j]) )
res = np.array(res)
return res
def P(M):
'''
:param M: 32bit密文
:return: 经过P置换之后的32bit密文
'''
order = [16, 7, 20, 21, 29, 12, 28, 17,
1, 15, 23, 26, 5, 18, 31, 10,
2, 8, 24, 14, 32, 27, 3, 9,
19, 13, 30, 6, 22, 11, 4, 25]
res = np.zeros(32)
for i in range(32):
res[i] = M[order[i] - 1]
return res
def PC_1(K):
'''
:param K: 64bit密文(含奇偶校验位)
:return: 经过初始置换的56bit密文
'''
PC_1 = [57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36,
63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4]
key = np.zeros(56)
for i in range(56):
key[i] = K[PC_1[i] - 1]
return key
def key_generate(K, num_round):
'''
:param K: 56bit密文
:param num_round: 轮数
:return: 下轮的56bit密钥、本轮的48bit密钥
'''
key_left = K[:28]
key_right = K[28:]
shift = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
num_move = shift[num_round - 1]
key_left = np.hstack((key_left[num_move:], key_left[:num_move]))
key_right = np.hstack((key_right[num_move:], key_right[:num_move]))
key = np.hstack((key_left, key_right))
PC_2 = [14, 17, 11, 24, 1, 5, 3, 28,
15, 6, 21, 10, 23, 19, 12, 4,
26, 8, 16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55, 30, 40,
51, 45, 33, 48, 44, 49, 39, 56,
34, 53, 46, 42, 50, 36, 29, 32]
key_i = np.zeros(48)
for i in range(48):
key_i[i] = key[PC_2[i] - 1]
return key, key_i
def key_create(key_message):
'''
:param key_message: 密钥字符串
:return: 64bit密钥
'''
key = []
for word in key_message:
word_bin = bin(ord(word))[2:]
if len(word_bin) > 7:
word_bin = word_bin[:7]
else:
for i in range(7 - len(word_bin)):
word_bin = '0' + word_bin
count = 0
for i in range(len(word_bin)):
if word_bin[i] == '1':
count += 1
if count % 2 == 0:
word_bin += '0'
else:
word_bin += '1'
for i in range(len(word_bin)):
key.append(int(word_bin[i]))
if len(key) > 64:
key = key[:64]
else:
for i in range(64 - len(key)):
key = [0] + key
res = np.array(key)
return res
def data_group(data_message):
'''
:param data_message: 需要加密的字符串
:return: 64bit分组后的data列表
'''
data = ''
for word in data_message:
word_bin = bin(ord(word))[2:]
if len(word_bin) > 8: # 如果message为英文字符串的话一般不会出现这种情况,也就不会出现信息缺失的问题
raise Exception('Eorro with : ' + word + ' > 8bit')
else:
for i in range(8 - len(word_bin)):
word_bin = '0' + word_bin
data = data + word_bin
res = []
for i in range(math.floor(len(data) / 64)):
temp = []
for j in range(64):
temp.append(int(data[64*i + j]))
res.append(temp)
temp = []
for i in range(len(data) % 64):
temp.append( int(data[math.floor(len(data) / 64)*64 + i]) )
for i in range(64 - len(temp)):
temp.append(0)
res.append(temp)
res = np.array(res)
return res
def encrypt(data_message, key_message):
'''
:param data: 需要加密的64bit数据
:param key_message: 字符串密钥
:return: 密文
'''
data = data_group(data_message)
key = key_create(key_message)
res = ''
key_before = PC_1(key)
for i in range(data.shape[0]):
data_before = IP(data[i]) # 初始置换
for j in range(16):
key_before, key_j = key_generate(K=key_before, num_round=j+1)
data_left = data_before[:32]
data_right = data_before[32:]
data_temp = Expand(data_right)
data_temp = Xor(data_temp, key_j)
data_temp = S(data_temp)
data_temp = P(data_temp)
data_right = Xor(data_left, data_temp)
data_before = np.hstack((data_right, data_left))
temp = IP(data_before, reverse=True)
for j in range(64):
res = res + str(int(temp[j]))
return res
print(encrypt(data_message='life is fantastic.', key_message='HELLO'))