密码学之DES,3DES详解与Python实现

一、定义

DES(Data Encryption Standard)是属于对称加密,即使用相同的密钥来完成加密和解密,是迄今为止世界上最为广泛使用和流行的一种分组密码算法,分组长度为64比特,密钥长度为64比特(密钥每个字节的最后一位都没有采用,所以我们说,DES的密钥有效位只有56位),由美国IBM公司研制 。

二、算法原理

算法主要分成两部分:明文运算和密钥编排, 加密流程图如下所示:

请添加图片描述

下面我们以明文0123456789ABCDEF,密钥133457799BBCDFF1为例,两者均为16进制表示,阐述整个加密过程。

2.1 密钥编排

第一步:密钥置换 (64bit -> 56bit)

64 位密钥根据下表PC-1进行置换。由于表中的第一项是“57”,这意味着原始密钥K的第57位 成为置换密钥K +的第一位。原始密钥的第 49 位成为置换密钥的第二位 ,依次类推

#Initial permut made on the key  56
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 位原始密钥

K = 00010011 00110100 01010111 01111001 10011011 10111100 11011111 11110001 

经PC-1表置换后,得到56位密钥

 K0 = 1111000 0110011 0010101 0101111 0101010 1011001 1001111 0001111

第二步: 56bit 分成两部分 L R 各 28bit

将上一步的密钥K0分成左右两半,C0D0,其中每一半有 28 位, 我们得到

C0 = 1111000 0110011 0010101 0101111
D0 = 0101010 1011001 1001111 0001111 

第三步:循环 16次 进行左移移位

定义了C0D0后,我们现在创建了 16 个块CnDn,1<= n <=16。每对块CnDn分别由前一对Cn-1Dn-1形成,对于n = 1, 2, …, 16,使用以下“左移”时间表上一个块。要进行左移,请将每个位向左移动一位,但第一位除外,它循环到块的末尾

n左移位数
11
21
32
42
52
62
72
82
91
102
112
122
132
142
152
161

这意味着,例如,C3D3分别从C2D2通过两次左移获得,而C16D16 分别从C15D15获得一次左移

C0 = 1111000011001100101010101111
D0 = 0101010101100110011110001111

C1 = 1110000110011001010101011111
D1 = 1010101011001100111100011110

C2 = 1100001100110010101010111111
D2 = 0101010110011001111000111101

C3 = 0000110011001010101011111111
D3 = 0101011001100111100011110101

C4 = 0011001100101010101111111100
D4 = 0101100110011110001111010101

C5 = 1100110010101010111111110000
D5 = 0110011001111000111101010101

C6 = 0011001010101011111111000011
D6 = 1001100111100011110101010101

C7 = 1100101010101111111100001100
D7 = 0110011110001111010101010110

C8 = 0010101010111111110000110011
D8 = 1001111000111101010101011001

C9 = 0101010101111111100001100110
D9 = 0011110001111010101010110011

C10 = 0101010111111110000110011001
D10 = 1111000111101010101011001100

C11 = 0101011111111000011001100101
D11 = 1100011110101010101100110011

C12 = 0101111111100001100110010101
D12 = 0001111010101010110011001111

C13 = 0111111110000110011001010101
D13 = 0111101010101011001100111100

C14 = 1111111000011001100101010101
D14 = 1110101010101100110011110001

C15 = 1111100001100110010101010111
D15 = 1010101010110011001111000111

C16 = 1111000011001100101010101111
D16 = 0101010101100110011110001111

第四步: L R两部分合并,置换 PC-2

Cn Dn来形成密钥 Kn,对于 1<= n* <=16 。每对有 56 位,但PC-2*只使用其中的 48 位

#Permut applied on shifted key to get Ki+1  48
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]

Kn的第一位是Cn Dn的第 14 位,第二位是第 17 位,依此类推,最后 Kn的第 48 位是CnDn的第 32 位。

示例: 对于K1,我们有 C1D1 = 1110000 1100110 0101010 1011111 1010101 0110011 0011110 0011110

在我们应用置换PC-2之后,它变成

K1 = 000110 110000 001011 101111 111111 000111 000001 110010

