mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
2870 字
8 分钟
DES从原理到cpp实现

DES从入门到cpp实现#

一、定义#

DES(Data Encryption Standard)是 1977 年 NIST 标准化的对称分组密码:

属性值
分组大小64 bit(8 字节)
密钥大小64 bit(8 字节),其中实际有效 56 bit,另 8 bit 是校验位
结构Feistel 网络,16 轮迭代
输出64 bit 密文
现状已被攻破(密钥太短),新项目用 AES

二、原理#

先来看流程图

flowchart LR %% 样式定义 classDef default fill:#f9fafb,stroke:#6b7280,stroke-width:2px,color:#1f2937,rx:8,ry:8; classDef keyInput fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a,rx:10,ry:10; classDef keyProcess fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e; classDef subkey fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f; classDef plainInput fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d,rx:10,ry:10; classDef pad fill:#fef9c3,stroke:#ca8a04,stroke-width:2px,color:#713f12; classDef dataProcess fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#166534; classDef round fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87; classDef cipherOutput fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#4c1d95,rx:10,ry:10; subgraph KeySchedule["密钥扩展 Key Schedule"] direction LR K["64bit 密钥"]:::keyInput --> PC1["PC-1<br/>64 → 56"]:::keyProcess PC1 --> Split["拆 C0 / D0<br/>各 28bit"]:::keyProcess Split --> Shift["每轮循环左移<br/>按 SHIFT 表"]:::keyProcess Shift --> PC2["每轮 PC-2<br/>56 → 48"]:::keyProcess PC2 -->|"i = i + 1,直到达到16轮"| Shift PC2 --> SK["K1 ~ K16<br/>16 个 48bit 子密钥"]:::subkey end subgraph Encrypt["数据加密"] direction LR Raw["原始明文"]:::plainInput --> Pad["填充<br/>PKCS#7 等"]:::pad Pad --> P["64bit 明文分组"]:::plainInput P --> IP["IP 初始置换"]:::dataProcess IP --> LR["拆 L0 / R0<br/>各 32bit"]:::dataProcess LR --> Round["16 轮 Feistel 迭代"]:::round Round --> Swap["左右交换<br/>preoutput = R16 ‖ L16"]:::dataProcess Swap --> FP["FP 逆初始置换"]:::dataProcess FP --> C["64bit 密文"]:::cipherOutput end SK -.->|"K1..K16"| Round %% 子图样式 style KeySchedule fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e3a8a style Encrypt fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#166534 %% 连线样式 linkStyle default stroke:#6b7280,stroke-width:2px; linkStyle 4 stroke:#d97706,stroke-width:2px,stroke-dasharray:5 5; linkStyle 11 stroke:#d97706,stroke-width:2px,stroke-dasharray:5 5;

妈的好复杂,你肯定这样想吧,我也这么想。

我们来拆解看看,这是两条线,简化为密钥扩展和数据加密。

先看明文的前些部分,我们看到了填充与分组。DES算法 是以64位为一组分组加密的,所以当输入的明文长度,不满足64的整数倍时,需要涉及到数据的填充。而分组的意思就和之前哈希算法分块差不多,他将填充后的原始消息进行分组,每组64位,依次对这N组进行加密,最终产出N组密文。

算法特点#

参考DES加密算法原理及代码实现_des算法代码-CSDN博客

  • 使用56位的密钥(一般密钥说是64位,实际上有效位只有56位,其余8位是校验位)
  • 以64位位单位,对数据分组进行加密和解密(密文与明文长度相同,均为64位)。
  • DES加密与解密使用同一密钥(属于对称加密)
  • DES的保密性依赖于密钥。(DES的加密算法是公开的,密钥是保密的)

明文前置处理#

填充部分省略了,这个后面会说,我们直接看填充后的8字节。

flowchart LR A["明文字节 8 个"] --> B["大端加载<br/>byte → uint64_t"] B --> C["IP 初始置换<br/>64 → 64"] C --> D["拆 L0 / R0<br/>各 32bit"] D --> E["进入 16 轮 Feistel"]

1.DES使用大端序,所以需要大端加载明文

uint64_t data = 0;
for (int i = 0; i < 8; ++i) data = (data << 8) | block[i];

2.IP 置换:一个 64→64 的位重排。规则是”输入第 58 位移到输出第 1 位,输入第 50 位移到输出第 2 位……”。

1

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
  • 表是一个 8×8 的矩阵。
  • 读取顺序:从左到右,从上到下。
  • 数值含义:输出块第 i 位的数据,来自于输入块的第 IP[i] 位。

IP 表第一行第一个数字是 58。这表示:输入明文第 58 位(从左往右数)的数据,经过 IP 置换后,成为了输出块的第 1 位(最左边)。

