前言
DES是一种非常强大的加密逻辑,其代码复杂性与数据混淆性堪称一绝,尽管整体来说仍然是较为线性的算法,虽然出题出得少但是我们仍然要进行一定的了解,毕竟在VM的题或者一些密码题当中仍然有可能出现。
与TEA家族相同,DES也分为三种模式:ECB,CBC,CTR,其实现逻辑大同小异;与AES不同,DES的密钥不会分为128,192和256位,就是固定的8字节密钥。
三种模式一览
1.ECB(电子密码本模式):明文和密文必须符合某一长度,如果不足时就会自动填充。明文和密文在加解密过程中被分为数块,在加解密的执行流中每一块数据的加解密互不干扰并且可以并行运行,即使某一块发生错误也不会影响到其他的数据块,只会影响到自身的加解密。
2.CBC(密码分组链接模式): 明文和密文必须符合某一长度或其倍数,如果不足时就会自动填充。加密时程序会首先随机生成一个初始向量与第一组密文异或,后面的所有数据都会与前一个数据相异或,如果发生错误就会进而影响到整一个程序出错。
3.CTR(计数器模式):这种模式下引入了一个计数器,计数器的值在执行过程中是绝对绝对绝对不可能重复的,意味着每一组数据对应计数器中一个特定的值,这个值会与数据进行异或用来使数据更为混淆。但这种模式可以处理任意长度的数据,无需关注数据长度,也无需对数据进行填充。

