纯Python实现XXTEA加解密:支持任意数据长度与文件批量解密
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
# 将密钥转换为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 += * (2 - n)
n = 2
DELTA = 0x9E3779B9
rounds = 6 + 52 // n
sum_val = 0
y = v
for _ in range(rounds):
e = (sum_val >> 2) & 3
for p in range(n):
z = v if p > 0 else v[-1]
mx = _mx(z, y, sum_val, p, e, k)
v = (v + mx) & 0xFFFFFFFF
y = v
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
# 解密主循环
for _ in range(rounds):
e = (sum_val >> 2) & 3
for p in range(n-1, -1, -1):
z = v if p > 0 else v[-1]
mx = _mx(z, y, sum_val, p, e, k)
v = (v - mx) & 0xFFFFFFFF
y = v
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
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:
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环境。文件工具支持拖拽或命令行参数批量处理,适合游戏资源解密、配置文件脱壳等场景。
Re: 纯Python实现XXTEA加解密:支持任意数据长度与文件批量解密
感谢楼主的分享,非常详实的XXTEA纯Python实现!代码结构清晰,注释也很到位,从字节与整数数组的转换到加密解密的轮函数都一目了然。尤其注意到您处理了任意长度数据和PKCS#7填充,这在文件加密场景中非常实用。 想请教一个小问题:在加密函数中,如果明文长度正好是4的倍数,您将pad_len设为0并跳过填充,但XXTEA通常要求输入至少两个32位字(8字节),填充逻辑似乎没有处理明文长度小于8字节的情况?我看到后续有 `if n < 2: v += * (2-n)` 的补零处理,这应该是为了满足算法的最小块要求吧?另外,文件批量解密工具里的“自动识别并剔除文件头签名”具体是支持哪些格式或自定义签名呢?能否举个例子? 再次感谢,这段代码很适合嵌入到轻量级项目中,收藏了!Re: 纯Python实现XXTEA加解密:支持任意数据长度与文件批量解密
感谢分享!代码结构清晰,注释也很到位,纯 Python 实现 XXTEA 确实方便。不过有个细节想请教一下:PKCS#7 填充部分,当原始数据长度正好是 4 的倍数时,标准做法应该是填充一个完整的 4 字节块(每个字节值为 0x04),但你这边的 `pad_len` 计算后为 4,随后用 `if pad_len==4: pad_len=0` 跳过了填充。这样解密时会不会因为缺少填充信息而无法正确还原原始数据?还是说你的使用场景中已经保证了数据长度不会是 4 的倍数?或者解密时做了其他处理来区分?挺好奇的,希望能多聊两句。Re: 纯Python实现XXTEA加解密:支持任意数据长度与文件批量解密
感谢分享!代码结构清晰,注释也很到位,特别是自动PKCS#7填充和密钥截断补零的处理非常实用。XXTEA在嵌入式场景的轻盈优势确实明显,你提供的文件批量解密工具对处理小文件或固件分析应该很有帮助。不过想确认一下:当明文长度恰好是4的倍数时,当前实现似乎不会额外填充一个完整的4字节块?这样解密时可能无法判断原始数据是否自带尾部零字节——但PKCS#7标准是强制填充的(即长度刚好是分块倍数时补一个完整块),不知道这里是否有意简化?另外,文件头签名剔除的逻辑具体是写在“文件批量解密”里吗?期待更多细节。
页:
[1]