对称算法(DES篇)

DES (Data Encryption Standard)算法是世界上最常用的加密算法。在很长时间内,许多人心目中“密码生成”与DES一直是个同义词。尽管最近有个叫Electronic Frontier Foundation的组织造了台价值22万的机器尝试破解DES加密的数据,DES和它的变种“三重数据加密算法”仍将在政府和银行中广泛应用。

概念

DES全称为Data Encryption Standard,即数据加密标准。1997年数据加密标准DES正式公布,其分组长度为64比特,密钥长度为64比特,其中8比特为奇偶校验位,所以实际长度为56比特。

特点

数据加密标准,速度较快,适用于加密大量数据的场合。

原理概述

DES是一个分组加密算法,就是将明文分组进行加密,每次按顺序取明文一部分,一个典型的DES以64位为分组,加密解密用算法相同。它的密钥长度为56位,因为每组第8位是用来做奇偶校验,密钥可以是任意56位的数,保密性依赖于密钥。

利用56+8奇偶校验位(第8,16,24,32,40,48,56,64)=64位的密钥对以64位为单位的块数据进行加解密。

具体加密步骤

所需参数:

key:8个字节共64位的工作密钥

data:8个字节共64位的需要被加密或被解密的数据

mode:DES工作方式,加密或者解密

1、置换IP:(生成16个子密钥,每个密钥长度为48 bit)

利用初始置换IP(Initial Permutation)对明文X进行换位处理,打乱原来的次序,得到一个乱序的64 bit 明文组。使用64位的密钥key将64位的明文输入块变为64位的密文输出块。

注意:这里的数字表示的是原数据的位置,不是数据

即将X中的58位数据放在转换后生成的X’表的第1位,X中的第50位放在X’的第2位,X中的42位放在第3位…X中的第7位放在X’的最后一位。

置换表如下:(纵向看)

0 1 2 3 4 5 6 7
0 58 50 42 34 26 18 10 2
1 60 52 44 36 28 20 12 4
2 62 54 46 38 30 22 14 6
3 64 56 48 40 32 24 16 8
4 57 49 41 33 25 17 9 1
5 59 51 43 35 27 19 11 3
6 61 53 45 37 29 21 13 5
7 63 55 47 39 31 23 15 7

代码实现(C语言):

const unsigned char IP_Table[64] = 
{ 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 }; 
int IP_Substitution(const unsigned char* BitPlain, unsigned char* Bit_IP_Table)
{ 
    int ret = 0; 
    for (int i = 0; i < 64; i++) { 
    Bit_IP_Table[i] = BitPlain[IP_Table[i] - 1]; 
    } 
  return ret; 
} 

2、分组(L,R组)

把输出块分为L0、R0两部分,每部分均为32位。0-3为L组,3-7为R组

0 1 2 3 4 5 6 7
0 58 50 42 34 26 18 10 2
1 60 52 44 36 28 20 12 4
2 62 54 46 38 30 22 14 6
3 64 56 48 40 32 24 16 8
4 57 49 41 33 25 17 9 1
5 59 51 43 35 27 19 11 3
6 61 53 45 37 29 21 13 5
7 63 55 47 39 31 23 15 7

代码实现(C语言):

unsigned char Bit_IP_Table[64]; //初始置换后的明文表 
unsigned char BitL_Table[17][32]; //L表Bit组 
unsigned char BitR_Table[17][32]; //R表Bit组 

memcpy(BitL_Table[0], Bit_IP_Table, 32); 
memcpy(BitR_Table[0], &Bit_IP_Table[32],32); 

3、右分组(R组)由32位扩展为48位

通过扩展置换变对R组进行扩展,原理和前面的置换表差不多,就是多了一些重复的映射

第0列、5列为拓展数据,1-4列为原始数据,

0 1 2 3 4 5
0 32 1 2 3 4 5
1 4 5 6 7 8 9
2 8 9 10 11 12 13
3 12 13 14 15 16 17
4 16 17 18 19 20 21
5 20 21 22 23 24 25
6 24 25 26 27 28 29
7 28 29 30 31 32 1

