XXTEA(eXtended eXtended Tiny Encryption Algorithm)是TEA加密算法家族的第三代成员,由David Wheeler和Roger Needham于1998年提出。它修正了XTEA的缺陷,支持任意长度的数据块(不限于64位),并保持简单的Feistel结构。算法仅使用加法、减法、异或、移位等基本整数运算,非常适合嵌入式环境。密钥固定为128位(16字节),轮数根据数据长度动态计算:rounds = 6 + 52 // n,其中n是32位字的个数。
下面给出完整的纯Python实现,包含加密、解密以及自动PKCS#7填充处理,不依赖任何第三方库。同时提供一个实用的文件批量解密工具,可自动识别并剔除文件头签名。
- import os
- import sys
- import struct
- # ---------- 辅助函数:字节 ↔ 32位整数数组 ----------
- def _bytes_to_uint32(data: bytes) -> list:
- """字节串 -> 小端序32位无符号整数列表,长度自动补齐到4的倍数"""
- n = len(data)
- if n % 4 != 0:
- data += b'\x00' * (4 - n % 4)
- return list(struct.unpack(f'<{len(data)//4}I', data))
- def _uint32_to_bytes(arr: list) -> bytes:
- """小端序32位整数列表 -> 字节串"""
- return struct.pack(f'<{len(arr)}I', *arr)
- # ---------- 轮函数辅助计算 ----------
- def _mx(z, y, sum_val, p, e, k):
- return (((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((sum_val ^ y) + (k[(p & 3) ^ e] ^ z))
- # ---------- XXTEA 加密 ----------
- def xxtea_encrypt(plain: bytes, key: bytes) -> bytes:
- """加密任意字节串,返回密文字节串(自动添加PKCS#7填充)
- key: 任意长度的密钥,内部将截断或补零至16字节
- """
- if len(plain) == 0:
- return b''
- # PKCS#7 填充:补足到4字节整数倍
- pad_len = 4 - (len(plain) % 4)
- if pad_len == 4:
- pad_len = 0
- if pad_len:
- plain += bytes([pad_len]) * pad_len
- # 将密钥转换为4个32位整数
- key_bytes = key.ljust(16, b'\x00')[:16]
- k = _bytes_to_uint32(key_bytes)
- # 将明文转换为32位整数数组
- v = _bytes_to_uint32(plain)
- n = len(v)
- if n < 2:
- v += [0] * (2 - n)
- n = 2
- DELTA = 0x9E3779B9
- rounds = 6 + 52 // n
- sum_val = 0
- y = v[0]
- for _ in range(rounds):
- e = (sum_val >> 2) & 3
- for p in range(n):
- z = v[p-1] if p > 0 else v[-1]
- mx = _mx(z, y, sum_val, p, e, k)
- v[p] = (v[p] + mx) & 0xFFFFFFFF
- y = v[p]
- sum_val = (sum_val + DELTA) & 0xFFFFFFFF
- return _uint32_to_bytes(v)
- # ---------- XXTEA 解密 ----------
- def xxtea_decrypt(cipher: bytes, key: bytes) -> bytes:
- """解密密文字节串,自动去除PKCS#7填充"""
- if len(cipher) == 0:
- return b''
- if len(cipher) % 4 != 0:
- raise ValueError("密文长度必须是4的倍数")
- # 密钥转换
- key_bytes = key.ljust(16, b'\x00')[:16]
- k = _bytes_to_uint32(key_bytes)
- # 将密文转换为32位整数数组
- v = _bytes_to_uint32(cipher)
- n = len(v)
- if n < 2:
- raise ValueError("密文至少需要8字节")
- DELTA = 0x9E3779B9
- rounds = 6 + 52 // n
- sum_val = (rounds * DELTA) & 0xFFFFFFFF
- y = v[0]
- # 解密主循环
- for _ in range(rounds):
- e = (sum_val >> 2) & 3
- for p in range(n-1, -1, -1):
- z = v[p-1] if p > 0 else v[-1]
- mx = _mx(z, y, sum_val, p, e, k)
- v[p] = (v[p] - mx) & 0xFFFFFFFF
- y = v[p]
- sum_val = (sum_val - DELTA) & 0xFFFFFFFF
- # 转换回字节并去除PKCS#7填充
- plain_bytes = _uint32_to_bytes(v)
- pad_len = plain_bytes[-1]
- if pad_len <= len(plain_bytes):
- return plain_bytes[:-pad_len]
- else:
- return plain_bytes
- # ---------- 文件解密工具(含签名自动裁剪) ----------
- def decrypt_file(file_path, key, sign=None):
- """解密单个文件,支持可选的文件头签名裁剪"""
- if not os.path.exists(file_path):
- print(f"[错误] 文件不存在: {file_path}")
- return
- print(f"[处理] 正在读取文件: {file_path}")
- try:
- with open(file_path, 'rb') as f:
- encrypted_data = f.read()
- if sign and encrypted_data.startswith(sign):
- print(f"[检测] 发现文件头签名 '{sign.decode()}',已自动裁剪前 {len(sign)} 字节。")
- encrypted_data = encrypted_data[len(sign):]
- else:
- print("[提示] 未发现已知文件头签名,尝试直接整包解密。")
- decrypted_data = xxtea_decrypt(encrypted_data, key)
- if not decrypted_data:
- print("[失败] 解密结果为空(可能Key错误,或该文件并非XXTEA加密)。")
- return
- dir_name, file_name = os.path.split(file_path)
- base_name, ext = os.path.splitext(file_name)
- output_name = f"{base_name}_decrypted{ext}" if ext else f"{base_name}_decrypted"
- output_path = os.path.join(dir_name, output_name)
- with open(output_path, 'wb') as f:
- f.write(decrypted_data)
- print(f"[成功] 解密完成!已生成文件: {output_path}")
- except Exception as e:
- print(f"[报错] 解密过程中出现异常: {e}")
- if __name__ == "__main__":
- # 配置密钥和可选签名
- KEY = b"your_secret_key_16"
- SIGN = b"4meJnPyl"
- if len(sys.argv) > 1:
- for path in sys.argv[1:]:
- decrypt_file(path, KEY, SIGN)
- else:
- print("提示: 可以直接把文件拖到此工具上运行。")
- path = input("或者请输入要解密的文件路径: ").strip('"')
- if path:
- decrypt_file(path, KEY, SIGN)
- input("\n处理完毕,按下回车键退出...")
复制代码
代码说明:
- _bytes_to_uint32 / _uint32_to_bytes:实现小端序字节串与32位整数列表的相互转换,自动补齐到4字节倍数。
- _mx:XXTEA轮函数中的核心计算公式,将输入z、y、sum、位置p、e和密钥k组合,产生混淆值。
- xxtea_encrypt:加密函数,先进行PKCS#7填充(补足到4字节倍数),然后进行多轮加密。轮数由6 + 52 // n决定。加密时使用正向循环(0到n-1),每轮更新sum。
- xxtea_decrypt:解密函数,与加密对称。初始化sum = rounds * DELTA,使用逆向循环(n-1到0)依次更新每个字,最后去除填充字节。
- decrypt_file:实用的文件解密函数,自动读取文件、裁剪文件头签名、调用解密函数并输出解密后文件。
使用要点:
1. 密钥长度:建议使用16字节(128位),代码内部会自动截断或补零至16字节。
2. 数据长度:XXTEA内部要求数据为4字节倍数,加密时自动填充,解密后自动去除。
3. 端序:默认使用小端序,与QQ、微信等常用实现一致。对接其他系统时需确认端序匹配。
4. 安全性:XXTEA在2010年后被发现存在理论攻击,但对于非极敏感数据仍足够使用。现代应用更推荐AES。
该实现完全自包含,无需安装第三方库,适用于任何Python 3环境。文件工具支持拖拽或命令行参数批量处理,适合游戏资源解密、配置文件脱壳等场景。 |