DES加密算法Golang实现

DES介绍

数据加密标准(英语:Data Encryption Standard,缩写为 DES)是一种对称密钥加密块密码算法,1976年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),随后在国际上广泛流传开来。它基于使用56位密钥的对称算法。(来自wikipedia
下面介绍一下对称加密和非对称加密的概念:

  • 对称密钥加密(Symmetric-key algorithm) 又称为对称加密、私钥加密、共享密钥加密,是密码学中的一类加密算法。这类算法在加密和解密时使用相同的密钥,或是使用两个可以简单地相互推算的密钥。实务上,这组密钥成为在两个或多个成员间的共同秘密,以便维持专属的通讯联系。与公开密钥加密相比,要求双方取得相同的密钥是对称密钥加密的主要缺点之一。
  • 非对称加密(asymmetric cryptography) 也称为公开密钥加密(英语:public-key cryptography,又译为公开密钥加密),一种密码学算法类型,在这种密码学方法中,需要一对密钥,一个是私人密钥,另一个则是公开密钥。这两个密钥是数学相关,用某用户密钥加密后所得的信息,只能用该用户的解密密钥才能解密。如果知道了其中一个,并不能计算出另外一个。因此如果公开了一对密钥中的一个,并不会危害到另外一个的秘密性质。称公开的密钥为公钥;不公开的密钥为私钥。

有关这两者概念上的理解,建议大家查看此链接,讲述的通俗易懂,肯定比我讲的好。

DES是一种典型的块密码(一种将固定长度的明文通过一系列复杂操作变成同样长度的密文的算法)。对于DES而言,块长度为64位,同时DES使用密钥来定义变换过程,并且也只有持有密钥的用户才能解密密文。密钥表面上是64位的,然而只有其中的56位被实际应用于算法,其余8位可以用于奇偶校验。因此,DES的有效密钥长度仅为56位。
为简单起见,此博客在讲解DES原理过程中使用64位的二进制数据作为密钥,也使用64的二进制数据作为明文,加密后的密文也为64位的二进制数据表示;并在最后给出更加一般格式密钥、明文的解决方法。

加密步骤

下面定义一下之后所需用到的参数和规则:
key:64位密钥
1101001010110101100110110111011110011001111010001101111001110110
text:64位需要被加密的明文
1111111011011100101110101001100001110110010101000011001000010000
[start:end]:左闭右开

置换表:数据范围从1开始,作为下标使用时需要进行相应的减1操作(S盒置换除外)。

一. 子密钥的获取

DES算法由64位密钥产生16轮的48位子密钥,在后续F函数的每一次迭代中使用不同的子密钥。

1. 选择置换1

前面已经介绍,64位的密钥有8位不参与运算。所以我们首先需要将64位的密钥经过选择置换1(PC-1) 变为56位的密钥key_pc_1
然后将key_pc_1分为两块 C 0 C_0 (28位)和 D 0 D_0 (28位)。
选择置换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

上面矩阵中的数字代表64位密钥中每位数据的位置(1~64,在计算过程中可能需要减1之后作为下标使用),即经过选择置换1之后,key的第1位置换到了第8位,第2位置换到了第16位……最终获得56位的密钥key_pc_1。
然后, C 0 = K 57 K 49 K 44 K 36 = k e y _ p c _ 1 [ 0 : 28 ] C_0=K_{57}K_{49}……K_{44}K_{36}=key\_pc\_1[0:28] D 0 = K 63 K 5 5 K 12 K 4 = k e y _ p c _ 1 [ 28 : 56 ] D_0=K_{63}K_55……K_{12}K_4=key\_pc\_1[28:56] 。这里的K指的是最初的64位长的密钥(为方便说明,这里并未将下标减1)。

2. 获取16个子秘钥

已经得到 C 0 C_0 D 0 D_0 ,然后 C 1 C_1 D 1 D_1 C 0 C_0 D 0 D_0 左移一位; C 2 C_2 D 2 D_2 C 1 C_1 D 1 D_1 左移一位; C 3 C_3 D 3 D_3 C 2 C_2 D 2 D_2 左移两位……位移表如下:

轮数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
左移 1 1 2 2 2 2 2 2 1 2 2 2 2 2 2 1

注意,每次获得 C N C_N D N D_N 之后,将两者合并得到 C N D N C_ND_N (注意 C N C_N 在前, D N D_N 在后)然后经过选择置换2(PC-2) 生成子秘钥 K N K_N
选择置换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

置换的过程与选择置换1一致,不再赘述。
最终,我们就获得了子秘钥 K 1 K 16 K_1 - K_{16}

K1 = 000111101101001001111101010111010110010101110111
K2 = 111111011110110111010001111001001101101111100101
K3 = 010101111110011110011011010100101010111011111011
K4 = 011111111001010110010111111111111001110100011001
K5 = 011111111000100011001111000010110111011101111010
K6 = 001110111110000010111110011111011111100100100100
K7 = 101111000000110110111110111000000100110011111110
K8 = 111101100010101000111101110011011011101010011111
K9 = 011110001111110111100111101010011010011100111001
K10 = 111100011110010101001111111110110111111000000110
K11 = 011000011100011110110111011111000100001110111110
K12 = 111101011001100110110111100101010111100011001111
K13 = 101101111010001011110011111001101011001011110001
K14 = 101111110101011010110110101100111010111101101111
K15 = 111111100001001111011100001111101001111110010010
K16 = 011000110111110111110011100111100011000111101110

二. 初始置换(IP)

首先,DES算法使用初始置换将明文输入块text变为64位的预处理的输出块text_IP。
初始置换矩阵如下:

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,

上面矩阵中的数字代表64位明文中每位数据的位置,即经过初始置换之后,text的第1位置换到了第40位,第2位置换到了第8位……最终获得输出块text_IP。
text_IP = 0011001111111111001100110000000000001111010101010000111101010101

三. 费斯妥函数(F函数)

上面步骤中我们获得了text_IP,定义 L 0 = t e x t _ I P [ 0 : 32 ] L_0=text\_IP[0:32] R 0 = t e x t _ I P [ 32 : 64 ] R_0=text\_IP[32:64]
接下来,从 L 0 L_0 R 0 R_0 开始,以下5个步骤需要迭代进行16次,从而获得 L 1 R 1 L_1R_1 L 16 R 16 L_{16}R_{16}
最初:
L0 = 00110011111111110011001100000000
R0 = 00001111010101010000111101010101

1. E扩张置换

将32位 R i R_i 扩展为48位(8*6)(只需扩展 R i R_i 即可,后续有相关步骤进行说明)。扩张置换的目的有两个:生成与密钥相同长度的数据已进行异或运算;提供更长的结果,在后续的替代运算中可以进行压缩。
E扩张置换表如下:

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

扩展之后 R i R_i 变为了48位R_i_extended。
R_0_extended = 100001011110101010101010100001011110101010101010

2. 与子秘钥进行混合

此时,R_i_extended和 K i K_i 长度相同;然后将R_i_extended和 K i K_i 做异或运算得到R_i_extended_xor。
R_0_extended_xor = 100110110011100011010111110110001000111111011101

3. S盒置换

S盒接受6位的输入,经过置换产生4位的输出。
将R_i_extended_xor分为8个6位的块,将每一个块通过一个对应的S盒( S i S_i )产生一个4位的输出。之后将所有输出按顺序重组得到R_i_extended_xor_S_trans。
S盒置换规则如下:
将6位的输入中的第一位和第六位取出来形成一个两位的二进制数X,将其转换为十进制作为行数,然后将中间4位构成另一个二进制数Y,并将其转换为十进制数作为列,然后查出 S i S_i 的X行Y列所对应的整数,将该整数置换为一个4位的二进制数,即S盒的输出。(由于S盒置换表中数据并不做下标使用,所以无需进行减1操作。)
Example:
由R_0_extended_xor 可知:
第1个块:x=2, y=3; S 1 [ x ] [ y ] = 8 S_1[x][y]=8
第2个块:x=3, y=9; S 2 [ x ] [ y ] = 6 S_2[x][y]=6
第3个块:x=3, y=1; S 3 [ x ] [ y ] = 10 S_3[x][y]=10
第4个块:x=1, y=11; S 4 [ x ] [ y ] = 12 S_4[x][y]=12
第5个块:x=2, y=11; S 5 [ x ] [ y ] = 5 S_5[x][y]=5
第6个块:x=0, y=4; S 6 [ x ] [ y ] = 9 S_6[x][y]=9
第7个块:x=3, y=15; S 7 [ x ] [ y ] = 12 S_7[x][y]=12
第8个块:x=1, y=14; S 8 [ x ] [ y ] = 9 S_8[x][y]=9
经过S盒置换之后:
R_0_extended_xor_S_trans = 10000110101011000101100111001001
S盒置换表如下:
S 1 : S_1:

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,

S 2 : S_2:

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,

S 3 : S_3:

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,

S 4 : S_4:

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,

S 5 : S_5:

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,

S 6 : S_6:

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,

S 7 : S_7:

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,

S 8 : S_8:

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,

4. P置换

S盒置换得到的R_i_extended_xor_S_trans需要作为P置换的输入块,然后获得R_extended_xor_S_P_trans。
P盒置换表如下:

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,

R_extended_xor_S_P_trans = 01111000100101000011100101010101

5. 与 L i L_i 进行异或操作

将R_extended_xor_S_P_trans与 L i L_i 异或得到的结果R_extended_xor_S_P_trans_xor作为 R i + 1 R_{i+1} R i R_i 作为 L i + 1 L_{i+1} 开始下一轮的迭代。
R_extended_xor_S_P_trans_xor = 01001011011010110000101001010101

第三步最终结果L、R

L1= 00001111010101010000111101010101 		R1= 01001011011010110000101001010101
L2= 01001011011010110000101001010101 		R2= 11101100110000001001111101100100
L3= 11101100110000001001111101100100 		R3= 01001010010111000111100100110000
L4= 01001010010111000111100100110000 		R4= 11011001010101110001011010111001
L5= 11011001010101110001011010111001 		R5= 10000110101100101001101100111100
L6= 10000110101100101001101100111100 		R6= 10100000000101101011110101101011
L7= 10100000000101101011110101101011 		R7= 01100101110111100100101100000010
L8= 01100101110111100100101100000010 		R8= 01011000000101000100110010001001
L9= 01011000000101000100110010001001 		R9= 11111110101101010010100000001100
L10= 11111110101101010010100000001100 		R10= 01101101100011111100100010000100
L11= 01101101100011111100100010000100 		R11= 10000001001101001101000011110000
L12= 10000001001101001101000011110000 		R12= 11011111111001011001110101000010
L13= 11011111111001011001110101000010 		R13= 00001110000101100100011001101111
L14= 00001110000101100100011001101111 		R14= 10100011010001111100000000100111
L15= 10100011010001111100000000100111 		R15= 10110101010001100011110001101111
L16= 10110101010001100011110001101111 		R16= 11111111101010011011101001000101

四. 逆序置换(FP)

第三步结束之后,我们便可以获得 R 16 L 16 R_{16}、L_{16} 左右拼接组成的64位数据块 R 16 L 16 R_{16}L_{16} (注意R在前,L在后),然后将此作为输入块,进行逆序置换得到最终的密文。逆置换是初始置换的逆运算。从初始置换规则中可以看到,原始数据的第1位置换到了第40位,第2位置换到了第8位。则逆置换就是将第40位置换到第1位,第8位置换到第2位。以此类推,逆置换规则如下:

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,

最终密文如下:
cipher_text = 1101001101100110111010110101111011001100110111100110001111010100

五. 解密

解密过程中,除了子密钥顺序相反外,密钥调度的过程与加密完全相同。(即我们可以首先获得16个子秘钥,加密过程使用顺序的子秘钥,解密过程使用逆序的子秘钥)
最终运行结果:
在这里插入图片描述

六. 改进

上述实现,我们的密钥和明文都要求是64位的二进制数,加密后的密文也是64位的二进制数。这并不便于我们的使用。通常情况下,密钥和明文都是一般的字符串对用户来说更加友好,并且明文的长度也是可变的,所以我们可以对用户的输入得密钥和明文做一些处理:
我的算法是用go语言实现的,所以我们可以使用go的官方包encoding/hex,代码实现如下:

package des

import (
	"encoding/hex"
	"fmt"
	"os"
)
// 由于hex.EncodeToString可以将字符串转化为其对应的16进制的序列(一个字符对应于两个16进制数)
// 且加密过程中64位为一组,所以我们需要将明文长度处理为8的倍数(每8个字符对应于64位二进制数),所以我在原始明文后面增加了一部分字符
// 经des(text, key, true)加密之后返回得到二进制串,我调用自己书写的hexText函数将二进制串转化为16进制
func Encrypt(clear_text, key string) string {
	extra := 8 - len(clear_text)%8
	for i := 0; i < extra; i++ {
		clear_text = clear_text + string('0'+extra)
	}
	clear_text = hex.EncodeToString([]byte(clear_text))
	return hexText(des(clear_text, key, true))
}
// 加密得到的cipher_text刚好就是16进制,所以不必再次处理为16进制
// 经des(text, key, false)解码之后返回的是二进制串,同样调用hexText将二进制串转化为16进制,此16进制串对应于增加部分字符的明文经hex.EncodeToString编码后的16进制串
// 调用hex.DecodeString()即得到增加部分字符的明文
// 去掉明文后面增加的字符记得到原始明文
func Decrypt(cipher_text, key string) string {
	clear_text_hex := hexText(des(cipher_text, key, false))
	clear_text, _ := hex.DecodeString(clear_text_hex)
	clear_text_len := len(clear_text)
	return string(clear_text[:clear_text_len-int(clear_text[clear_text_len-1]-'0')])
}
// 
func des(text, key string, tag bool) string {
	// key长度为固定的64位,所以输入key的字符串应该为8位
	if len(key) != 8 {
		fmt.Println("The secret key need to be 8 bits.")
		os.Exit(0)
	}
	// 将key处理为64位二进制串
	key = formatKey(key)
	keys := getKeys(key)
	final_text := ""
	if !tag {
		keys = reverse(keys)
	}
	// text为16进制,所以每次加密\解密16位
	for i := 0; i < len(text)/16; i++ {
		textSub := binText(text[i*16 : i*16+16])
		text_init_replace := initialReplace(textSub)
		R_16_L_16 := iteration(text_init_replace, keys)
		final_text += reverseReplace(R_16_L_16)
	}
	return final_text
}

在这里插入图片描述
完整代码详见Github

猜你喜欢

转载自blog.csdn.net/liuyh73/article/details/83472521