同理,其他子密钥如下:

K2 = 011110 011010 111011 011001 110110 111100 100111 100101
K3 = 010101 011111 110010 001010 010000 101100 111110 011001
K4 = 011100 101010 110111 010110 110110 110011 010100 011101
K5 = 011111 001110 110000 000111 111010 110101 001110 101000
K6 = 011000 111010 010100 111110 010100 000111 101100 101111
K7 = 111011 001000 010010 110111 111101 100001 100010 111100
K8 = 111101 111000 101000 111010 110000 010011 101111 111011
K9 = 111000 001101 101111 101011 111011 011110 011110 000001
K10= 101100 011111 001101 000111 101110 100100 011001 001111
K11 = 001000 010101 111111 010011 110111 101101 001110 000110
K12 = 011101 010111 000111 110101 100101 000110 011111 101001
K13 = 100101 111100 010111 010001 111110 101011 101001 000001
K14 = 010111 110100 001110 110111 111100 101110 011100 111010
K15 = 101111 111001 000110 001101 001111 010011 111100 001010
K16 = 110010 110011 110110 001011 000011 100001 011111 110101

密钥拓展就完成了。

2.2 明文运算

第一步:明文填充 与工作模式

des的明文分组长度为64比特,当明文长度不够分组大小时,我们需要指定某个标准进行填充,最常用的标准是PCKS5PADDING、PCKS7PADDING

最少填充一个字节,填充01;最多填充1个分组,内容为08 08 08 08 08 08 08 08

以此类推,如果需要填充两个字节,那就是02 02

三个字节 03 03 03

四个字节 04 04 04 04

五个字节 05 05 05 05 05

六个字节 06 06 06 06 06 06

七个字节 07 07 07 07 07 07 07

如果明文长度超过一个分组长度,那如何处理呢?

每个分组单独处理?还是彼此之间有联系呢?

这就是工作模式。工作模式分为ECB,CBC,CFB, OFB等;

其中,每个分组单独加密,多个分组一起加密,DES的这种处理方式,叫ECB工作模式。其他的模式自行查阅,这里不展开讲.

第二步: IP 盒置换(初始置换 )

64bit的明文块M,与 初始置换 IP 表,进行初始置换,IP表如下:

Initial permut matrix for the datas
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]

示例: 将前面给出的明文块M ( 0123456789ABCDEF 16进制)转成二进制

M = 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 

经IP初始置换后,得到新的IP如下:

IP= 1100 1100 0000 0000 1100 1100 1111 1111 1111 0000 1010 1010 1111 0000 1010 1010

M 的第 58 位是1,它成为 IP的第一位。M的第 50 位为1,成为IP的第二位。M的第 7 位为0,成为IP的最后一位

第三步:分成左右各32bit

将置换后的块IP分成 32 bit的左半部分 L0 和 32 位的右半部分R0

L0 = 1100 1100 0000 0000 1100 1100 1111 1111
R0 = 1111 0000 1010 1010 1111 0000 1010 1010

第四步:16轮运算

紧接着,进行 16 次迭代,对于 1<= n <=16,使用函数f对两个块(一个 32 位的数据块和一个 48 位的密钥Kn)进行操作,以生成一个 32 位的块. 让 + 表示 XOR 加法。然后对于n从 1 到 16 我们计算

Ln = Rn-1
Rn = Ln-1 + f( Rn-1, Kn)

对于n = 16, L 16 R 16就是最终块。也就是说,在每次迭代中,我们取前一个结果的右 32 位,并将它们作为当前步骤的左 32 位。对于当前步骤中的右 32 位,我们将上一步的左 32 位与计算f进行异或。

示例: 对于n = 1,我们有

K1 = 000110 110000 001011 101111 111111 000111 000001 110010
L1 = R0 = 1111 0000 1010 1010 1111 0000 1010 1010
R1 = L0 + f( R0, K1 ) # + 是xor

接下来的关注点就在f函数上了,DES中的f函数,代表了16轮运算中一轮的全部运算

我们首先将每个块Rn-1从 32 位扩展到 48 位。这是通过使用重复Rn-1中的一些位的选择表来完成的。我们将使用这个选择表称为函数E。因此E(Rn-1) 有一个 32 位的输入块和一个 48 位的输出块。

