原文:cnblogs.com/Tree-24/p/17330286.html 主题:逆向中 TEA 系列分组密码的原理、完整实现(带注释)与识别方法,附 CTF 例题
一、TEA(Tiny Encryption Algorithm)
简介:分组密码。明文/密文块 64 比特(2 个 uint32),密钥 128 比特(4 个 uint32)。每轮把轮密钥累加器 sum 增加一个 Delta(黄金分割率)常量,使每轮变换不同;迭代 32 轮。特征常量 delta = 0x9E3779B9(即 -0x61C88647)是识别 TEA 家族的第一特征。
加密实现(带注释)
#include <stdint.h>
/** * TEA 加密(32 轮) * @param v 64 位明文块:v[0]=高 32 位,v[1]=低 32 位(加密后被密文覆盖) * @param k 128 位密钥:k[0]~k[3],4 个 uint32 */void encrypt(uint32_t* v, uint32_t* k) { uint32_t v0 = v[0], v1 = v[1]; // 取出明文,拆成左右两半 v0、v1 uint32_t sum = 0; // 轮密钥累加器,每轮 +delta uint32_t delta = 0x9e3779b9; // ★ 黄金分割率常量(识别特征!) uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; // 缓存密钥,循环里少访存
for (int i = 0; i < 32; i++) { // 32 轮 sum += delta; // 每轮 sum 增加 delta → 每轮"轮密钥"不同 // 用 v1 更新 v0:三部分异或 = (v1<<4 + k0) ^ (v1+sum) ^ (v1>>5 + k1) // 即"左移 4 位 + k0"、"右移 5 位 + k1"、"v1 + sum" 三路混合,叠加到 v0 v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1); // 用刚更新过的 v0 再更新 v1(左右两半交替更新 = Feistel 结构) v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3); } v[0] = v0; v[1] = v1; // 写回密文}
/** * TEA 解密:与加密完全逆序 * sum 的初值 = delta × 32 = 0xC6EF3720(加密结束时 sum 的值),每轮往回减 */void decrypt(uint32_t* v, uint32_t* k) { uint32_t v0 = v[0], v1 = v[1]; uint32_t delta = 0x9e3779b9; uint32_t sum = delta * 32; // ★ 解密特征值 0xC6EF3720 由此而来 uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (int i = 0; i < 32; i++) { // 顺序相反:先还原 v1(此时 v0 还是加密后更新过的值),再还原 v0 v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3); v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1); sum -= delta; // sum 逆着减回 0 } v[0] = v0; v[1] = v1;}理解要点:TEA = 左右两半 + 32 轮 + 每轮 sum += delta 生成轮密钥 + 移位异或混合。逆向时看到 (v<<4) 与 (v>>5) 的移位异或组合 + 0x9E3779B9,基本可锁定 TEA。
二、XTEA(TEA 扩展版)
简介:同样是 64 位块 / 128 位密钥,但每轮按 sum 的值轮换取用 key[0..3](而不是固定 k0..k3),使密钥使用更均匀,安全性更高。
加密实现(带注释)
#include <stdint.h>
/** * XTEA 加密(轮数可变,建议 32 轮) * @param num_rounds 迭代轮数 * @param v 64 位明文块(2 个 uint32) * @param key 128 位密钥(4 个 uint32) */void encrypt(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) { unsigned int i; uint32_t v0 = v[0], v1 = v[1], sum = 0, delta = 0x9E3779B9;
for (i = 0; i < num_rounds; i++) { // (((v1<<4) ^ (v1>>5)) + v1):左移与右移结果异或后再加自身,是"混合"核心 // ^ (sum + key[sum & 3]):sum 的低 2 位作下标选 key[0..3] → 轮换取钥 v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]); sum += delta; // key[(sum >> 11) & 3]:取 sum 的第 11、12 位做下标,与上一行错开选 key v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]); } v[0] = v0; v[1] = v1;}
/** * XTEA 解密:逆向执行 * sum 初值 = delta × num_rounds,每轮往回减 */void decrypt(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) { unsigned int i; uint32_t v0 = v[0], v1 = v[1], delta = 0x9E3779B9, sum = delta * num_rounds;
for (i = 0; i < num_rounds; i++) { v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]); sum -= delta; v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]); } v[0] = v0; v[1] = v1;}与 TEA 的区别:① 混合部分从 (v<<4)+k 变成 ((v<<4) ^ (v>>5)) + v;② 密钥按 sum & 3 / (sum>>11) & 3 轮换选取。逆向识别点:出现 key[sum & 3] 这类用常量当下标轮换取钥 → XTEA/XXTEA。
三、XXTEA
简介:运算更复杂,块大小和密钥长度任意(对明文分块逐个加密)。块间首尾相接,用前后两个块做混合。
加密实现(带注释)
#include <stdint.h>
#define DELTA 0x9E3779B9// MX 混合宏:不是独立函数,直接内联展开,依赖外部变量:// y = 后一个块(v[p+1]),z = 当前块旧值(v[p]),sum = 轮密钥累加器,// p = 当前块下标,e = 由 sum 决定的密钥轮换因子,k = 密钥// 含义:(z>>5 ^ y<<2) + (y>>3 ^ z<<4) ← 前后两块的移位异或混合// ^ (sum ^ y) ← 与轮密钥相关项// + (k[(p&3)^e] ^ z) ← 密钥按 (p&3)^e 轮换选取,再异或 z#define MX (z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[(p & 3) ^ e] ^ z)
/** * XXTEA 加密 * @param v 明文块数组(uint32 数组,长度任意,≥2) * @param len v 中块的个数 * @param k 密钥数组(通常 4 个 uint32) */void xxtea_encrypt(uint32_t *v, uint32_t len, uint32_t *k) { uint32_t n = len - 1; // 最后一个块的下标 uint32_t y, z, sum = 0, e, p, q; q = 6 + 52 / len; // 轮数:块越少轮数越多(保证整体安全性) while (q-- > 0) { sum += DELTA; e = sum >> 2 & 3; // 由 sum 决定的密钥轮换因子 for (p = 0; p < n; p++) { // 正向处理除最后一个外的所有块 y = v[p + 1]; // y = 后一个块 z = v[p] += MX; // 当前块 += MX(宏里 z 取 v[p] 旧值参与运算) } y = v[0]; // 首尾相接:最后一个块的 y 取 v[0] z = v[n] += MX; }}
/** * XXTEA 解密:反向执行 * sum 初值 = q × DELTA(加密结束时的值),每轮往回减直到 0 */void xxtea_decrypt(uint32_t *v, uint32_t len, uint32_t *k) { uint32_t n = len - 1; uint32_t y, z, sum, e, p, q; q = 6 + 52 / len; sum = q * DELTA; while (sum != 0) { e = sum >> 2 & 3; for (p = n; p > 0; p--) { // 反向处理所有块 z = v[p - 1]; // z = 前一个块 y = v[p] -= MX; } z = v[n]; y = v[0] -= MX; sum -= DELTA; }}理解要点:
- XXTEA 的混合宏
MX同时用到前一个块 z 和后一个块 y,这是它和 TEA/XTEA 的最大区别; - 密钥下标
(p & 3) ^ e同时依赖位置 p 和轮密钥因子 e; - 注意:解密输出可能带填充字节(加密时为了凑整块填充的),需按数据长度截断。
逆向识别点:出现 (z>>5 ^ y<<2) 这种”前块移位异或后块”的结构 → XXTEA。
四、逆向识别特征汇总
- 存在针对 64bit / 128bit 数据的操作(输入 msg 与 key),通常用无符号 32 位数组表示;
- 先移位后异或的混合变换:
(z>>5 ^ y<<2)前后块混合 → XXTEA;(v<<4)与(v>>5)移位异或 → TEA / XTEA;
- 混合结果叠加到另一个值上、两半相互叠加(Feistel 结构);
- 常量下标轮换取钥(
key[sum & 3]/key[(sum>>11) & 3]/key[(p&3)^e])→ XTEA / XXTEA; - 定义 delta 常量且不随输入变化:未魔改就是
0x9E3779B9;魔改的话(如例题中的256256256、0x830A5376^0x1D3D2ACF)需从代码中提取。
逆向流程:识别算法(看特征)→ 编写对应解密脚本(Python 注意每步
& 0xffffffff)→ 提取 key / delta / 轮数 → 解出明文。
五、例题与 flag
1. [GDOUCTF2023]tea(XTEA 变体,delta 魔改为 256256256)
- 64 位无壳,IDA64 交叉引用 “you are right” 定位关键函数;
- 读入 16 进制数组
v8[10],key 为[2233,4455,6677,8899]; - Python 坑:C 无符号溢出自动回绕,Python 会升为长整数,解密每步必须
& 0xffffffff,否则输出乱码; - flag:
NSSCTF{hzCtf_94_re666fingcry5641qq}
2. [津门杯2021]GoodRe(TEA,delta 魔改为 0x830A5376 ^ 0x1D3D2ACF)
- TEA 运算以函数形式呈现,逐个分析并重命名简化反汇编;
- 密钥
[17,17,17,17],每 2 个 32 位一组解密后拼 hex 大写; - flag:
flag{7DEA3F6D3B3D6C0C620864ADD2FA2AE1A61F2736F0060DA0B97E8356D017CE59}
六、总结
识别口诀:看到
0x9E3779B9(或魔改 delta)+ 移位异或混合变换 + 相互叠加 Feistel 结构 → TEA 家族;key[sum&3]轮换取钥 → XTEA/XXTEA;(z>>5 ^ y<<2)前后块混合 → XXTEA。 解密要点:逆序执行、sum 从delta×轮数往回减、Python 无符号 32 位运算& 0xffffffff。
参考:TEA 系列加密解密、维基百科 TEA