dotnet 8 实现 XXTEA 解密核心算法
- 首先创建个目录
- 查看是否安装dotnet
- 初始化临时项目
- [创建 C# 脚本](# 脚本)
-
- [Program.cs 单文件版](#Program.cs 单文件版)
- 加强版
- [发布为单文件 EXE](#发布为单文件 EXE)
- 测试
- 参考资料
首先创建个目录
F:\program 接下来在这里面干活
查看是否安装dotnet
不记得什么时候装的了,有了直接用。(没装的看底部传送门)
bash
F:\program>dotnet --version
8.0.403
初始化临时项目
bash
dotnet new console --force
bash
F:\program>dotnet new console --force
已成功创建模板"控制台应用"。
正在处理创建后操作...
正在还原 F:\program\program.csproj:
正在确定要还原的项目...
已还原 F:\program\program.csproj (用时 48 毫秒)。
已成功还原。
创建 C# 脚本
Program.cs 单文件版
csharp
using System;
using System.IO;
using System.Text;
namespace XXTEATools
{
class XXTEADecryptor
{
// ================= 配置区域 =================
// SIGN: 加密文件的文件头标识(签名)
// KEY: XXTEA 加密的密钥
private static string SIGN = "4meJnPyl";
private static string KEY = "你的密钥";
// ============================================
static int Main(string[] args)
{
// 设置控制台编码为 UTF8,防止中文路径乱码
Console.OutputEncoding = Encoding.UTF8;
if (args.Length < 2)
{
Console.WriteLine("XXTEA 通用解密工具");
Console.WriteLine("---------------------------------------");
Console.WriteLine("用法: XXTEADecryptor.exe <输入文件路径> <输出文件路径>");
Console.WriteLine("示例: XXTEADecryptor.exe config.lua config_dec.lua");
return 1;
}
string inputPath = args[0];
string outputPath = args[1];
if (!File.Exists(inputPath))
{
Console.WriteLine($"[错误] 找不到输入文件: {inputPath}");
return 1;
}
try
{
byte[] data = File.ReadAllBytes(inputPath);
byte[] signBytes = Encoding.UTF8.GetBytes(SIGN);
// 1. 验证文件头标识
if (!IsMatchSign(data, signBytes))
{
Console.WriteLine($"[跳过] 签名不匹配,非目标加密文件: {Path.GetFileName(inputPath)}");
return 1;
}
// 2. 剥离签名头部,提取加密负载
byte[] encryptedData = new byte[data.Length - signBytes.Length];
Buffer.BlockCopy(data, signBytes.Length, encryptedData, 0, encryptedData.Length);
// 3. 执行 XXTEA 解密算法
byte[] keyBytes = Encoding.UTF8.GetBytes(KEY);
byte[] decryptedData = XXTEA.Decrypt(encryptedData, keyBytes);
if (decryptedData == null || decryptedData.Length == 0)
{
Console.WriteLine($"[失败] 解密结果为空,请检查密钥(Key)是否正确: {Path.GetFileName(inputPath)}");
return 1;
}
// 4. 自动处理输出目录
string outputDir = Path.GetDirectoryName(outputPath);
if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir))
{
Directory.CreateDirectory(outputDir);
}
// 5. 保存结果
File.WriteAllBytes(outputPath, decryptedData);
Console.WriteLine($"[成功] {Path.GetFileName(inputPath)} -> {Path.GetFileName(outputPath)}");
return 0;
}
catch (Exception ex)
{
Console.WriteLine($"[异常] 处理 {Path.GetFileName(inputPath)} 时发生错误: {ex.Message}");
return 1;
}
}
private static bool IsMatchSign(byte[] data, byte[] sign)
{
if (data.Length < sign.Length) return false;
for (int i = 0; i < sign.Length; i++)
{
if (data[i] != sign[i]) return false;
}
return true;
}
}
// ================= XXTEA 核心算法实现 =================
public static class XXTEA
{
private const uint DELTA = 0x9e3779b9;
public static byte[] Decrypt(byte[] data, byte[] key)
{
if (data == null || data.Length == 0) return data;
uint[] v = ToUInt32Array(data, false);
uint[] k = ToUInt32Array(FixKey(key), false);
v = Decrypt(v, k);
return ToByteArray(v, true);
}
private static uint[] Decrypt(uint[] v, uint[] k)
{
int n = v.Length - 1;
if (n < 1) return v;
uint z = v[n], y = v[0], sum, e;
int p, q = 6 + 52 / (n + 1);
unchecked
{
sum = (uint)(q * DELTA);
while (sum != 0)
{
e = (sum >> 2) & 3;
for (p = n; p > 0; p--)
{
z = v[p - 1];
y = v[p] -= ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (k[p & 3 ^ e] ^ z));
}
z = v[n];
y = v[0] -= ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (k[p & 3 ^ e] ^ z));
sum -= DELTA;
}
}
return v;
}
private static byte[] FixKey(byte[] key)
{
if (key.Length == 16) return key;
byte[] fixedKey = new byte[16];
Array.Copy(key, 0, fixedKey, 0, Math.Min(key.Length, 16));
return fixedKey;
}
private static uint[] ToUInt32Array(byte[] data, bool includeLength)
{
int length = data.Length;
int n = (((length & 3) == 0) ? (length >> 2) : ((length >> 2) + 1));
uint[] result;
if (includeLength)
{
result = new uint[n + 1];
result[n] = (uint)length;
}
else
{
result = new uint[n];
}
for (int i = 0; i < length; i++)
{
result[i >> 2] |= (uint)data[i] << ((i & 3) << 3);
}
return result;
}
private static byte[] ToByteArray(uint[] data, bool includeLength)
{
int n = data.Length << 2;
if (includeLength)
{
int m = (int)data[data.Length - 1];
n -= 4;
if (m < n - 3 || m > n) return null;
n = m;
}
byte[] result = new byte[n];
for (int i = 0; i < n; i++)
{
result[i] = (byte)(data[i >> 2] >> ((i & 3) << 3));
}
return result;
}
}
}
加强版
csharp
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Collections.Generic;
namespace XXTEATools
{
class XXTEADecryptor
{
// ================= 核心配置区域 =================
// SIGN: 加密文件的文件头标识(签名)
// KEY: XXTEA 加密的密钥
private static string SIGN = "4meJnPyl";
private static string KEY = "你的密钥";
// ================================================
static void Main(string[] args)
{
Console.OutputEncoding = Encoding.UTF8;
string inputPath = null;
string outputPath = null;
HashSet<string> filters = null;
// --- 命令行参数解析逻辑 ---
for (int i = 0; i < args.Length; i++)
{
switch (args[i].ToLower())
{
case "-i":
if (i + 1 < args.Length) inputPath = args[++i];
break;
case "-o":
if (i + 1 < args.Length) outputPath = args[++i];
break;
case "-f":
filters = new HashSet<string>();
while (i + 1 < args.Length && !args[i + 1].StartsWith("-"))
{
string ext = args[++i].ToLower();
filters.Add(ext.StartsWith(".") ? ext : "." + ext);
}
break;
case "-h":
case "--help":
PrintUsage();
return;
}
}
// 兼容性处理:如果没有开关且有参数,默认第一个为输入路径
if (string.IsNullOrEmpty(inputPath) && args.Length > 0 && !args[0].StartsWith("-"))
{
inputPath = args[0];
}
if (string.IsNullOrEmpty(inputPath))
{
PrintUsage();
return;
}
Execute(inputPath, outputPath, filters);
}
static void PrintUsage()
{
Console.WriteLine("\n==================================================");
Console.WriteLine(" XXTEA 全自动递归解密工具 ");
Console.WriteLine("==================================================\n");
Console.WriteLine("参数选项:");
Console.WriteLine(" -i <路径> 必需。指定要处理的文件或文件夹路径。");
Console.WriteLine(" -o <路径> 可选。指定输出根目录。若不带此参数,则直接覆盖原文件。");
Console.WriteLine(" -f <扩展名> 可选。指定后缀过滤,空格分隔。示例: -f png lua json");
Console.WriteLine(" -h 显示此帮助信息。");
Console.WriteLine("\n使用示例:");
Console.WriteLine(" 1. 原地还原当前目录所有文件:");
Console.WriteLine(" XXTEADecryptor.exe -i .");
Console.WriteLine("\n 2. 还原指定格式并输出到新文件夹 (保持子目录结构):");
Console.WriteLine(" XXTEADecryptor.exe -i D:\\Res -o D:\\Out -f png lua");
Console.WriteLine("\n 3. 处理单个文件并重命名输出:");
Console.WriteLine(" XXTEADecryptor.exe -i old.png -o new.png");
Console.WriteLine("--------------------------------------------------\n");
}
static void Execute(string inputPath, string outputPath, HashSet<string> filters)
{
bool isDirectory = Directory.Exists(inputPath);
bool isFile = File.Exists(inputPath);
if (!isDirectory && !isFile)
{
Console.WriteLine($"[错误] 路径不存在: {inputPath}");
return;
}
if (isDirectory)
{
Console.WriteLine($"[开始扫描] 目录: {Path.GetFullPath(inputPath)}");
var files = Directory.EnumerateFiles(inputPath, "*.*", SearchOption.AllDirectories);
foreach (var file in files)
{
string targetFile = file;
if (!string.IsNullOrEmpty(outputPath))
{
// 计算相对路径,以便在输出目录重建结构
string relativePath = Path.GetRelativePath(inputPath, file);
targetFile = Path.Combine(outputPath, relativePath);
}
ProcessFile(file, targetFile, filters);
}
}
else
{
string targetFile = outputPath ?? inputPath;
// 如果 -o 指向一个已存在的目录,则把文件放进去
if (!string.IsNullOrEmpty(outputPath) && Directory.Exists(outputPath))
{
targetFile = Path.Combine(outputPath, Path.GetFileName(inputPath));
}
ProcessFile(inputPath, targetFile, filters);
}
Console.WriteLine("\n[完成] 任务处理结束。");
}
static void ProcessFile(string sourceFile, string targetFile, HashSet<string> filters)
{
// 扩展名过滤
if (filters != null && !filters.Contains(Path.GetExtension(sourceFile).ToLower())) return;
try
{
byte[] signBytes = Encoding.UTF8.GetBytes(SIGN);
// 第一阶段:快速校验文件头(不读取全文,节省内存和IO)
using (FileStream fs = new FileStream(sourceFile, FileMode.Open, FileAccess.Read))
{
if (fs.Length < signBytes.Length) return;
byte[] head = new byte[signBytes.Length];
fs.Read(head, 0, head.Length);
for (int i = 0; i < signBytes.Length; i++)
{
if (head[i] != signBytes[i]) return; // 签名不匹配则跳过
}
}
// 第二阶段:读取并解密
byte[] fullData = File.ReadAllBytes(sourceFile);
byte[] encrypted = new byte[fullData.Length - signBytes.Length];
Buffer.BlockCopy(fullData, signBytes.Length, encrypted, 0, encrypted.Length);
byte[] decrypted = XXTEA.Decrypt(encrypted, Encoding.UTF8.GetBytes(KEY));
if (decrypted == null)
{
Console.WriteLine($"[异常] 解密失败 (Key可能不对): {Path.GetFileName(sourceFile)}");
return;
}
// 确保目标路径的父目录存在
string dir = Path.GetDirectoryName(targetFile);
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) Directory.CreateDirectory(dir);
File.WriteAllBytes(targetFile, decrypted);
Console.WriteLine($"[OK] {Path.GetFileName(sourceFile)}");
}
catch (Exception ex)
{
Console.WriteLine($"[跳过] {Path.GetFileName(sourceFile)}: {ex.Message}");
}
}
}
// ================= XXTEA 核心算法实现 =================
public static class XXTEA
{
private const uint DELTA = 0x9e3779b9;
public static byte[] Decrypt(byte[] data, byte[] key)
{
if (data == null || data.Length == 0) return data;
uint[] v = ToUInt32Array(data, false);
uint[] k = ToUInt32Array(FixKey(key), false);
v = Decrypt(v, k);
return ToByteArray(v, true);
}
private static uint[] Decrypt(uint[] v, uint[] k)
{
int n = v.Length - 1;
if (n < 1) return v;
uint z = v[n], y = v[0], sum, e;
int p, q = 6 + 52 / (n + 1);
unchecked
{
sum = (uint)(q * DELTA);
while (sum != 0)
{
e = (sum >> 2) & 3;
for (p = n; p > 0; p--)
{
z = v[p - 1];
y = v[p] -= ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (k[p & 3 ^ e] ^ z));
}
z = v[n];
y = v[0] -= ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (k[p & 3 ^ e] ^ z));
sum -= DELTA;
}
}
return v;
}
private static byte[] FixKey(byte[] key)
{
if (key.Length == 16) return key;
byte[] fixedKey = new byte[16];
Array.Copy(key, 0, fixedKey, 0, Math.Min(key.Length, 16));
return fixedKey;
}
private static uint[] ToUInt32Array(byte[] data, bool includeLength)
{
int length = data.Length;
int n = (((length & 3) == 0) ? (length >> 2) : ((length >> 2) + 1));
uint[] result = includeLength ? new uint[n + 1] : new uint[n];
if (includeLength) result[n] = (uint)length;
for (int i = 0; i < length; i++) result[i >> 2] |= (uint)data[i] << ((i & 3) << 3);
return result;
}
private static byte[] ToByteArray(uint[] data, bool includeLength)
{
int n = data.Length << 2;
if (includeLength)
{
int m = (int)data[data.Length - 1];
n -= 4;
if (m < n - 3 || m > n) return null;
n = m;
}
byte[] result = new byte[n];
for (int i = 0; i < n; i++) result[i] = (byte)(data[i >> 2] >> ((i & 3) << 3));
return result;
}
}
}
发布为单文件 EXE
AssemblyName=program 可以控制输出的 exe 文件名
bash
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:AssemblyName=program -o ./dist
bash
F:\program>dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:AssemblyName=program -o ./dist
正在确定要还原的项目...
已还原 F:\program\program.csproj (用时 2.59 秒)。
program -> F:\program\bin\Release\net8.0\win-x64\program.dll
program -> F:\program\dist\
得到文件:

测试
bash
F:\program\dist>program.exe 0.png 01.png
[成功] 0.png -> 0_decrypted.png