令 E使其输出的 48 位,写成 8 块,每块 6 位,是通过根据下表按顺序选择其输入中的位来获得的

# Expand matrix to get a 48bits matrix of datas to apply the xor with Ki
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]

示例:我们从R0 计算 E(R0) 如下:

R0 = 1111 0000 1010 1010 1111 0000 1010 1010
E(R0) = 011110 100001 010101 010101 011110 100001 010101 010101
# 请注意,每个 4 个原始位块已扩展为一个 6 个输出位块。

接下来在 f计算中,我们将输出E(Rn-1) 与密钥Kn进行异或: Kn + E(Rn-1)

示例: 对于K1 , E(R0),我们有

K1 = 000110 110000 001011 101111 111111 000111 000001 110010
E(R0)= 011110 100001 010101 010101 011110 100001 010101 010101
K1 + E(R0) = 011000 010001 011110 111010 100001 100110 010100 100111

我们现在有 48 位,或八组六位。我们现在对每组六位做一些奇怪的事情(S 盒压缩置换 ):我们将用它们来寻找S盒中的地址。每组六位将为我们提供不同 S盒中的地址。位于该地址的将是一个 4 位数字。这个 4 位数字将取代原来的 6 位。最终结果是八组 6 位转换为八组 4 位(从S盒)总共 32 位。

将前面的结果写成 48 位,格式如下:

Kn + E(Rn-1) = B1B2B3B4B5B6B7B8 

其中每个 B i 是一组六位。我们现在计算

S1(B1)S2(B2)S3(B3)S4(B4)S5(B5)S6(B6)S7(B7)S8(B8)

其中 S i (B i )指的是第i个S 框的输出。重复一遍,函数S1、S2、…、S8中的每一个都将一个 6 位块作为输入,并产生一个 4 位块作为输出。 S盒定义如下:

# SBOX
S_BOX = [

    [[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],
     ]
]

如果S1是该表中定义的函数并且B是 6 位块,则S1(B)确定如下: B 的第一位和最后一位以2 为基数表示十进制范围 0 到 3 中的数字(或二进制 00 到 11)。让那个数字是 i。B的中间 4 位以2 为基数表示十进制范围 0 到 15(二进制 0000 到 1111)中的数字。让那个数字是j。在表中查找第i行和第j列中的数字。它是一个 0 到 15 范围内的数字,由一个 4 位块唯一表示。该块是输出 S1(B)的S 1 用于输入B。例如,对于输入块B = 011011,第一位是“0”,最后一位是“1”,将 01 作为行。这是第 1 行。中间四位是“1101”。这是十进制 13 的二进制等值,因此该列是列号 13。在第 1 行中,第 13 列出现 5。这决定了输出;5 是二进制 0101,因此输出为 0101。因此S1 (011011) = 0101

示例: 对于第一轮,我们得到八个S框的输出:

K1 + E(R0) = 011000 010001 011110 111010 100001 100110 010100 100111
S1(B1)S2(B2)S3(B3)S4(B4)S5(B5)S6(B6)S7(B7)S8(B8) = 0101 1100 1000 0010 1011 0101 1001 0111

接着,对S- box 输出进行**P盒置换 **以获得f的最终值:

f = P(S1(B1)S2(B2)...S8(B8))

P盒定义如下:

# Permut made after each SBox substitution for each round
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]

示例: 从八个S框的输出:

S1(B1)S2(B2)S3(B3)S4(B4)S5(B5)S6(B6)S7(B7)S8(B8) = 0101 1100 1000 0010 1011 0101 1001 0111

进行P盒置换,得到

f = 0010 0011 0100 1010 1010 1001 1011 1011

至此,F 函数大功告成

最后,每轮运算的最后,进行一次异或运算

R1 = L0 + f (R0 , K1)
	= 1100 1100 0000 0000 1100 1100 1111 1111
	+ 0010 0011 0100 1010 1010 1001 1011 1011
	= 1110 1111 0100 1010 0110 0101 0100 0100

