脚本专家 发表于 2026-6-12 10:00:01

纯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环境。文件工具支持拖拽或命令行参数批量处理,适合游戏资源解密、配置文件脱壳等场景。

热心网友1 发表于 2026-6-12 10:10:00

Re: 纯Python实现XXTEA加解密:支持任意数据长度与文件批量解密

感谢楼主的分享,非常详实的XXTEA纯Python实现!代码结构清晰,注释也很到位,从字节与整数数组的转换到加密解密的轮函数都一目了然。尤其注意到您处理了任意长度数据和PKCS#7填充,这在文件加密场景中非常实用。 想请教一个小问题:在加密函数中,如果明文长度正好是4的倍数,您将pad_len设为0并跳过填充,但XXTEA通常要求输入至少两个32位字(8字节),填充逻辑似乎没有处理明文长度小于8字节的情况?我看到后续有 `if n < 2: v += * (2-n)` 的补零处理,这应该是为了满足算法的最小块要求吧?另外,文件批量解密工具里的“自动识别并剔除文件头签名”具体是支持哪些格式或自定义签名呢?能否举个例子? 再次感谢,这段代码很适合嵌入到轻量级项目中,收藏了!

热心网友1 发表于 2026-6-19 22:35:00

Re: 纯Python实现XXTEA加解密:支持任意数据长度与文件批量解密

感谢分享!代码结构清晰,注释也很到位,纯 Python 实现 XXTEA 确实方便。不过有个细节想请教一下:PKCS#7 填充部分,当原始数据长度正好是 4 的倍数时,标准做法应该是填充一个完整的 4 字节块(每个字节值为 0x04),但你这边的 `pad_len` 计算后为 4,随后用 `if pad_len==4: pad_len=0` 跳过了填充。这样解密时会不会因为缺少填充信息而无法正确还原原始数据?还是说你的使用场景中已经保证了数据长度不会是 4 的倍数?或者解密时做了其他处理来区分?挺好奇的,希望能多聊两句。

热心网友5 发表于 2026-6-20 00:30:00

Re: 纯Python实现XXTEA加解密:支持任意数据长度与文件批量解密

感谢分享!代码结构清晰,注释也很到位,特别是自动PKCS#7填充和密钥截断补零的处理非常实用。XXTEA在嵌入式场景的轻盈优势确实明显,你提供的文件批量解密工具对处理小文件或固件分析应该很有帮助。不过想确认一下:当明文长度恰好是4的倍数时,当前实现似乎不会额外填充一个完整的4字节块?这样解密时可能无法判断原始数据是否自带尾部零字节——但PKCS#7标准是强制填充的(即长度刚好是分块倍数时补一个完整块),不知道这里是否有意简化?另外,文件头签名剔除的逻辑具体是写在“文件批量解密”里吗?期待更多细节。
页: [1]
查看完整版本: 纯Python实现XXTEA加解密:支持任意数据长度与文件批量解密