本文从安卓逆向工程师的实战角度,全面剖析 DES(Data Encryption Standard,数据加密标准)算法的原理、实现、识别与还原方法。涵盖 Feistel 网络、S 盒代换、密钥调度等核心机制,以及 Frida Hook、IDA 静态分析等实战技术。
目录
1. DES 算法概述
1.1 历史背景
DES 于 1977 年由美国国家标准局(NBS,即现在的 NIST - National Institute of Standards and Technology)正式发布为联邦信息处理标准(FIPS PUB 46)。算法最初由 IBM 设计,基于密码学家 Horst Feistel 提出的 Lucifer 密码系统,后经 NSA(National Security Agency,美国国家安全局)修改后标准化。
DES 长期作为对称加密的事实标准,广泛应用于金融、通信、政务等领域,直至 2001 年被 AES(Advanced Encryption Standard,高级加密标准)正式取代。值得注意的是,1998 年 EFF(Electronic Frontier Foundation,电子前哨基金会)使用名为 "Deep Crack" 的专用硬件在 56 小时内暴力破解了 DES,标志着 DES 56 位密钥长度在现代计算能力下已不再安全。
1.2 核心参数
| | |
|---|
| | |
| 64 bit(有效 56 bit + 8 bit 奇偶校验) | |
| | |
| | |
| | |
1.3 为什么逆向中仍然常见 DES?
尽管 DES 已不被推荐用于高安全场景,但在安卓逆向中仍然频繁出现,原因包括:
- 遗留系统兼容:大量银行、支付、政务系统仍使用 DES/3DES 接口,新 App 为了对接旧后端不得不沿用。比如某些银行的网银系统从 2000 年代沿用至今的加密通信协议
- 开发者安全意识不足:很多开发者直接复制网上的 DES 加密示例代码(如 CSDN、Stack Overflow 上的老帖子),未意识到 DES 已过时
- 性能考虑:在老旧设备或嵌入式场景下 DES 计算开销小,比 AES 更轻量。部分 IoT 设备因硬件限制仍在使用 DES
- 混淆手段:部分应用使用魔改版 DES 作为轻量级混淆,开发者认为修改 S 盒等参数后"别人就破解不了"——这种安全幻觉在实际逆向中经常被打破
补充说明:从安全性角度看,现代推荐使用 AES-128/256 或 ChaCha20-Poly1305 替代 DES。在国密合规场景下,应使用 SM4 算法。但作为逆向工程师,DES 仍是必须掌握的基础算法之一。
1.4 DES 的安全性时间线
| | |
|---|
| | |
| | |
| | |
| EFF "Deep Crack" 56 小时暴力破解 DES | |
| distributed.net + Deep Crack 22 小时破解 | |
| | |
| NIST 撤销 DES 标准(FIPS 46-3) | |
| | |
关键数字:DES 56 bit 密钥的搜索空间为 2^56 ≈ 7.2×10^16。以现代 GPU(如 NVIDIA A100)的算力估算,暴力破解 DES 仅需数小时到数天。这就是为什么 DES 在现代环境下已经完全不安全。
2. 算法核心原理
2.1 Feistel 网络结构
DES 采用经典的 Feistel 结构(以发明者 Horst Feistel 命名),其核心思想是:将明文分为左右两半,通过多轮迭代使两半交叉混合。Feistel 结构是对称密码学中最重要的设计范式之一,后来的 Blowfish、Twofish、Camellia 等算法也采用了类似结构。
Feistel 网络结构每一轮的核心公式:
L(i) = R(i-1)R(i) = L(i-1) ⊕ F(R(i-1), K(i))
其中:
L(i) 和 R(i) 分别表示第 i 轮迭代后的左半部分和右半部分F 是轮函数(Round Function),接收 32 bit 数据和 48 bit 子密钥K(i) 是第 i 轮使用的 48 bit 子密钥
这个结构的精妙之处在于:加密和解密使用完全相同的结构,唯一区别是子密钥的使用顺序相反。加密时子密钥顺序为 K1, K2, ..., K16;解密时为 K16, K15, ..., K1。这也是逆向中判断加解密方向的关键线索——如果在代码中看到子密钥数组被逆序访问,那很可能是解密操作。
逆向实战提示:在反编译代码中,如果看到类似 for (i = 15; i >= 0; i--) 的循环遍历子密钥数组,可以确定这是解密操作。
Feistel 结构的数学证明(为什么解密有效):
设加密时第 i 轮的操作为:
L(i) = R(i-1)R(i) = L(i-1) ⊕ F(R(i-1), K(i))
解密时,已知 L(i) 和 R(i),要恢复 L(i-1) 和 R(i-1):
R(i-1) = L(i) (直接从加密公式第一行得出)L(i-1) = R(i) ⊕ F(R(i-1), K(i)) (对加密公式第二行两边同时异或 F) = R(i) ⊕ F(L(i), K(i)) (代入 R(i-1) = L(i))
由此可见,无论 F 函数多么复杂(甚至可以是不可逆的),Feistel 结构都保证了可逆性。这就是 Feistel 结构被广泛采用的根本原因。
2.2 初始置换 IP(Initial Permutation)
初始置换将 64 bit 输入按照固定的置换表重新排列。这一步不增加安全性,设计初衷是为了适配 1970 年代 IBM 硬件的总线宽度,方便硬件实现中的位交织操作。在软件实现中可以完全省略 IP 置换而不影响算法的安全强度,但为了保持标准兼容性,所有实现都保留了这一步。
IP 置换表(将第 58 位放到第 1 位,第 50 位放到第 2 位...):58 50 42 34 26 18 10 260 52 44 36 28 20 12 462 54 46 38 30 22 14 664 56 48 40 32 24 16 857 49 41 33 25 17 9 159 51 43 35 27 19 11 361 53 45 37 29 21 13 563 55 47 39 31 23 15 7
逆向识别要点:在反汇编代码中,如果看到以 58, 50, 42, 34... 开头的数组常量,几乎可以确定是 DES 的 IP 表。这个数组通常定义在 .rodata(只读数据段)或 .data 段中,且长度恰好为 64 字节。
理解 IP 置换的规律:观察 IP 表会发现,偶数位(2, 4, 6, ..., 64)被排列到前 32 位(成为 L0),奇数位(1, 3, 5, ..., 63)被排列到后 32 位(成为 R0)。这实际上是一种位交织(bit interleaving)操作。
2.3 F 函数(轮函数)详解
F 函数是 DES 的核心,也是算法安全性的关键所在。它将 32 bit 的 R(i-1) 和 48 bit 的子密钥 K(i) 作为输入,产生 32 bit 输出。F 函数包含四个步骤:E 扩展置换、与子密钥异或、S 盒代换、P 置换。
F 函数详细结构2.3.1 E 扩展置换(Expansion Permutation)
将 32 bit 扩展为 48 bit,方法是将某些位重复使用。扩展的目的是让输入数据能够与 48 bit 子密钥进行异或运算,同时引入扩散效果——每个输入位会影响到多个 S 盒的输入。
E 扩展置换表:32 1 2 3 4 5 4 5 6 7 8 9 8 9 10 11 12 1312 13 14 15 16 1716 17 18 19 20 2120 21 22 23 24 2524 25 26 27 28 2928 29 30 31 32 1
注意每行的首尾元素分别来自相邻组的边界位。例如第二行的 4 和 5 分别是第一组的最后一位和第二组的第一位,这意味着第一组数据的变化会"扩散"到第二组的 S 盒输入中。这种跨组边界的位共享是 DES 扩散特性的关键设计。
逆向识别要点:E 扩展表以 32, 1, 2, 3, 4, 5 开头,这个特征序列在标准 DES 实现中是固定的。
2.3.2 S 盒代换(S-box Substitution)——核心非线性变换
S 盒是 DES 中唯一的非线性操作,也是安全性的关键所在。如果没有 S 盒,DES 将退化为一个仿射变换(Affine Transformation),可以通过简单的线性代数运算破解。S 盒引入的非线性特性使得差分密码分析和线性密码分析等攻击手段的难度大大增加。
共 8 个 S 盒,每个接收 6 bit 输入,产生 4 bit 输出(压缩了 2 bit)。
S 盒查表方式:
输入 6 bit = b₁ b₂ b₃ b₄ b₅ b₆行号 = b₁b₆ (首尾两位拼接,范围 0~3,即 2 bit)列号 = b₂b₃b₄b₅ (中间四位,范围 0~15,即 4 bit)查 S 盒表得到 4 bit 输出
理解行号的构成:为什么用首尾两位而非前两位作为行号?这是 DES 设计者有意为之的巧妙设计。首尾位来自 E 扩展中相邻组的"共享位",这样的编排使得一个 S 盒的输出不仅依赖于本组的 4 位核心数据,还受到相邻组边界数据的影响,进一步增强了扩散效果。
S₁ 盒完整数据(4 行 x 16 列):
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 [ 14 4 13 1 2 15 11 8 3 10 6 12 5 9 0 7 ] 1 [ 0 15 7 4 14 2 13 1 10 6 12 11 9 5 3 8 ] 2 [ 4 1 14 8 13 6 2 11 15 12 9 7 3 10 5 0 ] 3 [ 15 12 8 2 4 9 1 7 5 11 3 14 10 0 6 13 ]
S 盒的密码学性质:
NSA 在修改 IBM 原始 Lucifer 算法时,特别调整了 S 盒的设计,使其满足以下性质:
- 每行都是 0-15 的一个置换:确保输出的均匀分布
- 非线性度要求:任何输入位的改变至少影响 2 个输出位
- 差分分析抵抗:精心选择的 S 盒值使得差分密码分析(1990 年由 Biham 和 Shamir 提出)的攻击复杂度最大化
- SAC(严格雪崩准则):输入任意 1 bit 翻转,输出每 bit 翻转概率接近 50%
逆向识别要点:S 盒数组是 DES 最具辨识度的特征。在逆向中,只要在 .rodata 段或常量池中发现 S₁ 盒的前几个数字 14, 4, 13, 1, 2, 15, 11, 8...,基本可以确认是标准 DES。如果发现结构像 DES S 盒(4x16 矩阵,值范围 0-15)但数据不同,则为魔改 DES。
2.3.3 P 置换(Permutation)
S 盒输出的 32 bit 经过 P 置换重新排列,增加扩散性。P 置换的作用是确保每个 S 盒的输出在下一轮中被分配到不同的 S 盒输入,避免同一个 S 盒在连续两轮中处理相同的数据位。
P 置换表:16 7 20 21 29 12 28 17 1 15 23 26 5 18 31 10 2 8 24 14 32 27 3 919 13 30 6 22 11 4 25
扩散效果分析:以 S₁ 盒为例,其 4 bit 输出分别被分配到 P 表的第 1、2、3、4 位置,即 P 表中的值 16、7、20、21。这意味着 S₁ 的输出将分散到下一轮 E 扩展后的不同 S 盒输入中,实现了"雪崩效应"——明文 1 bit 的改变最终会影响密文中多个 bit。
3. DES 的完整加密流程
下面用一个完整的流程图展示从明文到密文的全过程,包括数据路径(左)和密钥调度路径(右)的协同工作:
DES 完整加密流程流程概述:
- 密钥预处理:64 bit 原始密钥经 PC-1 置换去除校验位得到 56 bit 有效密钥,分为 C0(28 bit)和 D0(28 bit)
- 子密钥生成:C 和 D 各自循环左移后合并,经 PC-2 置换生成 48 bit 子密钥 Ki,重复 16 轮得到 K1-K16
- 明文预处理:64 bit 明文经 IP 初始置换后分为 L0(32 bit)和 R0(32 bit)
- 16 轮迭代:每轮使用对应子密钥 Ki,执行 Feistel 运算
- 末尾处理:最后一轮不执行左右交换,直接将 R16 和 L16 拼接(注意是 R16 在前)
- 逆初始置换:经 IP⁻¹ 置换得到 64 bit 密文
加密 vs 解密:解密过程与加密完全相同,唯一区别是子密钥使用顺序相反。加密使用 K1→K16,解密使用 K16→K1。这是 Feistel 结构的天然优势——加解密共用同一套硬件/软件实现。
4. 密钥调度算法
密钥调度(Key Schedule)负责从 64 bit 原始密钥生成 16 个 48 bit 子密钥。
密钥调度算法4.1 PC-1 置换(选择置换 1,Permuted Choice 1)
从 64 bit 密钥中选出 56 bit(去除第 8、16、24、32、40、48、56、64 位的奇偶校验位)。这些校验位的作用是确保密钥在传输过程中每个字节都有奇数个 1(奇校验),用于检测传输错误。
PC-1 置换表:57 49 41 33 25 17 9 1 58 50 42 34 26 1810 2 59 51 43 35 2719 11 3 60 52 44 3663 55 47 39 31 23 15 7 62 54 46 38 30 2214 6 61 53 45 37 2921 13 5 28 20 12 4
逆向实战提示:实际开发中,很多程序员并不会刻意设置校验位,而是直接使用 8 字节(64 bit)的字符串或字节数组作为密钥。DES 实现会自动忽略校验位,所以这不影响加密结果。但在逆向还原时需要注意,不同 DES 库对校验位的处理可能有细微差异。
4.2 循环左移(Left Circular Shift)
56 bit 有效密钥被分为 C 和 D 各 28 bit 后,每轮根据固定的位移表进行循环左移。所谓"循环"左移,是指移出左边界的位会回到右边界。
每轮的循环左移位数:
轮次: 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
规律总结:第 1、2、9、16 轮左移 1 位,其余轮左移 2 位。总左移量 = 4×1 + 12×2 = 28 位,恰好完成一个完整循环——这意味着 C16 = C0,D16 = D0。
逆向要点:这个左移位数序列 1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1 也是识别 DES 的重要特征。如果在反汇编代码中看到这个数组(通常以 int shifts[16] 或 uint8_t shifts[] 形式存在),可以高度怀疑是 DES 密钥调度。
4.3 PC-2 置换(选择置换 2,Permuted Choice 2)
从 56 bit(Ci 和 Di 合并)中选出 48 bit 作为子密钥。注意 PC-2 会丢弃 8 位,进一步增加密钥分析的难度。
PC-2 置换表:14 17 11 24 1 5 3 28 15 6 21 1023 19 12 4 26 816 7 27 20 13 241 52 31 37 47 5530 40 51 45 33 4844 49 39 56 34 5346 42 50 36 29 32
安全设计:PC-2 选择性地丢弃了 8 位,这意味着不同轮的子密钥 Ki 使用了密钥的不同子集,增加了从已知子密钥反推原始密钥的难度。
5. DES 的工作模式
在安卓逆向中,不仅需要识别 DES 算法本身,还需要准确判断其工作模式(Mode of Operation)。不同模式对还原结果有直接影响——即使算法和密钥完全正确,模式判断错误也会导致解密失败。
5.1 ECB(电子密码本模式,Electronic Codebook)ECB 和 CBC 工作模式对比
ECB 是最简单的工作模式,每个明文块独立加密,使用相同密钥。
- 特点:相同明文块 → 相同密文块(这是 ECB 最大的安全缺陷)
- 逆向线索:如果发现重复的 8 字节密文块,极有可能是 ECB 模式
- Java 标识:
Cipher.getInstance("DES/ECB/PKCS5Padding")
经典示例:ECB 模式的安全隐患最著名的演示是"ECB 企鹅"——将一张 Linux 企鹅 Tux 的 BMP 图片用 ECB 模式加密后,由于相同颜色的像素块产生相同的密文,加密后的图片仍然清晰可辨认企鹅的轮廓。这直观展示了 ECB 模式无法隐藏数据的统计特征。
5.2 CBC(密码块链接模式,Cipher Block Chaining)
CBC 模式通过将前一个密文块与当前明文块异或后再加密,形成链式结构。
- 特点:需要 IV(Initialization Vector,初始化向量),相同明文在不同 IV 下产生不同密文
- 逆向线索:存在额外的 8 字节 IV 参数;密文长度 = 明文填充后长度 + 可能的 IV
- Java 标识:
Cipher.getInstance("DES/CBC/PKCS5Padding")
IV 的常见位置:在逆向分析中,IV 可能出现在多种位置:硬编码在代码中(最常见且最不安全)、从服务器动态获取、拼接在密文头部(前 8 字节为 IV)、使用全零 IV(0x0000000000000000)。其中"IV 拼在密文前面"是最容易踩的坑——如果解密结果偏移 8 字节,很可能就是这个原因。
5.3 其他模式速览
| | | |
|---|
| | 可处理不满 8 字节的数据,将分组密码转化为流密码 | |
| | | |
| | | |
现代推荐:如果在新项目中需要选择工作模式,应优先使用 GCM(Galois/Counter Mode)或 CCM 模式,它们在提供加密的同时还提供认证功能(Authenticated Encryption),可以防止密文篡改。
5.4 填充方式(Padding)
DES 要求输入为 8 字节整数倍,不足时需要填充。正确识别填充方式对解密至关重要。
| | |
|---|
| | 解密后末尾有规律的重复字节(如缺 3 字节则填充 03 03 03) |
| | 与 PKCS5 实际效果一致(DES 块大小为 8,在 PKCS5 支持范围内) |
| | |
| | |
| | |
PKCS5 vs PKCS7:严格来说 PKCS5Padding 只定义了 8 字节块大小的填充,PKCS7Padding 支持任意块大小(1-255 字节)。但在 Java/Android 中,两者对 DES(8 字节块)的行为完全相同,可以互换使用。在逆向中不必纠结两者的区别。
6. 安卓平台中 DES 的实现方式
6.1 Java 层标准实现
这是安卓中最常见的 DES 使用方式,通过 javax.crypto 包提供的标准 API 实现:
// 最基础的 DES-ECB 加密// SecretKeySpec 直接将字节数组包装为密钥对象,不做校验位处理publicstaticbyte[] desEncrypt(byte[] data, byte[] key) throws Exception {// 创建密钥规范,指定算法为 "DES" SecretKeySpec keySpec = new SecretKeySpec(key, "DES");// 获取 Cipher 实例,指定算法/模式/填充 Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");// 初始化为加密模式 (ENCRYPT_MODE = 1) cipher.init(Cipher.ENCRYPT_MODE, keySpec);// 执行加密并返回密文return cipher.doFinal(data);}// DES-CBC 加密(需要 IV)// CBC 模式比 ECB 更安全,但需要额外的初始化向量参数publicstaticbyte[] desCBCEncrypt(byte[] data, byte[] key, byte[] iv) throws Exception { SecretKeySpec keySpec = new SecretKeySpec(key, "DES");// 创建 IV 参数规范,IV 长度必须为 8 字节 IvParameterSpec ivSpec = new IvParameterSpec(iv); Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");// CBC 模式需要同时传入密钥和 IV cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);return cipher.doFinal(data);}
逆向识别关键字符串:
"DES""DES/ECB/PKCS5Padding""DES/CBC/PKCS5Padding""DES/CBC/NoPadding""DESede" ← 3DES(Triple DES)"DESede/CBC/PKCS5Padding" ← 3DES-CBC
补充:在 Android 中,如果调用 Cipher.getInstance("DES") 而不指定模式和填充,默认使用的是 DES/ECB/PKCS5Padding。这是一个容易被忽略的细节,逆向时需注意。
6.2 通过 DESKeySpec 生成密钥
// 另一种常见写法,通过 DESKeySpec 和 SecretKeyFactory 生成密钥// 这种方式会自动处理奇偶校验位DESKeySpec dks = new DESKeySpec(keyBytes); // 注意:只取前 8 字节!SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");SecretKey secretKey = keyFactory.generateSecret(dks);
注意:DESKeySpec 只取前 8 字节作为密钥,如果传入更长的字节数组,多余的会被忽略。这是逆向中容易踩的坑——比如开发者传入了一个 16 字节的字符串作为密钥,实际参与加密的只有前 8 字节。
6.3 Native 层实现(JNI/C/C++)
很多安全要求较高的应用会将 DES 实现移到 Native 层(so 库),以增加逆向分析的难度:
// 典型的 Native DES 实现结构(基于 OpenSSL)#include<openssl/des.h>voidnative_des_encrypt(constunsignedchar *input, unsignedchar *output,constunsignedchar *key){ DES_cblock des_key; // DES 密钥块,8 字节 DES_key_schedule schedule; // 密钥调度表,存放 16 个子密钥// 将原始密钥复制到 DES 密钥块中memcpy(des_key, key, 8);// 生成密钥调度表(不检查弱密钥)// 如果使用 DES_set_key_checked 则会检查弱密钥并返回错误 DES_set_key_unchecked(&des_key, &schedule);// 执行 ECB 模式单块加密// DES_ENCRYPT = 1 表示加密,DES_DECRYPT = 0 表示解密 DES_ecb_encrypt((const_DES_cblock *)input, (DES_cblock *)output, &schedule, DES_ENCRYPT);}
逆向 Native 层的策略:对于使用 OpenSSL 的 DES 实现,可以直接搜索导出函数名 DES_ecb_encrypt、DES_cbc_encrypt 等。对于静态链接 OpenSSL 的情况(函数名被 strip),则需要通过 S 盒等常量特征来定位 DES 代码。
6.4 手写查表实现
部分开发者为了规避检测(或出于学习目的),会手写 DES 实现:
// 手写实现的 S 盒查表(常在混淆后的 so 中出现)// S1 盒:4 行 16 列,输入 6 bit,输出 4 bitstaticconstuint8_t S1[4][16] = { {14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7}, // 行 0 (b1b6 = 00) {0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8}, // 行 1 (b1b6 = 01) {4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0}, // 行 2 (b1b6 = 10) {15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13} // 行 3 (b1b6 = 11)};// ... S2~S8 类似,共 8 个 S 盒
手写实现的特点:手写实现通常会将 8 个 S 盒定义为一个大数组 uint8_t S[8][4][16] 或 uint32_t S[8][64](将 S 盒预计算为 32 bit 查找表以提高性能)。在 IDA 中,这些数组通常会被识别为一块连续的数据段。
7. 安卓逆向中如何识别 DES
7.1 静态分析识别方法
7.1.1 字符串特征搜索
使用 jadx、GDA 或 JEB 反编译 APK 后,搜索以下特征字符串:
关键字符串搜索列表:├── Java 标准 API 字符串│ ├── "DES"│ ├── "DES/"│ ├── "DESede"│ ├── "Cipher"│ ├── "SecretKeySpec"│ ├── "DESKeySpec"│ └── "SecretKeyFactory"│├── 类名 / 方法名│ ├── javax.crypto.Cipher│ ├── javax.crypto.spec.SecretKeySpec│ ├── javax.crypto.spec.IvParameterSpec│ ├── javax.crypto.spec.DESKeySpec│ └── javax.crypto.SecretKeyFactory│└── Native 层字符串 ├── "DES_set_key" ├── "DES_ecb_encrypt" ├── "DES_cbc_encrypt" └── "des_encrypt" / "des_decrypt"
搜索技巧:在 jadx 中使用 Navigation → Text Search(快捷键 Ctrl+Shift+F)进行全局搜索。建议先搜索 "DES/" 或 "DES/ECB" 等带斜杠的字符串,可以更精确地定位加密代码,避免被变量名中碰巧包含 "DES" 的无关代码干扰。
7.1.2 常量特征识别
在 Native 层或手写实现中,通过搜索 DES 的固定常量来识别:
常量特征速查:┌─────────────────────────────────────────────────┐│ IP 表首字节: 58 50 42 34 26 18 10 2 │ IP⁻¹首字节: 40 8 48 16 56 24 64 32 │ E 表首字节: 32 1 2 3 4 5 │ P 表首字节: 16 7 20 21 29 12 28 17 │ S₁盒首行: 14 4 13 1 2 15 11 8 │ PC-1首字节: 57 49 41 33 25 17 9 │ PC-2首字节: 14 17 11 24 1 5 │ 左移序列: 1 1 2 2 2 2 2 2 1 2... └─────────────────────────────────────────────────┘
实战技巧:在 IDA Pro 中可以使用 FindCrypt 插件或 Signsrch 工具自动扫描这些常量。FindCrypt 的原理就是维护了一个常见加密算法常量的数据库,在二进制文件中进行模式匹配。
FindCrypt 使用方法:安装 FindCrypt 插件后,在 IDA 中选择 Edit → Plugins → FindCrypt 即可自动扫描。如果发现了 DES S-box,IDA 会在对应地址添加注释标记。然后通过交叉引用(按 X 键)追溯到使用该常量的函数,即可定位 DES 核心代码。
7.1.3 IDA Pro / Ghidra 分析技巧
Native 层 DES 识别流程:1. 加载 so 文件到 IDA Pro / Ghidra2. 运行 FindCrypt / crypto_identifier 插件3. 搜索 S 盒常量 → 定位 DES 代码位置4. 向上追溯调用链(Xref) → 找到密钥和明文来源5. 分析是标准 DES 还是魔改版本
7.2 动态分析识别方法
7.2.1 Frida Hook Java 层
// Frida 脚本:Hook 所有 DES 加密操作// 使用方法:frida -U -f com.target.app -l des_hook.jsJava.perform(function() {var Cipher = Java.use('javax.crypto.Cipher');// Hook getInstance - 确认算法类型// 当应用调用 Cipher.getInstance() 时触发 Cipher.getInstance.overload('java.lang.String').implementation = function(algorithm) {if (algorithm.toUpperCase().indexOf('DES') !== -1) {console.log('[*] Cipher.getInstance: ' + algorithm);// 打印调用栈,帮助定位加密代码在哪个类/方法中被调用console.log('[*] Stack: ' + Java.use("android.util.Log") .getStackTraceString(Java.use("java.lang.Exception").$new())); }returnthis.getInstance(algorithm); };// Hook init - 获取密钥和 IV// mode=1 表示加密,mode=2 表示解密 Cipher.init.overload('int', 'java.security.Key').implementation = function(mode, key) {var modeStr = (mode === 1) ? "ENCRYPT" : "DECRYPT";console.log('[*] Cipher.init mode: ' + modeStr);// 获取密钥的原始字节var keyBytes = key.getEncoded();console.log('[*] Key (hex): ' + bytesToHex(keyBytes));console.log('[*] Key (utf8): ' + bytesToString(keyBytes));returnthis.init(mode, key); };// Hook 带 IV 的 init 重载 - CBC 模式会走这个分支 Cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function(mode, key, params) {var modeStr = (mode === 1) ? "ENCRYPT" : "DECRYPT";console.log('[*] Cipher.init (with IV) mode: ' + modeStr);var keyBytes = key.getEncoded();console.log('[*] Key (hex): ' + bytesToHex(keyBytes));// 尝试获取 IVtry {var ivSpec = Java.cast(params, Java.use('javax.crypto.spec.IvParameterSpec'));console.log('[*] IV (hex): ' + bytesToHex(ivSpec.getIV())); } catch(e) {console.log('[*] Params: ' + params.toString()); }returnthis.init(mode, key, params); };// Hook doFinal - 获取输入输出数据// 这是实际执行加密/解密的方法 Cipher.doFinal.overload('[B').implementation = function(data) {console.log('[*] doFinal input (hex): ' + bytesToHex(data));console.log('[*] doFinal input (utf8): ' + bytesToString(data));var result = this.doFinal(data);console.log('[*] doFinal output (hex): ' + bytesToHex(result));return result; };});// 辅助函数:字节数组转十六进制字符串functionbytesToHex(bytes) {var hex = [];for (var i = 0; i < bytes.length; i++) { hex.push(('0' + (bytes[i] & 0xFF).toString(16)).slice(-2)); }return hex.join('');}// 辅助函数:字节数组转 UTF-8 字符串functionbytesToString(bytes) {var str = '';for (var i = 0; i < bytes.length; i++) { str += String.fromCharCode(bytes[i] & 0xFF); }return str;}
7.2.2 Frida Hook Native 层
// Hook OpenSSL 的 DES 函数// 适用于使用 OpenSSL 动态链接库(libcrypto.so)的应用var des_ecb_encrypt = Module.findExportByName("libcrypto.so", "DES_ecb_encrypt");if (des_ecb_encrypt) { Interceptor.attach(des_ecb_encrypt, {onEnter: function(args) {console.log("[*] DES_ecb_encrypt called");// args[0] = 输入数据指针(8 字节明文/密文)console.log("[*] Input: " + hexdump(args[0], { length: 8 }));// args[2] = 密钥调度表指针console.log("[*] Key Schedule at: " + args[2]);// args[3] = 加密方向(1=加密,0=解密)console.log("[*] Direction: " + (args[3].toInt32() === 1 ? "ENCRYPT" : "DECRYPT"));// 保存输出指针,在 onLeave 中读取加密结果this.output = args[1]; },onLeave: function(retval) {// args[1] = 输出数据指针(8 字节密文/明文)console.log("[*] Output: " + hexdump(this.output, { length: 8 })); } });}
Native Hook 补充:如果是静态链接 OpenSSL(函数名被 strip),可以通过 FindCrypt 找到 S 盒地址,再通过交叉引用定位 DES 核心函数地址,然后用 Interceptor.attach(ptr("0x12345")) 直接 Hook 地址。
7.2.3 Xposed Hook
// Xposed 模块 Hook DES// 适用于需要持久化 Hook 的场景(如长期监控某个应用的加密行为)XposedHelpers.findAndHookMethod("javax.crypto.Cipher", lpparam.classLoader,"getInstance", String.class,newXC_MethodHook() {@OverrideprotectedvoidbeforeHookedMethod(MethodHookParam param){ String algorithm = (String) param.args[0];if (algorithm.contains("DES")) { XposedBridge.log("DES Algorithm detected: " + algorithm); } } });
Frida vs Xposed:Frida 适合临时分析和快速验证(即插即用),Xposed 适合持久化监控和模块化开发。在实际逆向中,通常先用 Frida 快速确认加密逻辑,再根据需要决定是否用 Xposed 开发持久化模块。
7.3 识别判定流程图
以下流程图总结了从发现加密逻辑到最终确认算法类型的完整判定过程:
DES 识别判定流程识别三板斧:搜字符串 → 找常量 → 动态 Hook。其中字符串 "DES" 和 S 盒常量 14, 4, 13, 1... 是最可靠的识别特征。
8. 实战:DES 算法还原
8.1 Java 层还原步骤
场景:反编译后发现如下混淆代码:
// 混淆后的代码示例// 类名和方法名被 ProGuard/R8 混淆为单字符publicclassa{publicstaticbyte[] a(byte[] bArr, String str) {// 从混淆代码中可以提取的关键信息:// 1. 密钥来源:str 参数,UTF-8 编码// 2. 算法:DES/CBC/PKCS5Padding// 3. IV:硬编码为 "12345678"(8 字节) SecretKeySpec secretKeySpec = new SecretKeySpec(str.getBytes("UTF-8"), "DES"); Cipher instance = Cipher.getInstance("DES/CBC/PKCS5Padding"); instance.init(1, secretKeySpec, new IvParameterSpec("12345678".getBytes()));return instance.doFinal(bArr); }}
还原分析:
信息提取:├── 算法: DES├── 模式: CBC├── 填充: PKCS5Padding├── 操作: 加密 (mode = 1 = Cipher.ENCRYPT_MODE)├── 密钥: str 参数,UTF-8 编码(需要从调用方追溯)├── IV: "12345678" 固定值(硬编码,安全隐患)└── 输入: bArr 字节数组
Python 还原代码:
from Crypto.Cipher import DESfrom Crypto.Util.Padding import pad, unpaddefdes_cbc_encrypt(data: bytes, key: bytes, iv: bytes = b'12345678') -> bytes:"""DES-CBC 加密,与 Java 代码行为一致"""# 创建 DES-CBC 加密器 cipher = DES.new(key, DES.MODE_CBC, iv)# PKCS5Padding: 缺 n 字节填充 n 个 n padded_data = pad(data, DES.block_size)return cipher.encrypt(padded_data)defdes_cbc_decrypt(data: bytes, key: bytes, iv: bytes = b'12345678') -> bytes:"""DES-CBC 解密""" cipher = DES.new(key, DES.MODE_CBC, iv) decrypted = cipher.decrypt(data)# 去除 PKCS5 填充return unpad(decrypted, DES.block_size)
8.2 Native 层还原步骤
场景:在 so 库中发现 DES 实现,通过 IDA 分析后得到以下伪代码:
// IDA 反编译伪代码(简化)// sub_1A3C0 被 FindCrypt 标记为 DES 相关函数int __fastcall sub_1A3C0(int *input, int *output, int *key_schedule){int L, R, temp;// IP 置换 - 将 64 bit 输入重排为 L 和 R initial_permutation(input, &L, &R);// 16 轮 Feistel 迭代for (int i = 0; i < 16; i++) { temp = R;// F 函数:扩展置换 + 子密钥异或 + S 盒代换 + P 置换 R = L ^ feistel_function(R, key_schedule[i]); L = temp; // 左右交换 }// 最终交换 + IP⁻¹ 逆初始置换 final_permutation(R, L, output); // 注意 R 在前,L 在后return0;}
还原要点:
- 确认 S 盒是否标准:将二进制中的 S 盒数据提取出来,与标准 DES S 盒逐一对比
- 确认置换表是否标准:检查 IP、IP⁻¹、E、P 置换表是否为标准值
- 确认密钥调度是否标准:检查 PC-1、PC-2 置换表和循环左移序列
- 确认字节序(大端 / 小端):ARM 架构通常是小端序,但 DES 标准定义使用大端序,需检查是否做了字节序转换
- 检查是否有额外的预处理或后处理:如密钥变换、输入/输出的 XOR、Base64 编码等
8.3 常见还原陷阱
| | |
|---|
| | 首先用 Frida Hook 确认实际使用的密钥和 IV |
| | |
| | 检查是 PKCS5、ZeroPadding 还是 NoPadding |
| | |
| | Native 层常见,尤其是 ARM↔x86 跨平台时 |
| | |
| | 检查是否使用了 -_ 代替 +/ 的 URL-safe Base64 |
| | 密文的前 8 字节可能是 IV,真正的密文从第 9 字节开始 |
8.4 逆向验证方法
用已知的输入输出对来验证还原结果——这是确认还原正确性的金标准:
# 验证脚本import binasciifrom Crypto.Cipher import DES# 从 Frida Hook 中获取的实际数据(替换为真实值)known_plaintext = binascii.unhexlify("48656c6c6f575244") # "HelloWRD"known_key = binascii.unhexlify("0123456789abcdef") # 8 字节密钥known_ciphertext = binascii.unhexlify("a1b2c3d4e5f6a7b8") # Hook 到的密文# 用还原的算法验证加密结果cipher = DES.new(known_key, DES.MODE_ECB)result = cipher.encrypt(known_plaintext)# 对比 Hook 结果与计算结果if result == known_ciphertext: print("[+] 还原成功!算法匹配")else: print("[-] 还原失败,需要进一步分析") print(f" 期望: {binascii.hexlify(known_ciphertext).decode()}") print(f" 实际: {binascii.hexlify(result).decode()}")# 常见排查步骤:# 1. 检查密钥是否正确(可能有预处理)# 2. 检查模式是否正确(试试 CBC)# 3. 检查是否为 3DES# 4. 检查是否为魔改 DES
9. DES 变种算法
9.1 3DES(Triple DES / DESede)
3DES 是 DES 最常见的增强版本,使用两个或三个密钥对数据进行三轮 DES 操作。名称中的 "ede" 代表 Encrypt-Decrypt-Encrypt(加密-解密-加密),这种设计使得当 K1=K2=K3 时,3DES 退化为标准 DES,保持了向后兼容性。
DES 变种算法对比安卓中的标识:
// 3DES-CBC 加密示例Cipher.getInstance("DESede/CBC/PKCS5Padding");// 密钥长度:24 字节 (3-Key) 或 16 字节 (2-Key)// 2-Key 模式下,Java 会自动将 16 字节扩展为 24 字节(K3=K1)SecretKeySpec keySpec = new SecretKeySpec(key24Bytes, "DESede");
逆向识别:如果发现密钥长度为 16 或 24 字节,且算法字符串为 "DESede",则为 3DES。在 Native 层,3DES 通常通过 DES_ede3_cbc_encrypt 或 DES_ecb3_encrypt 等 OpenSSL 函数实现。
安全性说明:3DES-2K(112 bit 有效密钥)和 3DES-3K(168 bit 有效密钥)目前仍被认为在一定程度上是安全的,但 NIST 已建议在 2023 年后逐步淘汰 3DES,替换为 AES。3DES 的实际安全强度因 meet-in-the-middle 攻击而低于理论值。
9.2 DES 的魔改变种
在安卓逆向中,以下魔改方式比较常见:
9.2.1 修改 S 盒(最常见)
// 魔改:自定义 S 盒// 保持了 S 盒的结构(4x16 矩阵,值 0~15),但更改了具体数值staticconstuint8_t custom_S1[4][16] = { {12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11},// ... 与标准 S 盒不同的数据};
识别方法:对比 S 盒数据,如果结构像 DES(4x16 矩阵,值 0~15)但数据不同,就是 S 盒魔改。这种魔改虽然改变了 S 盒的具体值,但通常不会改变 S 盒的密码学性质要求(每行是 0-15 的置换),否则加密可能出现严重问题。
9.2.2 修改置换表
// 魔改:自定义 IP 置换表// 长度仍为 64,值仍为 1~64 的某种排列staticconstuint8_t custom_IP[64] = {40, 8, 48, 16, 56, 24, 64, 32, // 故意打乱// ...};
9.2.3 修改轮数
// 标准 DES 为 16 轮,魔改可能改为其他值// 增加轮数并不一定能提高安全性,但会增加逆向分析的难度#define CUSTOM_ROUNDS 20 // 增加到 20 轮
9.2.4 修改密钥调度
// 魔改:修改循环左移位数// 修改后密钥调度生成的子密钥与标准 DES 不同staticconstint custom_shifts[16] = {2, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1// 第 1 轮从 1 改为 2};
9.2.5 前后处理魔改
// 魔改:在标准 DES 前后添加额外操作// 这种方式最隐蔽,因为 DES 本身是标准的publicbyte[] customEncrypt(byte[] data, byte[] key) {// 前处理:XOR 一个固定值(简单的数据变换)for (int i = 0; i < data.length; i++) { data[i] ^= 0xAB; }// 标准 DES 加密byte[] encrypted = standardDESEncrypt(data, key);// 后处理:字节翻转(逆序排列) reverse(encrypted);return encrypted;}
逆向策略:对于前后处理魔改,最有效的方法是使用 Frida 分别 Hook 标准 DES 的输入输出,再与应用的最终输入输出对比,差异部分就是前后处理逻辑。
9.3 DESX
DESX 在 DES 的外部添加额外的异或操作(由 Ron Rivest 于 1984 年提出),使用三个密钥。其设计目的是以最小的性能代价显著增加暴力搜索的难度:
密文 = K₃ ⊕ DES_E(K₁, 明文 ⊕ K₂)其中 K₁ 为标准 DES 密钥(56 bit 有效),K₂ 和 K₃ 为额外的 64 bit 密钥有效密钥长度 = 56 + 64 + 64 = 184 bit
DESX 在逆向中很少见,但理解其原理有助于识别类似的"XOR 包裹"设计模式——很多自研加密方案都采用"先 XOR 再加密再 XOR"的结构。
9.4 各变种对比总结
10. 防护与对抗
10.1 开发者常见的 DES 防护手段
| | |
|---|
| | jadx 反混淆 + 搜索 API 特征(如 Cipher.getInstance) |
| | IDA/Ghidra + FindCrypt 插件定位 S 盒 |
| | Hook 网络请求或直接 Hook Cipher.init 获取密钥 |
| | 分析查找表还原密钥,或直接 Hook 获取运行时密钥 |
| | |
| | 符号执行 + 去混淆工具(如 D-810、deflat) |
| | 过反调试 + 使用增强版 Frida(如 Frida-Gadget 注入) |
10.2 逆向工程师的工具箱
推荐工具清单:静态分析:├── jadx ─── Java 反编译(首选,开源免费)├── JEB ─── 商业级反编译器(付费,反混淆能力强)├── GDA ─── 国产安卓反编译器(免费,支持 DEX 直接分析)├── IDA Pro ─── Native 代码分析(业界标准,支持 ARM/x86)├── Ghidra ─── NSA 开源逆向工具(免费,功能与 IDA 接近)├── FindCrypt ─── IDA 加密常量识别插件(自动识别 DES/AES/MD5 等)└── Signsrch ─── 签名搜索工具(支持独立使用或作为 IDA 插件)动态分析:├── Frida ─── 动态插桩框架(首选,跨平台,社区活跃)├── Xposed ─── Java 层 Hook 框架(需 Root,持久化)├── Objection ─── 基于 Frida 的自动化工具(内置加密 Hook 脚本)├── r2frida ─── radare2 + Frida 联动(适合内存分析)└── Burp Suite ─── 流量抓包分析(HTTP/HTTPS 代理)算法验证:├── CyberChef ─── 在线加解密瑞士军刀(快速验证)├── PyCryptodome ─── Python 加密库(编写还原脚本)├── openssl ─── 命令行加解密工具(快速测试)└── 自编验证脚本 ─── 快速验证还原结果是否正确
10.3 算法迁移建议
如果在逆向分析中发现 App 仍在使用 DES,从安全角度应建议迁移:
DES (56 bit) ──不安全──► 不推荐使用,可被暴力破解3DES (112/168 bit) ──逐步淘汰──► 仅用于兼容遗留系统(NIST 建议 2023 后停用)AES-128/256 ──推荐──► 当前国际标准,性能优异ChaCha20-Poly1305 ──推荐──► 移动端友好,ARM NEON 加速SM4 ──推荐──► 国密标准场景(金融、政务合规)
迁移建议:对于新项目,强烈建议使用 AES-256-GCM(提供加密 + 认证)或 ChaCha20-Poly1305(在无 AES 硬件加速的设备上性能更好)。如果是国内金融/政务场景,应使用 SM4 算法(国密标准 GM/T 0002-2012)。
11. 附录:核心常量速查表
11.1 完整 S 盒数据
以下是全部 8 个 S 盒的数据,用于逆向中对比验证。在 IDA 中找到疑似 S 盒的数据后,逐行对比即可判断是标准 DES 还是魔改版本:
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 015 12 8 2 4 9 1 7 5 11 3 14 10 0 6 13S2: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 1513 8 10 1 3 15 4 2 11 6 7 12 0 5 14 9S3:10 0 9 14 6 3 15 5 1 13 12 7 11 4 2 813 7 0 9 3 4 6 10 2 8 5 14 12 11 15 113 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 12S4: 7 13 14 3 0 6 9 10 1 2 8 5 11 12 4 1513 8 11 5 6 15 0 3 4 7 2 12 1 10 14 910 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 14S5: 2 12 4 1 7 10 11 6 8 5 3 15 13 0 14 914 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 1411 8 12 7 1 14 2 13 6 15 0 9 10 4 5 3S6:12 1 10 15 9 2 6 8 0 13 3 4 14 7 5 1110 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 13S7: 4 11 2 14 15 0 8 13 3 12 9 7 5 10 6 113 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 12S8: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 2 0 14 9 11 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
11.2 CyberChef 快速验证
在无法搭建完整开发环境时,可使用 CyberChef 在线工具进行快速验证:
验证步骤:1. 打开 CyberChef(搜索 "CyberChef" 即可找到官方页面)2. 在左侧 Operations 搜索框中输入 "DES"3. 拖入 "DES Encrypt" 或 "DES Decrypt" 操作到 Recipe 区域4. 设置参数: - Key: 输入十六进制密钥(如 0123456789abcdef) - IV: 输入十六进制 IV(CBC 模式需要,如 0000000000000000) - Mode: 选择 ECB 或 CBC - Input/Output: 选择 Hex/Base64/Raw5. 在 Input 区域输入待加密/解密的数据6. 对比 Output 结果与 Frida Hook 到的数据
CyberChef 使用技巧:可以将多个操作串联成 Recipe,比如先 From Base64 再 DES Decrypt,一次完成解码+解密。Recipe 可以导出为 URL 分享给团队成员。
11.3 常用 openssl 命令
# DES-ECB 加密(-nosalt 禁止添加盐值,保持与代码行为一致)echo -n "HelloWRD" | openssl des-ecb -K 0123456789abcdef -nosalt | xxd# DES-CBC 加密(需要指定 IV)echo -n "HelloWRD" | openssl des-cbc -K 0123456789abcdef -iv 0000000000000000 -nosalt | xxd# DES-CBC 解密(-d 表示解密)echo"密文hex" | xxd -r -p | openssl des-cbc -d -K 0123456789abcdef -iv 0000000000000000 -nosalt# 3DES-CBC 加密(密钥长度为 24 字节 = 48 个十六进制字符)echo -n "HelloWRD" | openssl des-ede3-cbc -K 0123456789abcdef0123456789abcdef0123456789abcdef -iv 0000000000000000 -nosalt | xxd
openssl 版本注意:在较新版本的 OpenSSL(3.0+)中,DES 相关命令可能需要添加 -provider legacy 参数才能使用,因为 DES 已被标记为遗留算法。如果遇到 unsupported cipher 错误,可以尝试:openssl des-ecb -provider legacy -K ...
11.4 DES 弱密钥和半弱密钥
DES 存在一些特殊密钥,使用时会导致安全性降低:
4 个弱密钥(使用这些密钥时,16 个子密钥全部相同,加密两次等于不加密):
0101010101010101 (全 0,含校验位)FEFEFEFEFEFEFEFE (全 1,含校验位)1F1F1F1F0E0E0E0E (C0 全 0,D0 全 1)E0E0E0E0F1F1F1F1 (C0 全 1,D0 全 0)
6 对半弱密钥(使用半弱密钥 K1 加密的结果,可以用对应的另一个半弱密钥 K2 解密):
01FE01FE01FE01FE ←→ FE01FE01FE01FE011FE01FE00EF10EF1 ←→ E01FE01FF10EF10E01E001E001F101F1 ←→ E001E001F101F1011FFE1FFE0EFE0EFE ←→ FE1FFE1FFE0EFE0E011F011F010E010E ←→ 1F011F010E010E01E0FEE0FEF1FEF1FE ←→ FEE0FEE0FEF1FEF1
逆向意义:如果发现应用使用了弱密钥或半弱密钥,可以利用这一特性简化分析。例如使用弱密钥时 DES_E(DES_E(plaintext)) = plaintext,即加密两次恢复原文。不过实际遇到弱密钥的概率极低(4/2^56)。
11.5 DES 与 AES 核心差异对比
| | |
|---|
| | SPN(Substitution-Permutation Network) |
| | |
| | |
| | |
| | 1 个 8→8 bit S 盒(基于 GF(2^8) 有限域逆元) |
| | |
| | |
| | |
| | S 盒 0x63,0x7c,0x77,0x7b... |
对逆向工程的启示:掌握 DES 后学习 AES 会容易很多。两者都使用了替代-置换的设计哲学,区别在于 DES 使用 Feistel 结构(可以使用不可逆的 F 函数),而 AES 使用 SPN 结构(每一步操作都必须可逆)。识别方法也类似——通过搜索特征常量(DES 的 S 盒 vs AES 的 S-Box/逆 S-Box)来定位算法代码。
总结DES 逆向还原工作流
从安卓逆向的角度来看,DES 算法的识别和还原遵循以下核心思路:
识别三板斧:搜字符串 → 找常量 → 动态 Hook。字符串 "DES" 和 S 盒常量 14, 4, 13, 1... 是最可靠的识别特征。
还原四步走:确定算法(标准/魔改)→ 确定模式(ECB/CBC)→ 提取密钥和 IV → 编写验证脚本。
防坑要点:注意 DESKeySpec 只取前 8 字节、区分 DES 和 3DES 的密钥长度、检查 IV 是否拼接在密文中、确认 Base64 编码方式。
尽管 DES 已经不再安全,但在安卓逆向工程中,掌握 DES 的识别与还原仍然是一项必备技能。理解 DES 的 Feistel 结构和核心原理,也有助于理解其他对称加密算法(如 AES、SM4、Blowfish)的设计思路——它们都遵循类似的"混淆+扩散"设计哲学。