一、分组密码的基本概念
1、分组密码概述
(1)分组密码是许多系统安全的一个重要组成部分,可用于构造伪随机数生成器、流密码、消息认证码(MAC)和杂凑函数,是消息认证技术、数据完整性机制、实体认证协议以及单钥数字签字体制的核心组成部分。
(2)分组密码,就是把明文切成固定长度的"块"(分组),每一块用同一个密钥独立加密/解密的密码算法。
2、分组密码的设计原则
(1)分组密码的设计问题在于找到一种算法,能在密钥控制下从一个足够大且足够好的置换子集中,简单而迅速地选出一个置换,用来对当前输入的明文的数字组进行加密变换。
(2)安全性设计原则:
①混淆原则:
混淆原则就是将密文、明文、密钥三者之间的统计关系和代数关系变得尽可能复杂,使得敌手即使获得了密文和明文,也无法求出密钥的任何信息,即使获得了密文和明文的统计规律,也无法求出明文的新的信息
可进一步理解,明文不能由已知的明文、密文及少许密钥bit代数地或统计地表示出来,密钥不能由已知的明文、密文及少许密钥bit代数地或统计地表示出来
②扩展原则:
扩散原则就是应将明文的统计规律和结构规律散射到相当长的一段统计中去
也就是说让明文中的每一位影响密文中的尽可能多的位,或者说让密文中的每一位都受到明文中的尽可能多位的影响
如果当明文变化一个bit时,密文有某些bit不可能发生变化,则这个明文就与那些密文无关,因而在攻击这个明文bit时就可不利用那些密文bit
(3)分组密码算法应满足的要求:
①分组长度n要足够大:防止明文穷举攻击法奏效。
②密钥量要足够大:尽可能消除弱密钥并使所有密钥同等地好,以防止密钥穷举攻击奏效。
③由密钥确定置换的算法要足够复杂:充分实现明文与密钥的扩散和混淆,没有简单的关系可循,要能抗击各种已知的攻击。
④加密和解密运算简单:易于软件和硬件高速实现。
⑤数据扩展:一般无数据扩展,在采用同态置换和随机化加密技术时可引入数据扩展。
⑥差错传播尽可能地小:一个密文分组的错误尽可能少地影响其它密文分组的解密。
⑦软件实现的原则:
1\]使用子块和简单的运算。如将分组n化分为子段,每段长为8、16或32。
\[2\]应选用简单的运算,使作用于子段上的密码运算易于以标准处理器的基本运算,如加、乘、移位等实现,避免用以软件难于实现的逐比特置换。
⑧硬件实现的原则:加密解密可用同样的器件来实现。
## 二、SP网络
### 1、代换
(1)代换的定义:
如果明文和密文的分组长都为n比特,则明文的每一个分组都有个可能的取值,为使加密运算可逆(使解密运算可行),明文的每一个分组都应产生唯一的一个密文分组,这样的变换是可逆的,称明文分组到密文分组的可逆变换为代换
不同可逆变换的个数有个
(2)代换示例:


(3)代换的弱点:
如果分组长度太小,如n=4,系统等价于古典的代换密码,容易通过对明文的统计分析而被攻破,不过这个弱点不是代换结构固有的,只是因为分组长度太小,如果分组长度n足够大,而且从明文到密文可有任意可逆的代换,那么明文的统计特性将被隐藏而使以上的攻击不能奏效
从实现的角度来看,分组长度很大的可逆代换结构是不实际的,仍以4bit的代换表为例,上表定义了n=4时从明文到密文的一个可逆映射,其中第2列是每个明文分组对应的密文分组的值,可用来定义这个可逆映射,从本质上来说,第2列是从所有可能映射中决定某一特定映射的密钥,密钥需要64比特

### 2、SP网络介绍
(1)SP 网络(Substitution-Permutation Network),就是由两种基本操作交替组合而成的分组密码结构:
①S(Substitution,代换/替换):将固定长度的输入块,通过一个非线性表(如S盒)映射成另一个值,目的是引入混淆,破坏明文和密文之间的线性关系。
②P(Permutation,置换/扩散):对数据的比特位置进行打乱重排,目的是引入扩散,让明文/密钥的微小变化扩散到整个密文块。
(2)代换网络:
①代换的概念:

②代换网络与全代换网络:

(3)代换盒(S盒):