换句话讲,分组输入进来的原文不是64bit吗?按照这个表查,索引1的位置上是58,就代表明文的第58位将来会填在输出的第一位。

初始置换就是将明文打乱重排序。并生成L0 , R0。

L0取明文前32字节,R0取明文后32字节。当然这里说的是经过IP打乱后的明文。

密钥前置处理#

好了,我们再来看看密钥这一侧的前置处理。

flowchart LR %% 样式定义 classDef default fill:#f9fafb,stroke:#6b7280,stroke-width:2px,color:#1f2937,rx:8,ry:8; classDef keyInput fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a,rx:10,ry:10; classDef load fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e; classDef permute fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d; classDef split fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f; classDef loop fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87; classDef subkey fill:#d1fae5,stroke:#059669,stroke-width:2px,color:#064e3b,rx:10,ry:10; A["密钥字节 8 个"]:::keyInput B["大端加载<br/>byte → uint64_t"]:::load C["PC-1 置换<br/>64 → 56<br/>(去 8 校验位)"]:::permute D["拆 C0 / D0<br/>各 28bit"]:::split E["16 轮循环左移 + PC-2"]:::loop F["K1 ~ K16<br/>各 48bit"]:::subkey A --> B B --> C C --> D D --> E E --> F %% 连线样式 linkStyle default stroke:#6b7280,stroke-width:2px;

DES加密的过程,明文M共经历了16次运算迭代,每次迭代的数据长度是48 ,因此我们需要16组48位的加密秘钥来进行加密。

首先密钥是不会填充的,他必须得输入8字节。

2

依旧是大端加载,不多说。

PC-1表置换#

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位变成了后面的56位。

DES 密钥的每个字节最低位是奇偶校验位,用于检测密钥传输错误。这 8 位不参与加密,PC-1 直接丢弃。

丢弃的位置是第 8、16、24、32、40、48、56、64 位。

CD瓜分#

接着C0和D0各自瓜分走28比特。

16轮循环左移+查表#

注意这里是对28比特长度的C和D进行循环左移,左移位数查看下面表格。

依次是C1,D1为C0,D0左移一位,C2,D2为C1,D1左移一位,C3,D3为C2,D2左移两位…

循环左移表
轮次: 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

循环左移中间过程,我们会得到16个Cn和16个Dn,他们会被根据PC-2进行合并压缩。

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

每轮把左移后的 C||D(56bit)合并,用 PC-2 压缩成 48bit 子密钥:PC-2表共48个数据,所以上面拼接出来的的值56会被压缩。

ok,这样我们就能拿到16个子密钥了,每个都是48bit。

为什么是 48bit? 因为 Feistel 里 E 扩展把 R(32bit)扩成 48bit,子密钥必须也是 48bit 才能异或。

ok,到这里我们的明文和密钥处理就完成了,现在正式进入加密过程。

Feistel 网络#

flowchart LR %% 样式定义 classDef default fill:#f9fafb,stroke:#6b7280,stroke-width:2px,color:#1f2937,rx:8,ry:8; classDef input fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a,rx:10,ry:10; classDef subkey fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f; classDef func fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87; classDef xor fill:#fce7f3,stroke:#db2777,stroke-width:2px,color:#831843; classDef output fill:#d1fae5,stroke:#059669,stroke-width:2px,color:#064e3b,rx:10,ry:10; L["L_{i-1}<br/>32bit"]:::input R["R_{i-1}<br/>32bit"]:::input K["K_i<br/>48bit"]:::subkey F["轮函数 F(R, K_i)<br/>32bit 输出"]:::func XOR2["异或 ⊕"]:::xor RN["R_i"]:::output LN["L_i = R_{i-1}"]:::output L --> XOR2 R --> F K --> F F --> XOR2 XOR2 --> RN R --> LN %% 连线样式 linkStyle default stroke:#6b7280,stroke-width:2px;
符号含义长度来源
K_i第 i 轮子密钥48 bit密钥扩展产生
R_i第 i 轮后的右半部分32 bitL_{i-1} ⊕ F(R_{i-1}, K_i)
L_i第 i 轮后的左半部分32 bit等于 R_{i-1}

第一轮的L_1,R_1就是之前的IP置换后瓜分到的C和D

公式
L_i = R_{i-1}
R_i = L_{i-1} ⊕ F(R_{i-1}, K_i)
关键性质:加密和解密结构完全相同,只需子密钥逆序。这是 Feistel 网络的对称美。

我们可以看到L_i继承了上一轮的R值,真正在变化的只有R_i.

这里的重点是轮函数F,我们来看看吧。

F函数#

F函数就是不断计算Li,Ri的过程,此过程包括四个部分:E盒置换、S盒置换、P盒置换、左右校验,我们简要的总结如下:

flowchart LR %% 样式定义 classDef default fill:#f9fafb,stroke:#6b7280,stroke-width:2px,color:#1f2937,rx:8,ry:8; classDef input fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a,rx:10,ry:10; classDef expand fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e; classDef subkey fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f; classDef xor fill:#fce7f3,stroke:#db2777,stroke-width:2px,color:#831843; classDef sbox fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d; classDef permute fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87; classDef output fill:#d1fae5,stroke:#059669,stroke-width:2px,color:#064e3b,rx:10,ry:10; R["输入 R<br/>32bit"]:::input E["1. E 扩展<br/>32 → 48"]:::expand K["子密钥 K_i<br/>48bit"]:::subkey XOR["2. 异或<br/>48bit"]:::xor S["3. S 盒替换<br/>48 → 32"]:::sbox P["4. P 置换<br/>32 → 32"]:::permute OUT["输出 F<br/>32bit"]:::output R --> E E --> XOR K --> XOR XOR --> S S --> P P --> OUT %% 连线样式 linkStyle default stroke:#6b7280,stroke-width:2px; linkStyle 2 stroke:#d97706,stroke-width:2px,stroke-dasharray:5 5;
E盒扩展置换#

4

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
  • 输入:32 位
  • 输出:48 位
  • 读取顺序:从左到右,从上到下

输出第 i 位,来自输入的第 E[i] 位。

例如:

  • 输出第 1 位 ← 输入第 32 位
  • 输出第 2 位 ← 输入第 1 位
  • 输出第 3 位 ← 输入第 2 位

我们可以看到E盒的中间四位是按顺序来的,两边多了两个无关的数字?无关吗?找个规律呢。

每一行的 6 位 = [上一组的最后一位] + [本组的 4 位] + [下一组的第一位]

为什么这么搞?

E 盒的目的不是“增加信息”,而是:

  1. 把 32 位扩展成 48 位,以便和 48 位子密钥异或。
  2. 让相邻的位产生重叠,使得 S 盒的输入中,每一位都受到多个输入位的影响。
  3. 增强扩散(Diffusion):一个输入位的改变,会影响多个 S 盒的输出。

如果只是简单地把 32 位重复一遍凑成 48 位,扩散效果会差很多。E 盒的“边界借位”让每个 S 盒的 6 位输入中,有 2 位来自相邻组,这样 S 盒的输出会互相影响。

子密钥异或#

扩展后的数据与对应轮数的密钥进行异或,没啥好说的。

S盒替换#

S 盒(Substitution Box)是 DES 里唯一非线性的部分,也是整个 DES 安全性的核心。

把刚刚异或的 48bit 分成 8 组 6bit,每组查一个 S 盒变成 4bit。

8 个 S 盒并行工作:8 × 6 = 48 位输入,8 × 4 = 32 位输出

3

每个s盒是一个 4 行 × 16 列 的表,里面存的是 0~15 的数(4 位)

这里以s1为例

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
  • 行号:0~3
  • 列号:0~15
  • 值:0~15

注意这里是从0开始了,与之前的不同。

查表规则:

  • 行号 = b1 b6(第 1 位和第 6 位拼成的 2 位数,范围 0~3)
  • 列号 = b2 b3 b4 b5(中间 4 位拼成的 4 位数,范围 0~15)
  • 查表得到 4 位数,就是输出

大白话:将6bit数据的左右两位拆下来合并做行号,中间剩的做列号

举个例子就是

输入 101100:

  • b1 b6 = 10 = 2 → 行号 2
  • b2 b3 b4 b5 = 0110 = 6 → 列号 6
  • 查 S1 第 2 行第 6 列 → 值是 2 → 二进制 0010

所以 S1(101100) = 0010。

完整8个s盒如下

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
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

32位输入,32位输出,和之前IP表,PC表一个查法

16轮F函数之后#

flowchart LR %% 样式定义 classDef default fill:#f9fafb,stroke:#6b7280,stroke-width:2px,color:#1f2937,rx:8,ry:8; classDef input fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a,rx:10,ry:10; classDef swap fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f; classDef permute fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87; classDef convert fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e; classDef output fill:#d1fae5,stroke:#059669,stroke-width:2px,color:#064e3b,rx:10,ry:10; A["16 轮结束<br/>L16 / R16"]:::input B["左右交换<br/>preoutput = R16 ‖ L16"]:::swap C["FP 逆初始置换"]:::permute D["大端拆回<br/>8 字节"]:::convert E["64bit 密文"]:::output A --> B B --> C C --> D D --> E %% 连线样式 linkStyle default stroke:#6b7280,stroke-width:2px;

16轮结束后,要进行左右交换,这点很重要。

妈的我就知道你忘了一开始的流程图