代码实现(C语言):

const unsigned char E_Table[48] = 
{ 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}; 
int E_Substitution(const unsigned char* BitR_Table, unsigned char* BitE_Table) 
{ 
    int ret = 0; 
    for (int i = 0; i < 48; i++) { 
        BitE_Table[i] = BitR_Table[E_Table[i] - 1]; 
    } 
    return ret; 
} 
#### 4.扩展的R组和48位密钥进行异或 
int DES_XOR(const unsigned char* Bit1_Table, const unsigned char* Bit2_Table, unsigned char* Bit_Xor/*异或运算的结果*/, int nBit/*异或运算的位数*/) { 
    int ret = 0; 
    for (int i = 0; i < nBit; i++) { 
        Bit_Xor[i] = Bit1_Table[i] ^ Bit2_Table[i]; 
    } 
    return ret; 
} 

4、将48位异或的结果转换为32位

详细过程:

先将48为分成8组,每组6位,在将6位数字经过变化转换为4位

**6->4操作:**例(一个6位的分组)

100101

将该分组的第一位和最后一位取出来,以二进制的形式表示,作为行索引

将该分组的第2到5位取出来,以二进制的形式表示,作为列索引

代码实现(C语言):

const unsigned char S_Table[8][4][16] = { 
    //S1盒 
    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, 
    //S2盒 
    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, 
    //S3盒 
    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, 
    //S4盒 
    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, 
     //S5盒 
     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,
     //S6盒 
      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, 
      //S7盒 
      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,
      //S8盒 
      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 }; 
unsigned char Bit_Xor[8][6]; //存放异或运算的结果 
unsigned char Bit_Integer[8][4]; //将整数变成Bit位 
unsigned char Row; //S盒的行号 unsigned char Col; 
//S盒的列号 
unsigned char Integer; 
//从S盒中取得的32位整数 
for (int i = 0; i < 8; i++) { 
//计算S盒的行号和列号 
    Row = (Bit_Xor[i][0] << 1) + Bit_Xor[i][5];
     Col = (Bit_Xor[i][1] << 3) + (Bit_Xor[i][2] << 2) + (Bit_Xor[i][3] << 1) + Bit_Xor[i][4]; 
 //从S盒中取得整数 
 Integer = S_Table[i][Row][Col]; 
 //将取得的4Bit数转换成Bit组 
for (int j = 0; j < 4; j++) { 
     Bit_Integer[i][j] = Integer >> (3 - j) & 1; 
 } 
 } 

5、p置换

同最开始的初始置换,只是对数据进行了打乱操作

0 1 2 3 4 5 6 7
0 16 7 20 21 29 12 28 17
1 1 15 23 26 5 18 31 10
2 2 8 24 14 32 27 3 9
3 19 13 30 6 22 11 4 25

代码实现(C语言):

const unsigned char P_Table[32] =
{
    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
};
int P_Substitution(const unsigned char *Bit_Integer, unsigned char* BitP_Table)
{
    int ret = 0;
    for (int i = 0; i < 32; i++)
    {
        BitP_Table[i] = Bit_Integer[P_Table[i] - 1];
    }
    return ret;
}

6、将返回的结果和L组进行异或操作,作为新的R组,并将旧的R组赋值给新的L组

代码实现(C语言):

R[i+1] = L[i] ^ F(R[i], K[i]);
L[i+1] = R[i];

7、按照3、4、5、6步重复16次

代码实现(C语言):

for (int i = 0; i < 16; i++)
{
    //将R组和子密钥组进行F函数运算
    DES_F_Function(BitR_Table[i], BitSubKey[i], Bit_F_Out);
     //L组盒F函数的输出结果进行异或运算
    DES_XOR(BitL_Table[i], Bit_F_Out, BitR_Table[i + 1], 32);
     //Li+1 = Ri
    memcpy(BitL_Table[i + 1], BitR_Table[i], 32);
} 
//L[16]和R[16]进行交叉合并
memcpy(BitRL_Table,         BitR_Table[16], 32);
memcpy(&BitRL_Table[32],    BitL_Table[16], 32);