## 三、Feistel密码结构
### 1、Feistel密码设计思想
(1)Feistel提出了实现代换和置换的方法,其思想实际上是Shannon提出的利用乘积密码实现混淆和扩散思想的具体应用。(乘积密码指顺序地执行两个或多个基本密码系统,使得最后结果的密码强度高于每个基本密码系统产生的结果)
(2)Feistel网络的实现与以下参数和特性有关:
①分组大小:分组越大则安全性越高,但加密速度就越慢。
②密钥大小:密钥越长则安全性越高,但加密速度就越慢。
③轮数:单轮结构远不足以保证安全性,但多轮结构可提供足够的安全性。典型地,轮数取为16。
④子密钥产生算法:该算法的复杂性越大,则密码分析的困难性就越大。
⑤轮函数:轮函数的复杂性越大,密码分析的困难性也越大。
(3)在设计Feistel网络时,还有以下两个方面需要考虑:
①快速的软件实现:在很多情况中,算法是被镶嵌在应用程序中,因而无法用硬件实现,此时算法的执行速度是考虑的关键。
②算法容易分析:如果算法能被无疑义地解释清楚,就可容易地分析算法抵抗攻击的能力,有助于设计高强度的算法。
### 2、Feistel密码加解密结构
(1)Feistel加密结构:


(2)Feistel解密结构:


(3)Feistel密码解密的正确性:


## 四、DES算法
### 1、DES的框架和主要参数
(1)DES的主要参数:
①明文分组长度为64bits(8字节),密文分组长度为64bits(8字节)。
②密钥长度为64bits,其中有8用于奇偶校验,有效密钥长度为56bits。
(2)DES算法主要包括------初始置换、16轮迭代的乘积变换、逆初始置换以及16个子密钥产生器。如下为DES算法框图:



(3)DES算法流程示意图:

### 2、初始置换与逆初始置换



### 3、DES的轮函数
(1)DES的轮函数的结构:


(2)DES算法轮结构:
①DES是典型的Feistel结构,每一轮的核心操作可以用公式表示

②如下图所示,左侧是Feistel轮的数据通路,右侧是本轮的子密钥生成流程。

③Feistel轮的数据通路(一轮):
\[1\]轮函数是DES实现"混淆与扩散"的关键,分为四步:


\[2\]轮函数输出与左右更新:

④子密钥生成流程(一轮):


(3)扩展运算E:

(4)代换运算S:


(5)置换运算P:

### 4、DES的密钥编排
(1)DES 的原始密钥是64bit,但其中只有56bit是有效密钥,另外8bit是奇偶校验位(每字节的第8位),密钥编排的目标,就是把这56bit密钥,生成16个不同的48bit子密钥,供16轮Feistel迭代使用。
(2)密钥编排流程可以分为三大步:

①PC-1置换选择1:去掉8个校验位(第8、16、...、64位),依据PC-1表(规则和P盒类似)将64bit密钥处理为56bit,并打乱顺序。

②循环左移:将56bit比特分成两半,每轮循环左移1位或2位,移位次数由"移位次数表"决定。

③PC-2置换选择2:将移位后的两半数据拼接为56bit数据,依据PC-2表(规则和P盒类似)将其压缩为48bit,得到本轮的子密钥。