flowchart LR %% 样式定义 classDef default fill:#f9fafb,stroke:#6b7280,stroke-width:2px,color:#1f2937,rx:8,ry:8; classDef keyInput fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a,rx:10,ry:10; classDef keyProcess fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e; classDef subkey fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f; classDef plainInput fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d,rx:10,ry:10; classDef pad fill:#fef9c3,stroke:#ca8a04,stroke-width:2px,color:#713f12; classDef dataProcess fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#166534; classDef round fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87; classDef cipherOutput fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#4c1d95,rx:10,ry:10; subgraph KeySchedule["密钥扩展 Key Schedule"] direction LR K["64bit 密钥"]:::keyInput --> PC1["PC-1<br/>64 → 56"]:::keyProcess PC1 --> Split["拆 C0 / D0<br/>各 28bit"]:::keyProcess Split --> Shift["16 轮循环左移<br/>按 SHIFT 表"]:::keyProcess Shift --> PC2["PC-2<br/>56 → 48"]:::keyProcess PC2 --> SK["K1 ~ K16<br/>16 个 48bit 子密钥"]:::subkey end subgraph Encrypt["数据加密"] direction LR Raw["原始明文"]:::plainInput --> Pad["填充<br/>PKCS#7 等"]:::pad Pad --> P["64bit 明文分组"]:::plainInput P --> IP["IP 初始置换"]:::dataProcess IP --> LR["拆 L0 / R0<br/>各 32bit"]:::dataProcess LR --> Round["16 轮 Feistel 迭代"]:::round Round --> Swap["左右交换<br/>preoutput = R16 ‖ L16"]:::dataProcess Swap --> FP["FP 逆初始置换"]:::dataProcess FP --> C["64bit 密文"]:::cipherOutput end SK -.->|"K1..K16"| Round %% 子图样式 style KeySchedule fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e3a8a style Encrypt fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#166534 %% 连线样式 linkStyle default stroke:#6b7280,stroke-width:2px; linkStyle 11 stroke:#d97706,stroke-width:2px,stroke-dasharray:5 5;

将f函数的过程使用k1-k16,迭代16次,即经过16次的交换替代,得到L16 、R16,并将此作为输入进行逆置换得到最后的密文,逆置换的过程同初始置换,不再赘述。

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

三、ECB模式和CBC模式#

DES 只能处理 8 字节块,实际使用需要配合模式和填充。

7.1 PKCS#7 填充#

补到 8 的整数倍:

  • 差 N 字节(1 ≤ N ≤ 8)→ 补 N 个值为 N 的字节
  • 恰好 8 的倍数 → 补 8 个 0x08

例:明文 5 字节 → 补 3 个 0x03;明文 16 字节 → 补 8 个 0x08。

ECB模式#

现在已经不咋用了,不太安全。公式C_i = E(P_i)

特点:

  • 每个块独立加密,相同明文块产生相同密文块
  • ❌ 不安全:会泄露明文的模式(比如图片 ECB 加密后还能看出轮廓)
  • ✅ 优点:可并行、实现简单
flowchart LR P1[P1] --> E1["DES加密"] P2[P2] --> E2["DES加密"] P3[P3] --> E3["DES加密"] E1 --> C1[C1] E2 --> C2[C2] E3 --> C3[C3]

CBC模式#

flowchart LR %% 样式定义 classDef default fill:#f9fafb,stroke:#6b7280,stroke-width:2px,color:#1f2937,rx:8,ry:8; classDef plain fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a,rx:10,ry:10; classDef iv fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f,rx:10,ry:10; classDef xor fill:#fce7f3,stroke:#db2777,stroke-width:2px,color:#831843; classDef encrypt fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87; classDef cipher fill:#d1fae5,stroke:#059669,stroke-width:2px,color:#064e3b,rx:10,ry:10; P1["P1"]:::plain --> X1["⊕"]:::xor IV["IV"]:::iv --> X1 X1 --> E1["DES 加密"]:::encrypt E1 --> C1["C1"]:::cipher P2["P2"]:::plain --> X2["⊕"]:::xor C1 --> X2 X2 --> E2["DES 加密"]:::encrypt E2 --> C2["C2"]:::cipher P3["P3"]:::plain --> X3["⊕"]:::xor C2 --> X3 X3 --> E3["DES 加密"]:::encrypt E3 --> C3["C3"]:::cipher %% 连线样式 linkStyle default stroke:#6b7280,stroke-width:2px; linkStyle 4 stroke:#059669,stroke-width:2.5px; linkStyle 9 stroke:#059669,stroke-width:2.5px;

加密:C_i = E(P_i ⊕ C_{i-1}),其中 C_0 = IV 解密:P_i = D(C_i) ⊕ C_{i-1}