在下一轮中,我们将有L2 = R1,也就是我们刚刚计算的块,然后我们必须计算R2 = L1 + f(R1 , K2),以此类推 16 轮。在第十六轮结束时,我们有块L16 和R16。然后我们 将两个块的顺序颠倒成64位块, 得到R16L16

第五步:逆初始置换(末置换)

末置换也用到表,叫FP表(即Final Permutation)

# Final permut for datas after the 16 rounds
FP = [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]

示例: 如果我们使用之前定义的方法处理所有 16 个块,我们在第 16 轮得到,

L16 = 0100 0011 0100 0010 0011 0010 0011 0100
R16 = 0000 1010 0100 1100 1101 1001 1001 0101

我们颠倒这两个块的顺序并将最终排列应用于

R16 L16 = 00001010 01001100 11011001 10010101 01000011 01000010 00110010 00110100

FP = 10000101 11101000 00010011 01010100 00001111 00001010 10110100 00000101

转成十六进制格式是85E813540F0AB405

这是明文块0123456789ABCDEF, 密钥133457799BBCDFF1经des加密后的结果(不考虑填充),即 85E813540F0AB40


三、语言实现

# -*- coding: utf8 -*-

# Initial permut matrix for the datas
PI = [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]

# Initial permut made on the key
CP_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]

# Permut applied on shifted key to get Ki+1
CP_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]

# Expand matrix to get a 48bits matrix of datas to apply the xor with Ki
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]

# SBOX
S_BOX = [

    [[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],
     ]
]

# Permut made after each SBox substitution for each round
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]

# Final permut for datas after the 16 rounds
PI_1 = [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]

# Matrix that determine the shift for each round of keys
SHIFT = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]


def string_to_bit_array(text):  # Convert a string into a list of bits
    array = list()
    for char in text:
        binval = binvalue(char, 8)  # Get the char value on one byte
        array.extend([int(x) for x in list(binval)])  # Add the bits to the final list
    return array


def bit_array_to_string(array):  # Recreate the string from the bit array
    res = ''.join([chr(int(y, 2)) for y in [''.join([str(x) for x in _bytes]) for _bytes in nsplit(array, 8)]])
    return res


def binvalue(val, bitsize):  # Return the binary value as a string of the given size
    binval = bin(val)[2:] if isinstance(val, int) else bin(ord(val))[2:]
    if len(binval) > bitsize:
        raise "binary value larger than the expected size"
    while len(binval) < bitsize:
        binval = "0" + binval  # Add as many 0 as needed to get the wanted size
    return binval


def nsplit(s, n):  # Split a list into sublists of size "n"
    return [s[k:k + n] for k in range(0, len(s), n)]


ENCRYPT = 1
DECRYPT = 0