DES的核心组件
总览
无论在哪一种模式下的DES它们的盒与表都是不会发生变化的(故曰核心组件),各种表与盒如下
1 | //定义IP表 |
IP表**(Initial Permutation-初始置换)**
1 | const int IP[64]= |
IP表的作用在于对输入明文进行扩散混淆:
将输入的第 1 位变为输出的第58位,输入的第 2 位变为输出的第50位…(诸如此类以此类推)
逆IP表(Final Permutation-最终置换)
(这里的命名大抵是翻译的问题)
1 | const int FP[64]= |
这里就是对输入明文进行二次扩散混淆,运行逻辑与IP表相同。
注意:在加密时输入的明文先在IP表中进行处理再在FP表中进行处理,那么在解密时密文就应该先在FP表中进行处理再在IP表中处理
E表
1 | const int E[48]= |
由于我们输入的数据是32位数据,这里E表的作用就是将输入的32位数据通过重复某几位上的数据转化为48位数据。
设计目的在于使某一位数据的变化能影响到多个S盒(后文会讲到),同时与48位的子密钥(后文会讲到)相匹配。
P盒
1 | const int P[32]= |
P盒的存在主要是为了处理从S盒出来后的数据,从S盒出来的数据的第 1 位是输出的第16位(诸如此类以此类推),这样的设计可以大大增强数据的扩展性和混淆性
PC-1表
1 | const int PC1[56]= |
首先,DES使用64位密钥,其中第8、16、24、32、40、48、56、64位是奇偶效验位,在进行处理时会被丢弃。P盒对密钥的处理方式与别的盒相同。最终会按照表中的结构输出56位的密钥。(与后面生成28位半密钥相关)
PC-2表
1 | const int PC2[48]= |
PC-2表主要是针对已经处理好的,从PC-1表中出来并且完成位移和拼接的密钥,将它们混淆为48位子密钥,并且由于左位移的存在导致每次从PC-2表中出来的子密钥都不相同,且丢弃的位:PC-1输出中的第9、18、22、25、35、38、43、54位。
左移位数表
1 | const int left_shift[16]= |
定义了一个左移位数的数组。
S盒
1 | const uint8_t S[8][4][16]= |
S盒的主要作用在于将一个 6 位数据转为 4 位数据。S[a][b][c]的意思是将第 a 个S盒中第 b 行第 c 列的数据当作最终数据输出。至于为什么是 4 位数据:因为S盒中最大的值为15,在二进制中的表示为1111,所以这样就能得到一个 4 位数据。(再具体a,b,c的求法后文会讲到)
DES的核心逻辑
在DES算法中有几个逻辑是固定不变的
置换函数
1 | uint64_t permute(uint64_t input,const int *table,int n) |
首先看轮函数的传参:传入input,table和n。input就是我们的输入,table就是表,一般来说就是各种表与盒,而 n 则是某一个表或盒的元素具体数量。
由于在数学定义中,算法是从 1 开始计算索引的,这里的table[i]-1就是计算出相应的C语言索引(毕竟C语言的索引是从 0 开始的)。后面的if是在检测第64-1-bit_pos位是否为1,如果是 1 就在该位置上放一个1,否则跳过。
循环位移函数
1 | uint32_t left_rotate28(uint32_t value,int shift) |
作用就在于处理密钥,对密钥进行位移。解密时反向位移即可。
子密钥生成函数
1 | void generate_subkeys(uint64_t key,uint64_t subkeys[16]) |
子密钥生成逻辑在DES中也是不变的核心函数逻辑,很清晰就不赘述了。
轮函数
1 | uint32_t f_function(uint32_t right,uint64_t subkey) |
轮函数的主要作用在于将 6 位数据通过S盒的替换,变成 4 位数据(上文S盒处有讲)。
接下来将分别展示ECB,CBC,CTR的完整代码,梳理其执行流并比较其不同。
ECB模式
1 |
|
大部分函数上文都已提及,所以这里我们把main函数和DES_encrypt函数单拎出来(毕竟主要执行流就在这两个函数中了)
1 | int main() |
可以看到main函数一开始就对明文的长度进行检索,并将其填充到 8 的倍数长度(因为DES除CTR模式以外都只能加密长度为 8 的倍数长度的明文)。将处于缓冲区中的数据和密钥传入DES_encrypt函数中。后面的u64_to_bytes是为了方便输出才有的。
1 | uint64_t DES_encrypt(uint8_t *plaintext,uint8_t *key) |
代码很清晰,就不多赘述了。但有两点需要注意
第一
1 | for(int i=0;i<16;i++) |
这个代码在每一种模式下的DES都是不变的。
第二
1 | for(int i = 15; i >= 0; i--) { |
解密时改代码逆向逻辑如上
CBC模式
1 |
|
可以看到,CBC模式下的代码与ECB模式下的代码几乎完全相同,唯二不同的地方就在于前一个数据与后一个数据相异或,且定义了一个初始化向量(详情请见前言)。但总体上与ECB模式相同。
CTR模式
1 |
|
与上面两种方式都不同,CTR模式需要定义一个计数器nonce。CTR模式下密钥替代明文进入DES加密逻辑中与密钥进行加密生成密钥流,而明文就只是在最后与密钥流进行异或生成最终密文。
DES加密模式详细对比表
| 对比维度 | ECB模式 (Electronic Codebook) | CBC模式 (Cipher Block Chaining) | CTR模式 (Counter) |
|---|---|---|---|
| 全称 | Electronic Codebook(电子密码本) | Cipher Block Chaining(密码块链接) | Counter(计数器) |
| 工作原理 | 每个明文块独立加密,互不影响 | 每个明文块先与前一个密文块异或,再加密 | 将分组密码转换为流密码,加密计数器生成密钥流 |
| 加密公式 | Cᵢ = Eₖ(Pᵢ) | C₀ = Eₖ(P₀ ⊕ IV) Cᵢ = Eₖ(Pᵢ ⊕ Cᵢ₋₁) | Cᵢ = Pᵢ ⊕ Eₖ(Counterᵢ) |
| 解密公式 | Pᵢ = Dₖ(Cᵢ) | P₀ = Dₖ(C₀) ⊕ IV Pᵢ = Dₖ(Cᵢ) ⊕ Cᵢ₋₁ | Pᵢ = Cᵢ ⊕ Eₖ(Counterᵢ) |
| 是否需要IV/Nonce | ❌ 不需要 | ✅ 需要8字节IV | ✅ 需要Nonce(通常8字节) |
| IV/Nonce要求 | 无 | 必须随机且不可预测 | 必须唯一(通常随机生成) |
| 是否需要填充 | ✅ 需要(PKCS#5/7) | ✅ 需要(PKCS#5/7) | ❌ 不需要(流密码特性) |
| 填充方式 | 不足8字节用填充值补齐 | 不足8字节用填充值补齐 | 无填充,最后块可截断 |
| 并行加密 | ✅ 可以(各块独立) | ❌ 不可以(依赖前块) | ✅ 可以(计数器可预计算) |
| 并行解密 | ✅ 可以(各块独立) | ❌ 不可以(依赖前块) | ✅ 可以(计数器可预计算) |
| 相同明文块结果 | 相同(安全性缺陷) | 不同(与位置相关) | 不同(计数器变化) |
| 错误传播 | 只影响当前块 | 影响当前及后续块 | 只影响当前位(流密码) |
| 自同步能力 | ❌ 无 | ✅ 有(可从错误恢复) | ❌ 无 |
| 实现复杂度 | 简单 | 中等 | 中等 |
| 安全性等级 | ⭐☆☆☆☆(最低) | ⭐⭐⭐☆☆(中等) | ⭐⭐⭐⭐☆(较高) |
| 加密速度 | 快 | 中等 | 快(可并行) |
| 内存需求 | 低 | 低 | 低 |
| 适用场景 | 随机数据加密 单个数据块加密 | 通用数据加密 文件、消息加密 | 实时数据流 随机访问加密 |
| 不适用场景 | 结构化数据 重复模式数据 | 实时流数据(错误传播) | 需要认证的场合 |
| 标准化 | ISO 10116 | ISO 10116 | NIST SP 800-38A |
| 常见应用 | 早期系统 简单协议 | SSL/TLS(早期) IPSec 文件加密 | WiFi WPA2 磁盘加密 实时通信 |
| 优缺点总结 | 优点:简单、并行 缺点:模式暴露、不安全 | 优点:隐藏模式、较安全 缺点:串行、错误传播 | 优点:并行、无需填充 缺点:需唯一nonce |
| 推荐程度 | ❌ 不推荐(已淘汰) | ⚠️ 谨慎使用(逐渐淘汰) | ✅ 推荐使用(现代应用) |
DES解密
DES的解密相比于AES来说更为简单,其表,盒,函数和执行流基本上都是不变的,唯一变的东西是密钥的输入方式,即加密时子密钥从索引第0个开始调度使用,解密时则刚好相反,从索引第15个开始调度使用。其他的流程全部与加密相同。