特点:

  • 相同明文块在不同位置产生不同密文
  • ✅ 安全(相比 ECB)
  • ❌ 不能并行加密(解密可以并行)
  • 需要 IV(初始向量),IV 每次加密必须随机

7.4 ECB vs CBC 对比#

维度ECBCBC
相同明文块相同密文不同密文
安全性❌ 差✅ 好
并行加密✅ 可❌ 不可
并行解密✅ 可✅ 可
需要 IV❌ 不需要✅ 需要
推荐场景不推荐传统主流

四、cpp实现#

常量表#

先来定义常量表备用

#include <iostream>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
#include <array>
#include <stdexcept>
#include <iomanip>
#include <sstream>
// ============================================================
// DES 常量表
// ============================================================
namespace tables {
// IP 初始置换
constexpr std::array<uint8_t, 64> 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
};
// FP 逆初始置换
constexpr std::array<uint8_t, 64> 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
};
// E 扩展
constexpr std::array<uint8_t, 48> 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
};
// P 置换
constexpr std::array<uint8_t, 32> 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
};
// PC-1 密钥置换
constexpr std::array<uint8_t, 56> PC1 = {
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 压缩置换
constexpr std::array<uint8_t, 48> PC2 = {
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
};
// 循环左移位数表
constexpr std::array<uint8_t, 16> SHIFT = {
1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
};
// S 盒
constexpr uint8_t SBOX[8][4][16] = {
{{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}}
};
} // namespace tables

PKCS7填充#

// ============================================================
// PKCS#7 填充
// ============================================================
namespace pkcs7 {
std::vector<uint8_t> pad(const std::vector<uint8_t>& data, size_t blockSize) {
size_t padLen = blockSize - (data.size() % blockSize);//计算出要填充的长度,用分组大小减去数据取模块大小
std::vector<uint8_t> out = data; //拷贝一份避免修改原数据。
out.insert(out.end(), padLen, static_cast<uint8_t>(padLen));//在out的末尾插入padLen个值为padLen的字节。
//static_cast<uint8_t>(padLen):把 padLen(size_t 类型)转成 uint8_t,因为 padLen 最大是 16,能放进一个字节。
return out;
}
std::vector<uint8_t> unpad(const std::vector<uint8_t>& data) {
if (data.empty()) throw std::runtime_error("empty data");
uint8_t padLen = data.back(); //取最后一个字节,因为填充就是按照长度的数值填充的。
if (padLen == 0 || padLen > 8 || padLen > data.size()) {
throw std::runtime_error("invalid PKCS#7 padding");
}
for (size_t i = data.size() - padLen; i < data.size(); ++i) {
if (data[i] != padLen) {
throw std::runtime_error("invalid PKCS#7 padding");
}
}
return std::vector<uint8_t>(data.begin(), data.end() - padLen);//直接返回一个新的数组,减去了填充的长度即为原始数据长度。
}
} // namespace pkcs7

DES实现类#

前方高能,一大段DES定义即将来袭。

// ============================================================
// DES 核心类
// ============================================================
class DES {
public:
static constexpr size_t BLOCK_SIZE = 8;
static constexpr size_t KEY_SIZE = 8;
//explicit 关键字,禁止隐式类型转换
explicit DES(const std::vector<uint8_t>& key) {
if (key.size() != KEY_SIZE) {
throw std::invalid_argument("DES key must be 8 bytes");
}
generateSubkeys(key.data()); //这个函数是私有函数,在下面,可能把他放到上面会更好阅读点,这里我的。。。
}
void encryptBlock(uint8_t block[BLOCK_SIZE]) const {
processBlock(block, true);
}
void decryptBlock(uint8_t block[BLOCK_SIZE]) const {
processBlock(block, false);
}
private:
uint64_t subkeys_[16]; //
// --------------------------------------------------------
// 通用位痣,置换函数(注意断句)
// table : 置换表,1-based,第 1 位是 MSB
// n : 输出位数
// in : 输入值(右对齐,低 inBits 位有效)
// inBits : 输入的有效位宽
// --------------------------------------------------------
static uint64_t permute(const uint8_t* table, int n,
uint64_t in, int inBits) {
//该函数的作用就是查找对应的置换表然后将输入数据指定位置的数置换成表里面的数
uint64_t out = 0;
for (int i = 0; i < n; ++i) {
int pos = table[i];
uint64_t bit = (in >> (inBits - pos)) & 1ULL; //获取输入数据pos位的比特数
/*
注意1ULL 就是一个无符号长长整型(unsigned long long)的常量 1。
举个例子
in = 0b1101
pos = 2, inBits = 4
inBits - pos = 2
in >> 2 = 0b11
0b11 & 1 = 1
*/
out = (out << 1) | bit; //把刚取出的 1 个 bit 拼到输出末尾。
}
return out;
}
// 28bit 循环左移
static uint32_t rotl28(uint32_t x, int n) {
return ((x << n) | (x >> (28 - n))) & 0x0FFFFFFFU;
}
// 密钥扩展
void generateSubkeys(const uint8_t key[KEY_SIZE]) {
using namespace tables;
// 1. 大端加载 64bit 密钥
uint64_t key64 = 0;
for (int i = 0; i < 8; ++i) {
key64 = (key64 << 8) | key[i];
}
// 2. PC-1: 64 → 56(输入 64bit)
// PC1.data()返回PC1数组第一个元素
uint64_t pc1 = permute(PC1.data(), 56, key64, 64);
// 3. 拆 C / D(各 28bit)
uint32_t C = static_cast<uint32_t>((pc1 >> 28) & 0x0FFFFFFFU);
uint32_t D = static_cast<uint32_t>(pc1 & 0x0FFFFFFFU);
// 4. 16 轮:左移 + PC-2 压缩
for (int i = 0; i < 16; ++i) {
C = rotl28(C, SHIFT[i]); //循环左移位数也是要查表的记得吗?懂我意思?👌
D = rotl28(D, SHIFT[i]);
uint64_t CD = (static_cast<uint64_t>(C) << 28) | D; // 56bit
// PC-2 的输入是 56bit,不是 64bit,输出是48bit
subkeys_[i] = permute(PC2.data(), 48, CD, 56)
& 0xFFFFFFFFFFFFULL;
}
}
// 轮函数 F
static uint32_t feistel(uint32_t R, uint64_t subkey) {
using namespace tables;
// E 扩展:输入 32bit
uint64_t expanded = permute(E.data(), 48, R, 32);
// 异或子密钥
expanded ^= subkey;
// S 盒:48 → 32,最关键一步
uint32_t sOut = 0;
for (int i = 0; i < 8; ++i) {
int shift = (7 - i) * 6; //记住咱们s盒是从索引0开始的,每个盒子接受6个bit
uint8_t six = static_cast<uint8_t>((expanded >> shift) & 0x3F);
int row = ((six & 0x20) >> 4) | (six & 0x01); //行号就是首末两bit
int col = (six >> 1) & 0x0F;
uint8_t val = SBOX[i][row][col];
sOut = (sOut << 4) | val;
}
// P 置换:输入 32bit
return static_cast<uint32_t>(permute(P.data(), 32, sOut, 32));
}
// 单块处理
void processBlock(uint8_t block[BLOCK_SIZE], bool encrypt) const {
using namespace tables;
// 1. 大端加载
uint64_t data = 0;
for (int i = 0; i < 8; ++i) {
data = (data << 8) | block[i];
}
// 2. IP 置换:输入 64bit
uint64_t ip = permute(IP.data(), 64, data, 64);
// 3. 拆 L / R
uint32_t L = static_cast<uint32_t>(ip >> 32);
uint32_t R = static_cast<uint32_t>(ip & 0xFFFFFFFFULL);
// 4. 16 轮 Feistel
for (int i = 0; i < 16; ++i) {
int ki = encrypt ? i : (15 - i);
uint32_t F = feistel(R, subkeys_[ki]);
uint32_t newR = L ^ F;
L = R;
R = newR;
}
// 5. 左右交换
uint64_t preoutput = (static_cast<uint64_t>(R) << 32) | L;
// 6. FP 置换:输入 64bit
uint64_t out = permute(FP.data(), 64, preoutput, 64);
// 7. 大端拆回字节
for (int i = 0; i < 8; ++i) {
block[i] = static_cast<uint8_t>((out >> (56 - i * 8)) & 0xFF);
}
}
};

ECB模式#

我们先来看看ECB模式

// ============================================================
// ECB 模式
// ============================================================
class DESECB {
public:
explicit DESECB(const std::vector<uint8_t>& key) : des_(key) {}
std::vector<uint8_t> encrypt(const std::vector<uint8_t>& plaintext) const {
std::vector<uint8_t> data = pkcs7::pad(plaintext, DES::BLOCK_SIZE);//先对数据进行填充
std::vector<uint8_t> out(data.size()); //声明输出
for (size_t i = 0; i < data.size(); i += DES::BLOCK_SIZE) {
uint8_t block[DES::BLOCK_SIZE];
std::memcpy(block, data.data() + i, DES::BLOCK_SIZE);
des_.encryptBlock(block); //ECB就这样,对每个块分别加密
std::memcpy(out.data() + i, block, DES::BLOCK_SIZE);
}
return out;
}
std::vector<uint8_t> decrypt(const std::vector<uint8_t>& ciphertext) const {
if (ciphertext.size() % DES::BLOCK_SIZE != 0) {
throw std::invalid_argument("ciphertext size must be multiple of 8");
}
std::vector<uint8_t> out(ciphertext.size());
for (size_t i = 0; i < ciphertext.size(); i += DES::BLOCK_SIZE) {
uint8_t block[DES::BLOCK_SIZE];
std::memcpy(block, ciphertext.data() + i, DES::BLOCK_SIZE);
des_.decryptBlock(block);
std::memcpy(out.data() + i, block, DES::BLOCK_SIZE);
}
return pkcs7::unpad(out);
}
private:
DES des_;
};