class des():
    def __init__(self):
        self.password = None
        self.text = None
        self.keys = list()

    def run(self, key, text, action=ENCRYPT, padding=False):
        if len(key) < 8:
            raise "Key Should be 8 bytes long"
        elif len(key) > 8:
            key = key[:8]  # If key size is above 8bytes, cut to be 8bytes long

        self.password = key
        self.text = text

        if padding and action == ENCRYPT:
            self.addPadding()
        elif len(self.text) % 8 != 0:  # If not padding specified data size must be multiple of 8 bytes
            raise "Data size should be multiple of 8"

        self.generatekeys()  # Generate all the keys
        text_blocks = nsplit(self.text, 8)  # Split the text in blocks of 8 bytes so 64 bits
        result = list()
        for block in text_blocks:  # Loop over all the blocks of data
            block = string_to_bit_array(block)  # Convert the block in bit array
            block = self.permut(block, PI)  # Apply the initial permutation
            g, d = nsplit(block, 32)  # g(LEFT), d(RIGHT)
            tmp = None
            for i in range(16):  # Do the 16 rounds
                d_e = self.expand(d, E)  # Expand d to match Ki size (48bits)
                if action == ENCRYPT:
                    tmp = self.xor(self.keys[i], d_e)  # If encrypt use Ki
                else:
                    tmp = self.xor(self.keys[15 - i], d_e)  # If decrypt start by the last key
                tmp = self.substitute(tmp)  # Method that will apply the SBOXes
                tmp = self.permut(tmp, P)
                tmp = self.xor(g, tmp)
                g = d
                d = tmp
            result += self.permut(d + g, PI_1)  # Do the last permut and append the result to result
        final_res = bit_array_to_string(result)
        if padding and action == DECRYPT:
            return self.removePadding(final_res)  # Remove the padding if decrypt and padding is true
        else:
            return final_res  # Return the final string of data ciphered/deciphered

    def substitute(self, d_e):  # Substitute bytes using SBOX
        subblocks = nsplit(d_e, 6)  # Split bit array into sublist of 6 bits
        result = list()
        for i in range(len(subblocks)):  # For all the sublists
            block = subblocks[i]
            row = int(str(block[0]) + str(block[5]), 2)  # Get the row with the first and last bit
            column = int(''.join([str(x) for x in block[1:][:-1]]), 2)  # Column is the 2,3,4,5th bits
            val = S_BOX[i][row][column]  # Take the value in the SBOX appropriated for the round (i)
            bin = binvalue(val, 4)  # Convert the value to binary
            result += [int(x) for x in bin]  # And append it to the resulting list
        return result

    def permut(self, block, table):  # Permut the given block using the given table (so generic method)
        return [block[x - 1] for x in table]

    def expand(self, block, table):  # Do the exact same thing than permut but for more clarity has been renamed
        return [block[x - 1] for x in table]

    def xor(self, t1, t2):  # Apply a xor and return the resulting list
        return [x ^ y for x, y in zip(t1, t2)]

    def generatekeys(self):  # Algorithm that generates all the keys
        self.keys = []
        key = string_to_bit_array(self.password)
        key = self.permut(key, CP_1)  # Apply the initial permut on the key
        g, d = nsplit(key, 28)  # Split it in to (g->LEFT),(d->RIGHT)
        for i in range(16):  # Apply the 16 rounds
            g, d = self.shift(g, d, SHIFT[i])  # Apply the shift associated with the round (not always 1)
            tmp = g + d  # Merge them
            self.keys.append(self.permut(tmp, CP_2))  # Apply the permut to get the Ki

    def shift(self, g, d, n):  # Shift a list of the given value
        return g[n:] + g[:n], d[n:] + d[:n]

    def addPadding(self):  # Add padding to the datas using PKCS5 spec.
        pad_len = 8 - (len(self.text) % 8)
        self.text += pad_len * chr(pad_len)

    def removePadding(self, data):  # Remove the padding of the plain text (it assume there is padding)
        pad_len = ord(data[-1])
        return data[:-pad_len]

    def encrypt(self, key, text, padding=False):
        return self.run(key, text, ENCRYPT, padding)

    def decrypt(self, key, text, padding=False):
        return self.run(key, text, DECRYPT, padding)


if __name__ == '__main__':
    key = "hellohel"
    text = "Hello woHello wo"
    d = des()
    r = d.encrypt(key, text)
    r2 = d.decrypt(key, r)
    print("Ciphered: %r" % r)
    print("Deciphered: ", r2)

四、3DES

按照我们的想象,3重DES应该是三次DES加密,但事实上,三重DES是加密+解密+加密

请添加图片描述

这种设计出于两个原因:

  • 1.The second key is used to DES-decrypt the encrypted message. (Since the second key is not the right key, this decryption just scrambles the data further.)

    第二个密钥用于对加密的消息进行DES解密。 (由于第二个密钥不是正确的密钥,因此该解密只会进一步扰乱数据。)

  • 这个方案由IBM公司设计和提出,理由是这样的话,当三个密钥相同时,3重DES等于普通的DES,这样就可以实现向下的兼容,使用DES的,比如老旧的银行系统等等,兼容性对它们很重要,因此这个方案被采纳了


五、Tips

  • 标准的DES算法,为什么Findcrypt找不到呢?

    采用了空间换时间的查表法实现

  • DES算法的魔改

    • 可以改动的地方实在太多
    • 最不会造成安全性下降,又好用,而且能减少DES特征的,就是去掉初始置换以及末置换,因为它俩本来就缺少密码学上的意义

六、参考

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值