第4章 对称加密实战(DES/3DES)
对称加密,说白了就是加密和解密用同一把钥匙。你想想看,就像你家的门锁,用钥匙锁上,再用同一把钥匙打开。DES 和 3DES 就是这类算法里的老前辈了。虽然现在 AES 是主流,但很多遗留系统、金融终端、智能卡里,DES/3DES 依然在跑。我去年还帮一个银行客户排查过 3DES 的兼容性问题,嗯,这玩意儿真没那么容易退休。
DES 算法原理:一个 56 位的“铁锁”
DES 的全称是 Data Encryption Standard,数据加密标准。它用 56 位的密钥,对 64 位的数据块进行加密。为什么是 56 位?因为早期设计时,密钥里每 8 位有一位是奇偶校验位,实际有效位就是 56 位。
它的核心流程,我习惯把它拆成三步:
- 初始置换(IP):把 64 位明文按一张固定表重新排列顺序。说白了就是打乱一下。
- 16 轮迭代:这是最核心的部分。每一轮都用子密钥对数据进行混淆和扩散。具体包括扩展置换、与子密钥异或、S 盒替换、P 盒置换。
- 逆初始置换(IP⁻¹):把 16 轮后的结果再打乱一次,得到密文。
这里有个关键点:S 盒。DES 有 8 个 S 盒,每个 S 盒是一个 4×16 的查找表。输入 6 位,输出 4 位。这是 DES 唯一的非线性部分,也是整个算法的安全基石。我在项目中遇到过有人自己改 S 盒想“增强”安全性,结果反而把算法搞废了——千万别动 S 盒,那是经过严格数学证明的。
核心要点:DES 的强度完全依赖于 56 位密钥和 S 盒的非线性。56 位密钥空间是 2⁵⁶,约 7.2×10¹⁶ 种可能。在 1998 年,电子前沿基金会用 25 万美元的专用机器,56 小时内就暴力破解了一个 DES 密钥。所以,现在单 DES 已经不安全了。
3DES 的兼容性:用三次加密弥补短板
3DES 不是新算法,而是把 DES 跑了三次。它用两个或三个密钥,执行“加密-解密-加密”的流程。为什么中间是解密?这是为了兼容性——如果三个密钥相同,3DES 就退化成单 DES。我当年做系统迁移时,就靠这个特性让新旧系统无缝对接了一段时间。
3DES 有三种模式:
| 模式 | 密钥长度 | 安全强度 | 典型场景 |
|---|---|---|---|
| DES-EEE3 | 3 个独立密钥(168 位) | 高 | 金融交易 |
| DES-EDE3 | 3 个独立密钥(168 位) | 高 | EMV 芯片卡 |
| DES-EDE2 | 2 个独立密钥(112 位) | 中 | 遗留系统兼容 |
你可能会问:为什么不用 3 个密钥直接加密三次?因为 EDE 模式(加密-解密-加密)有个好处:如果三个密钥相同,加密和解密就抵消了,结果等于单 DES。这在系统升级时特别有用——先让所有设备用同一个密钥跑单 DES,再逐步切换到 3DES。
我的经验:做 3DES 兼容性测试时,一定要验证“单密钥退化”这个特性。我曾经遇到一个设备,厂商说支持 3DES,结果三个密钥设成一样时,加密结果和单 DES 对不上——原来是他们自己魔改了算法。这种坑,踩一次就够了。
C 语言实现:从零写一个 DES 核心
我不会让你从零写完整 DES,那太折磨人了。但理解核心的 S 盒替换和 Feistel 结构,对后续安全编程很有帮助。下面是一个简化版的 DES 轮函数实现,重点展示 S 盒替换的逻辑:
#include <stdint.h>
#include <string.h>
// 8 个 S 盒(每个 4x16,这里只展示 S1 作为示例)
static const uint8_t sbox[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~S8,此处省略
};
// 6 位输入 -> 4 位输出
uint8_t sbox_lookup(int box_index, uint8_t input_6bit) {
// 行:由首尾两位决定(bit0 和 bit5)
uint8_t row = ((input_6bit & 0x20) >> 4) | (input_6bit & 0x01);
// 列:由中间 4 位决定(bit1~bit4)
uint8_t col = (input_6bit >> 1) & 0x0F;
return sbox[box_index][row][col];
}
// 简化版 Feistel 轮函数(仅演示 S 盒部分)
uint32_t feistel(uint32_t half_block, uint64_t subkey) {
// 实际 DES 中,这里要先做扩展置换(E 盒)
// 然后与子密钥异或,再经过 8 个 S 盒,最后 P 盒置换
// 这里仅做概念演示
uint8_t input = (uint8_t)(half_block & 0x3F); // 取低 6 位
uint8_t output = sbox_lookup(0, input); // 用 S1 盒
return (uint32_t)output;
}
int main() {
// 测试:输入 0x3F(二进制 111111),行=11(3),列=1111(15)
uint8_t result = sbox_lookup(0, 0x3F);
// 查表 S1[3][15] = 13(0x0D)
printf("S1(0x3F) = 0x%X\n", result);
return 0;
}
这段代码虽然简单,但抓住了 S 盒替换的精髓。实际 DES 的轮函数要复杂得多,包括 48 位的扩展置换、与 48 位子密钥的异或、8 个 S 盒并行、32 位 P 盒置换。但核心思想不变:混淆 + 扩散。
警告:千万不要在生产环境中使用自己写的 DES 实现。密码学实现有太多侧信道攻击的坑——时序攻击、缓存攻击、功耗分析。我见过一个团队自己实现了 DES,结果因为 S 盒查表时间不一致,被攻击者通过网络延迟就还原出了密钥。用 OpenSSL 或 mbedTLS 这类经过审计的库,才是正道。
知识体系总览
下面这张图,是我梳理的 DES/3DES 知识结构。你可以把它当作学习路线图:
这张图把 DES 原理、3DES 兼容性、C 语言实现要点串在了一起。你可以看到,DES 的核心是 16 轮 Feistel 结构和 8 个 S 盒;3DES 的核心是 EDE 模式和单密钥退化;C 语言实现则要重点关注 S 盒查表和密钥调度。
最后说一句:虽然 DES 和 3DES 正在被逐步淘汰,但理解它们对学习 AES、SM4 等现代对称加密非常有帮助。很多设计思想——比如 Feistel 结构、S 盒非线性替换、密钥扩展——都是一脉相承的。你把这些吃透了,后面学 AES 会轻松很多。
一句话总结:DES 是 56 位密钥的 Feistel 密码,3DES 是三次 DES 的兼容方案。C 语言实现时,S 盒查表是核心,但生产环境务必使用成熟库。
公众号:蓝海资料掘金营,微信deep3321