CBC模式#

// ============================================================
// CBC 模式
// ============================================================
class DESCBC {
public:
DESCBC(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv)
: des_(key)
{
if (iv.size() != DES::BLOCK_SIZE) {
throw std::invalid_argument("IV must be 8 bytes");
}
std::memcpy(iv_, iv.data(), DES::BLOCK_SIZE);
}
std::vector<uint8_t> encrypt(const std::vector<uint8_t>& plaintext) const {
std::vector<uint8_t> data = pkcs7::pad(plaintext, DES::BLOCK_SIZE);
std::vector<uint8_t> out(data.size());
uint8_t prev[DES::BLOCK_SIZE];
std::memcpy(prev, iv_, DES::BLOCK_SIZE); //记录向量
for (size_t i = 0; i < data.size(); i += DES::BLOCK_SIZE) {
uint8_t block[DES::BLOCK_SIZE];
for (size_t j = 0; j < DES::BLOCK_SIZE; ++j) {
block[j] = data[i + j] ^ prev[j];
}
des_.encryptBlock(block);
std::memcpy(out.data() + i, block, DES::BLOCK_SIZE);
std::memcpy(prev, block, DES::BLOCK_SIZE); //更新向量用作下一轮的输入向量
}
return out;
}
std::vector<uint8_t> decrypt(const std::vector<uint8_t>& ciphertext) const {
if (ciphertext.size() % DES::BLOCK_SIZE != 0) {
throw std::invalid_argument("ciphertext size must be multiple of 8");
}
std::vector<uint8_t> out(ciphertext.size());
uint8_t prev[DES::BLOCK_SIZE];
std::memcpy(prev, iv_, DES::BLOCK_SIZE);
for (size_t i = 0; i < ciphertext.size(); i += DES::BLOCK_SIZE) {
uint8_t block[DES::BLOCK_SIZE];
std::memcpy(block, ciphertext.data() + i, DES::BLOCK_SIZE);
uint8_t cipherBlock[DES::BLOCK_SIZE];
std::memcpy(cipherBlock, block, DES::BLOCK_SIZE);
des_.decryptBlock(block);
for (size_t j = 0; j < DES::BLOCK_SIZE; ++j) {
out[i + j] = block[j] ^ prev[j]; //异或反运算就是异或
}
std::memcpy(prev, cipherBlock, DES::BLOCK_SIZE);
}
return pkcs7::unpad(out);
}
private:
DES des_;
uint8_t iv_[DES::BLOCK_SIZE];
};

惊为天人啊!根本看不懂!

五、3DES#

最后说说这家伙吧。

有两种常见模式,第二种比较常用。

  • EEE 模式:C = E_K3(E_K2(E_K1(P))),三次加密,纯扩展密钥空间。
  • EDE 模式:C = E_K3(D_K2(E_K1(P))),加密-解密-加密。

我们主要说EDE模式,这个模式也有分类,具体就是密钥个数。我们可以用三个,两个,一个。用一个和普通DES没区别,用两个如果前两次加密用同一个密钥的话还是和普通加密没区别,所以用三个才是优质之选。

flowchart LR P["明文 P<br/>64bit"] --> E1["DES 加密<br/>E_K1"] K1["K1<br/>8字节"] --> E1 E1 --> D1["DES 解密<br/>D_K2"] K2["K2<br/>8字节"] --> D1 D1 --> E2["DES 加密<br/>E_K3"] K3["K3<br/>8字节"] --> E2 E2 --> C["密文 C<br/>64bit"]

流程就是加密——解密——加密,当然也可以解密——加密——解密

flowchart TD %% 样式定义 classDef default fill:#f9fafb,stroke:#6b7280,stroke-width:2px,color:#1f2937,rx:8,ry:8; classDef keyInput fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a,rx:10,ry:10; classDef keySchedule fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e; classDef plainInput fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d,rx:10,ry:10; classDef pad fill:#fef9c3,stroke:#ca8a04,stroke-width:2px,color:#713f12; classDef dataProcess fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#166534; classDef iv fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f,rx:10,ry:10; classDef cbcXor fill:#fce7f3,stroke:#db2777,stroke-width:2px,color:#831843; classDef ede fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87; classDef cipherOutput fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#4c1d95,rx:10,ry:10; subgraph KeySchedule["三个独立的 DES 密钥扩展"] direction TB K1["K1<br/>8 字节"]:::keyInput --> KS1["DES_set_key(K1)"]:::keySchedule K2["K2<br/>8 字节"]:::keyInput --> KS2["DES_set_key(K2)"]:::keySchedule K3["K3<br/>8 字节"]:::keyInput --> KS3["DES_set_key(K3)"]:::keySchedule end subgraph Encrypt["3DES-EDE-CBC 加密(每组循环)"] direction TB P["明文"]:::plainInput --> Pad["PKCS#7 填充"]:::pad Pad --> Split["分成 8 字节块<br/>P1, P2, ..., Pn"]:::dataProcess Split --> Init["C0 = IV"]:::iv Init --> Xor["X_i = P_i ⊕ C_{i-1}<br/>(CBC 异或)"]:::cbcXor Xor --> EDE["EDE:<br/>E_K1 → D_K2 → E_K3"]:::ede EDE --> C["C_i<br/>(输出密文分组)"]:::cipherOutput C -->|"作为下一组的 C_{i-1}"| Xor C --> AllC["拼接所有 C_i"]:::cipherOutput AllC --> Final["最终密文"]:::cipherOutput end KS1 -.-> EDE KS2 -.-> EDE KS3 -.-> EDE %% 子图样式 style KeySchedule fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e3a8a style Encrypt fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#166534 %% 连线样式 linkStyle default stroke:#6b7280,stroke-width:2px;

说直白点就是将原来CBC链式异或过程中的DES加密给改成了这三傻货。我给你瞅瞅原图,你就知道了

下面这个是原来的CBC

flowchart LR %% 样式定义 classDef default fill:#f9fafb,stroke:#6b7280,stroke-width:2px,color:#1f2937,rx:8,ry:8; classDef plain fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a,rx:10,ry:10; classDef iv fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f,rx:10,ry:10; classDef xor fill:#fce7f3,stroke:#db2777,stroke-width:2px,color:#831843; classDef encrypt fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87; classDef cipher fill:#d1fae5,stroke:#059669,stroke-width:2px,color:#064e3b,rx:10,ry:10; P1["P1"]:::plain --> X1["⊕"]:::xor IV["IV"]:::iv --> X1 X1 --> E1["DES 加密"]:::encrypt E1 --> C1["C1"]:::cipher P2["P2"]:::plain --> X2["⊕"]:::xor C1 --> X2 X2 --> E2["DES 加密"]:::encrypt E2 --> C2["C2"]:::cipher P3["P3"]:::plain --> X3["⊕"]:::xor C2 --> X3 X3 --> E3["DES 加密"]:::encrypt E3 --> C3["C3"]:::cipher %% 连线样式 linkStyle default stroke:#6b7280,stroke-width:2px; linkStyle 4 stroke:#059669,stroke-width:2.5px; linkStyle 9 stroke:#059669,stroke-width:2.5px;

这个是3DES-CBC

flowchart LR %% 样式定义 classDef default fill:#f9fafb,stroke:#6b7280,stroke-width:2px,color:#1f2937,rx:8,ry:8; classDef plain fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a,rx:10,ry:10; classDef iv fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f,rx:10,ry:10; classDef xor fill:#fce7f3,stroke:#db2777,stroke-width:2px,color:#831843; classDef encrypt fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87; classDef cipher fill:#d1fae5,stroke:#059669,stroke-width:2px,color:#064e3b,rx:10,ry:10; P1["P1"]:::plain --> X1["⊕"]:::xor IV["IV"]:::iv --> X1 X1 --> E1["三傻货DES 加密"]:::encrypt E1 --> C1["C1"]:::cipher P2["P2"]:::plain --> X2["⊕"]:::xor C1 --> X2 X2 --> E2["三傻货DES 加密"]:::encrypt E2 --> C2["C2"]:::cipher P3["P3"]:::plain --> X3["⊕"]:::xor C2 --> X3 X3 --> E3["三傻货DES 加密"]:::encrypt E3 --> C3["C3"]:::cipher %% 连线样式 linkStyle default stroke:#6b7280,stroke-width:2px; linkStyle 4 stroke:#059669,stroke-width:2.5px; linkStyle 9 stroke:#059669,stroke-width:2.5px;

当然,不可能只有三个分组,这个视具体情况而定。

六、结语#

好了,今天就到这里吧,一个人走到这里也挺累的,目前还休息不了,什么时候是个头呢。。。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

DES从原理到cpp实现
https://fatdog.20060113.xyz/posts/des/
作者
神秘大胖狗
发布于
2026-09-17
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00