8、逆初始值置换

对上述16步操作后的结果进行逆初始值置换操作,也是类似于初始置换表

代码实现(C语言):

const unsigned char reIP_Table[64] =
{
    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
};
int reIP_Substitution(const unsigned char *BitRL_Table, unsigned char *Bit_reIP_Table)
{
    int ret = 0;
 
    for (int i = 0; i < 64; i++)
    {
        Bit_reIP_Table[i] = BitRL_Table[reIP_Table[i] - 1];
    }
    return ret;
}

9.密钥的扩展和加密

PC-1置换

将64位密钥中的8、16、24、32、40、48、56、64位剔除作为校验位。

其余的位进行置换表操作

const unsigned char PC_1_Table[56] =
    {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
};
 
int PC_1_Substitution(const unsigned char *BitKey, unsigned char *BitKey_PC_1)
{
    int ret = 0;
 
    for (int i = 0; i < 56; i++)
    {
        BitKey_PC_1[i] = BitKey[PC_1_Table[i] - 1];
    }
 
    return ret;
}

然后对56位数据进行分组,分成C、D组,采用上下分组,再进行16次变化,每次变化后会进行(PC-2置换)生成一次密钥用作每一轮的加密

每轮变化都进行整体左位移操作(并把前面超出范围的两位数据移动到数据的最后面),根据具体的轮数位移的位数也不同,其中1、2、9、16轮中,位移1位,其余轮位移2位

const unsigned char Bit_Round[16] =
{
    1, 1, 2, 2,
    2, 2, 2, 2,
    1, 2, 2, 2,
    2, 2, 2, 1
};
int BitRound_L(const unsigned char* SrcBitGroup, unsigned char* DesBitGroup, int nBit)
{
    int ret = 0;
 
    memcpy(DesBitGroup,             &SrcBitGroup[nBit], 28 - nBit);
    memcpy(&DesBitGroup[28 - nBit], SrcBitGroup,        nBit);
    return ret;
}
//将C、D两组进行轮转移位操作    左移
BitRound_L(BitC_Table[i], BitC_Table[i + 1], Bit_Round[i]);
BitRound_L(BitD_Table[i], BitD_Table[i + 1], Bit_Round[i]);
密钥生成算法PC-2置换

PC-2置换

const unsigned char PC_2_Table[48]= 
{ 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}; 
int PC_2_Substitution(const unsigned char *BitKey, unsigned char *SubKey) 
{                    
    int ret = 0; 
    for (int i = 0; i < 48; i++){ 
    SubKey[i] = BitKey[PC_2_Table[i] - 1]; 
    } 
    return ret; 
} 

Java代码实现:

根据模式的不同有很多实现方式,现展示一种方式,主要抓住特征private static final String ALGORITHM = “DES”;来对加密方式进行判断,标准的DES算法以字符串输出时很容易产生乱码,因此输出时需要将其转化为hex或者base64编码的形式

package abc; 
import javax.crypto.Cipher; 
import javax.crypto.SecretKey; 
import javax.crypto.SecretKeyFactory; 
import javax.crypto.spec.DESKeySpec; 
import java.util.Base64; 
public class DES { 
public static void main(String[] args) throws Exception { 
    DESKeySpec desKey=new DESKeySpec("12345678".getBytes()); 
    SecretKeyFactory key=SecretKeyFactory.getInstance("DES"); 
    SecretKey secretKey=key.generateSecret(desKey); 
    Cipher cipher=Cipher.getInstance("DES/ECB/PKCS5Padding"); 
    cipher.init(1, secretKey); 
    byte[] res=cipher.doFinal("a12345678".getBytes()); 
    System.out.println(Base64.getEncoder().encodeToString(res)); 
    } 
}