### 5、DES算法的C语言实现
```cpp
#include
#include
#include
#include
// ------------------------- 置换表 -------------------------
// 初始置换 IP
static const uint8_t IP[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
};
// 逆初始置换 IP^(-1)
static const uint8_t FP[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
};
// 扩展置换 E (32 -> 48)
static const uint8_t E[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
};
// 置换 P (32 -> 32)
static const uint8_t P[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
};
// S盒 (8个,每个输入6位,输出4位)
static const uint8_t S[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}
}
};
// 密钥调度表
// PC-1 (64 -> 56)
static const uint8_t PC1[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
};
// PC-2 (56 -> 48)
static const uint8_t PC2[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
};
// 每轮循环左移的位数 (第1,2,...,16轮)
static const uint8_t SHIFT[16] = {
1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
};
// ------------------------- 辅助函数 -------------------------
// 将一个64位整数的指定位(从1开始计数)取出,组成一个bit数组
// 这里我们实现通用的置换函数,输入64位数据,输出置换后的64位或更少
static uint64_t permute(uint64_t input, const uint8_t *table, int n) {
uint64_t output = 0;
for (int i = 0; i < n; i++) {
int bit_pos = table[i] - 1; // 表中位置从1开始,转换为0-index
uint64_t bit = (input >> (63 - bit_pos)) & 1; // 注意input最高位是第1位?实际我们通常按位处理简化
// 但我们更常用另一种方法:将输入视为64位值,位索引0为最高位。为了简单,我们按字节处理较好。
// 为了清晰,我们编写另一个函数:对64位数据进行位提取,这里采用直接位运算。
// 下面的实现假设输入数据为64位,bits从63(最高)到0(最低)。表中编号1对应最高位。
// 构造方式:output左移一位再或上bit。
output |= (bit << (n - 1 - i));
}
return output;
}
// 更实用的置换函数:将64位块按置换表重新排列,返回64位结果(n=64)或56位等。
// 采用移位循环方法,每次取一位。
// 注意:表值从1开始,对应输入最高位(bit63)
static uint64_t permute64(uint64_t input, const uint8_t *table, int n) {
uint64_t out = 0;
for (int i = 0; i < n; i++) {
int pos = table[i] - 1; // 0-index from MSB
uint64_t bit = (input >> (63 - pos)) & 1;
out = (out << 1) | bit;
}
return out;
}
// 对于56位的密钥段(PC-1结果)进行置换,输出48位子密钥
static uint64_t permute56to48(uint64_t input, const uint8_t *table, int n) {
uint64_t out = 0;
// 输入为56位,存在低56位中,最高位(bit63没用)。但我们的输入实际是56位值,位55为最高?我们将其视为一个数值,最高位是bit55。
// 为了统一,我们使用一个辅助函数,将56位值视为bit55..bit0,并在提取时做转换。
// 更方便:在生成子密钥时,我们使用两个28位半块(C,D),然后组合成56位,再通过PC-2。
// 为了代码清晰,我们将在密钥调度中直接使用两个28位变量。
return out; // 暂未实现,下面会单独处理子密钥生成
}
// ------------------------- 核心DES函数 -------------------------
// 生成16轮子密钥 (每轮48位)
static void generate_subkeys(uint64_t key, uint64_t subkeys[16]) {
// 步骤1: PC-1,将64位密钥变成56位
uint64_t pc1_result = permute64(key, PC1, 56);
// 拆分56位为C0(28位高位)和D0(28位低位)
uint32_t C = (pc1_result >> 28) & 0x0FFFFFFF; // 高28位
uint32_t D = pc1_result & 0x0FFFFFFF; // 低28位
// 生成16轮子密钥
for (int i = 0; i < 16; i++) {
// 循环左移
uint8_t shift = SHIFT[i];
C = ((C << shift) | (C >> (28 - shift))) & 0x0FFFFFFF;
D = ((D << shift) | (D >> (28 - shift))) & 0x0FFFFFFF;
// 合并C和D成56位
uint64_t combined = ((uint64_t)C << 28) | D;
// PC-2置换得到48位子密钥
uint64_t subkey = 0;
for (int j = 0; j < 48; j++) {
int pos = PC2[j] - 1; // PC2表中值1..56,对应56位中的位位置(最高位索引0)
// 在combined中,位55为最高位(MSB),位0为最低位。
// pos=0对应combined的最高位,即(combined >> 55) & 1
uint64_t bit = (combined >> (55 - pos)) & 1;
subkey = (subkey << 1) | bit;
}
subkeys[i] = subkey;
}
}
// F函数:输入32位R和48位子密钥,输出32位
static uint32_t f_function(uint32_t R, uint64_t subkey) {
// 1. 扩展置换 E: 32 -> 48
uint64_t expanded = 0;
for (int i = 0; i < 48; i++) {
int pos = E[i] - 1; // E表中值1..32,对应R的位索引(R位31最高,0最低)
uint32_t bit = (R >> (31 - pos)) & 1;
expanded = (expanded << 1) | bit;
}
// 2. 与子密钥异或
uint64_t xored = expanded ^ subkey;
// 3. S盒替换: 48 -> 32
uint32_t sbox_output = 0;
for (int i = 0; i < 8; i++) {
// 取6位块 (从高位到低位)
uint8_t block = (xored >> (42 - 6 * i)) & 0x3F; // 每次取6位,注意xored有48位,位47最高
// 确定S盒行和列
uint8_t row = ((block & 0x20) >> 4) | (block & 0x01); // 第1位和第6位组合成行(0-3)
uint8_t col = (block >> 1) & 0x0F; // 中间4位列(0-15)
uint8_t s_val = S[i][row][col];
sbox_output = (sbox_output << 4) | s_val;
}
// 4. P置换: 32 -> 32
uint32_t p_result = 0;
for (int i = 0; i < 32; i++) {
int pos = P[i] - 1; // P表中值1..32,对应sbox_output的位31最高
uint32_t bit = (sbox_output >> (31 - pos)) & 1;
p_result = (p_result << 1) | bit;
}
return p_result;
}
// DES加密一轮:输入(L,R)和子密钥,输出新的(L,R)
static void des_round(uint32_t *L, uint32_t *R, uint64_t subkey) {
uint32_t new_R = *L ^ f_function(*R, subkey);
*L = *R;
*R = new_R;
}
// DES加密64位数据块,使用16轮子密钥
static uint64_t des_encrypt_block(uint64_t block, uint64_t subkeys[16]) {
// 初始置换IP
uint64_t ip = permute64(block, IP, 64);
// 拆分为L和R (各32位)
uint32_t L = (ip >> 32) & 0xFFFFFFFF;
uint32_t R = ip & 0xFFFFFFFF;
// 16轮迭代
for (int i = 0; i < 16; i++) {
des_round(&L, &R, subkeys[i]);
}
// 交换左右 (最后一轮不交换,但标准DES在16轮后交换,然后逆初始置换)
uint64_t pre_output = ((uint64_t)R << 32) | L;
// 逆初始置换
uint64_t cipher = permute64(pre_output, FP, 64);
return cipher;
}
// DES解密64位数据块,子密钥逆序使用
static uint64_t des_decrypt_block(uint64_t block, uint64_t subkeys[16]) {
uint64_t ip = permute64(block, IP, 64);
uint32_t L = (ip >> 32) & 0xFFFFFFFF;
uint32_t R = ip & 0xFFFFFFFF;
// 使用逆序子密钥
for (int i = 15; i >= 0; i--) {
des_round(&L, &R, subkeys[i]);
}
uint64_t pre_output = ((uint64_t)R << 32) | L;
uint64_t plain = permute64(pre_output, FP, 64);
return plain;
}
// ------------------------- 用户接口 -------------------------
void des_encrypt(const uint8_t *plain, size_t len, const uint8_t *key, uint8_t *cipher) {
// 补位?假设输入是8字节的倍数。实际中需要填充,这里简化,仅处理8字节。
uint64_t block = 0;
for (int i = 0; i < 8; i++) {
block = (block << 8) | plain[i];
}
uint64_t key64 = 0;
for (int i = 0; i < 8; i++) {
key64 = (key64 << 8) | key[i];
}
uint64_t subkeys[16];
generate_subkeys(key64, subkeys);
uint64_t encrypted = des_encrypt_block(block, subkeys);
for (int i = 7; i >= 0; i--) {
cipher[i] = encrypted & 0xFF;
encrypted >>= 8;
}
}
void des_decrypt(const uint8_t *cipher, size_t len, const uint8_t *key, uint8_t *plain) {
uint64_t block = 0;
for (int i = 0; i < 8; i++) {
block = (block << 8) | cipher[i];
}
uint64_t key64 = 0;
for (int i = 0; i < 8; i++) {
key64 = (key64 << 8) | key[i];
}
uint64_t subkeys[16];
generate_subkeys(key64, subkeys);
uint64_t decrypted = des_decrypt_block(block, subkeys);
for (int i = 7; i >= 0; i--) {
plain[i] = decrypted & 0xFF;
decrypted >>= 8;
}
}
// ------------------------- 测试 -------------------------
void print_hex(uint8_t *data, int len) {
for (int i = 0; i < len; i++) {
printf("%02X ", data[i]);
}
printf("\n");
}
int main() {
// 测试向量:明文 0x0123456789ABCDEF,密钥 0x133457799BBCDFF1 (标准DES测试密钥)
uint8_t plain[8] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };
uint8_t key[8] = { 0x13, 0x34, 0x57, 0x79, 0x9B, 0xBC, 0xDF, 0xF1 };
uint8_t cipher[8] = { 0 };
uint8_t decrypted[8] = { 0 };
printf("Plaintext: ");
print_hex(plain, 8);
printf("Key: ");
print_hex(key, 8);
des_encrypt(plain, 8, key, cipher);
printf("Ciphertext: ");
print_hex(cipher, 8);
des_decrypt(cipher, 8, key, decrypted);
printf("Decrypted: ");
print_hex(decrypted, 8);
// 验证
if (memcmp(plain, decrypted, 8) == 0) {
printf("Success: encryption and decryption match.\n");
}
else {
printf("Error: mismatch.\n");
}
return 0;
}
```
### 6、DES的安全性


### 7、双重DES算法
(1)双重DES就是对明文使用两个不同的密钥进行二次加密,目的是为了抵抗穷搜索攻击,期望密钥长度扩展为112bit。

(2)中间相遇攻击:


### 8、三重DES算法
(1)三重DES中三个密码组件既可以是一个加密函数,也可以是一个解密函数。

(2)双密钥三重DES算法:

(3)双密钥三重DES算法的安全性:
