227 lines
7.8 KiB
Python
227 lines
7.8 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
解码 .dat 文件到 XML 文件的脚本
|
||
|
||
使用方法:
|
||
python decode_dat_to_xml.py <input_file> [--key KEY] [--output OUTPUT] [--batch]
|
||
|
||
示例:
|
||
python decode_dat_to_xml.py ./xml/examproblems.dat # 自动检测密钥
|
||
python decode_dat_to_xml.py ./xml/user.dat # 自动使用公钥
|
||
python decode_dat_to_xml.py ./xml/1000000000/examproblems_26.dat # 自动使用目录名作为密钥
|
||
python decode_dat_to_xml.py ./xml --batch # 批量处理整个目录
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import argparse
|
||
from Crypto.Cipher import DES
|
||
from Crypto.Util.Padding import unpad
|
||
|
||
# 项目中的固定公钥(用于 user.dat 文件)
|
||
PUBLIC_KEY = "1413201160"
|
||
|
||
def detect_key_from_path(file_path):
|
||
"""
|
||
从文件路径自动检测密钥
|
||
|
||
Args:
|
||
file_path: 文件路径
|
||
|
||
Returns:
|
||
检测到的密钥,如果无法检测则返回None
|
||
"""
|
||
# 标准化路径
|
||
normalized_path = os.path.normpath(file_path)
|
||
path_parts = normalized_path.split(os.sep)
|
||
|
||
# 检查是否是 user.dat 文件(使用公钥)
|
||
if os.path.basename(normalized_path) == "user.dat":
|
||
return PUBLIC_KEY
|
||
|
||
# 检查文件是否在用户目录下(例如: xml/1000000000/examproblems_26.dat)
|
||
# 用户目录通常是8-10位数字
|
||
# 遍历路径的每一部分,查找数字目录名
|
||
for i, part in enumerate(path_parts):
|
||
# 检查是否是用户ID目录(8-10位数字)
|
||
if part.isdigit() and 8 <= len(part) <= 10:
|
||
# 如果这是目录名,且文件在这个目录下,使用这个目录名作为密钥
|
||
# 例如: xml/1000000000/examproblems_26.dat
|
||
if i < len(path_parts) - 1: # 确保后面还有文件
|
||
return part
|
||
|
||
# 如果文件在 xml 根目录下,尝试从文件名或父目录推断
|
||
# 但这种情况通常需要手动指定密钥
|
||
return None
|
||
|
||
def decrypt_dat_file(input_file, key=None, output_file=None):
|
||
"""
|
||
解密 .dat 文件到 XML
|
||
|
||
Args:
|
||
input_file: 输入的 .dat 文件路径
|
||
key: DES 解密密钥(如果为None,则自动检测)
|
||
output_file: 输出的 XML 文件路径(如果为None,则自动生成)
|
||
"""
|
||
# 检查输入文件是否存在
|
||
if not os.path.exists(input_file):
|
||
print(f"错误: 文件不存在: {input_file}")
|
||
return False
|
||
|
||
# 如果没有提供密钥,尝试自动检测
|
||
if key is None:
|
||
detected_key = detect_key_from_path(input_file)
|
||
if detected_key:
|
||
key = detected_key
|
||
print(f"自动检测到密钥: {key}")
|
||
else:
|
||
print(f"无法自动检测密钥,请手动指定")
|
||
return False
|
||
|
||
# 处理密钥:如果长度小于10,补0到10位(与Java代码逻辑一致)
|
||
if len(key) < 10:
|
||
key = key + "0" * (10 - len(key))
|
||
|
||
# 确保密钥至少8位(DES要求)
|
||
if len(key) < 8:
|
||
key = key + "0" * (8 - len(key))
|
||
|
||
# 只取前8位作为DES密钥(DES密钥必须是8字节)
|
||
des_key = key[:8].encode('utf-8')
|
||
|
||
# IV 固定为 "12345678"(与Java代码一致)
|
||
iv = b"12345678"
|
||
|
||
try:
|
||
# 读取加密文件
|
||
with open(input_file, 'rb') as f:
|
||
encrypted_data = f.read()
|
||
|
||
if len(encrypted_data) == 0:
|
||
print("错误: 文件为空")
|
||
return False
|
||
|
||
# 创建 DES 解密器
|
||
cipher = DES.new(des_key, DES.MODE_CBC, iv)
|
||
|
||
# 解密数据
|
||
decrypted_data = cipher.decrypt(encrypted_data)
|
||
|
||
# 去除 PKCS5 填充
|
||
try:
|
||
decrypted_data = unpad(decrypted_data, 8)
|
||
except ValueError as e:
|
||
print(f"警告: 去除填充时出错,可能文件格式不正确: {e}")
|
||
# 尝试直接使用,可能是未填充的数据
|
||
pass
|
||
|
||
# 尝试解码为字符串(使用GBK编码,因为Java代码中使用了GBK)
|
||
try:
|
||
xml_content = decrypted_data.decode('GBK')
|
||
except UnicodeDecodeError:
|
||
# 如果GBK失败,尝试UTF-8
|
||
try:
|
||
xml_content = decrypted_data.decode('UTF-8')
|
||
except UnicodeDecodeError:
|
||
# 如果都失败,尝试使用错误处理
|
||
xml_content = decrypted_data.decode('GBK', errors='ignore')
|
||
print("警告: 使用GBK解码时遇到无法解码的字符,已忽略")
|
||
|
||
# 确定输出文件路径
|
||
if output_file is None:
|
||
# 自动生成输出文件名:将 .dat 扩展名改为 .xml
|
||
base_name = os.path.splitext(input_file)[0]
|
||
output_file = base_name + ".xml"
|
||
|
||
# 写入XML文件
|
||
with open(output_file, 'w', encoding='utf-8') as f:
|
||
f.write(xml_content)
|
||
|
||
print(f"成功! 解密后的XML文件已保存到: {output_file}")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"错误: 解密过程中出现异常: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def batch_process(directory):
|
||
"""
|
||
批量处理目录中的所有 .dat 文件
|
||
|
||
Args:
|
||
directory: 要处理的目录路径
|
||
"""
|
||
if not os.path.isdir(directory):
|
||
print(f"错误: {directory} 不是一个有效的目录")
|
||
return False
|
||
|
||
success_count = 0
|
||
fail_count = 0
|
||
|
||
# 递归查找所有 .dat 文件
|
||
for root, dirs, files in os.walk(directory):
|
||
for file in files:
|
||
if file.endswith('.dat'):
|
||
file_path = os.path.join(root, file)
|
||
print(f"\n处理文件: {file_path}")
|
||
if decrypt_dat_file(file_path):
|
||
success_count += 1
|
||
else:
|
||
fail_count += 1
|
||
|
||
print(f"\n批量处理完成: 成功 {success_count} 个, 失败 {fail_count} 个")
|
||
return fail_count == 0
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(
|
||
description='解码 .dat 文件到 XML 文件(自动检测密钥)',
|
||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||
epilog="""
|
||
示例:
|
||
%(prog)s ./xml/examproblems.dat # 自动检测密钥
|
||
%(prog)s ./xml/user.dat # 自动使用公钥 "1413201160"
|
||
%(prog)s ./xml/1000000000/examproblems_26.dat # 自动使用目录名作为密钥
|
||
%(prog)s ./xml/examproblems.dat --key "userid1234" # 手动指定密钥
|
||
%(prog)s ./xml --batch # 批量处理整个目录
|
||
"""
|
||
)
|
||
|
||
parser.add_argument('input_path', help='输入的 .dat 文件路径或目录路径(使用 --batch 时)')
|
||
parser.add_argument('--key', '-k', help='DES 解密密钥(用户ID,至少8位)。如果不指定,将自动检测')
|
||
parser.add_argument('--output', '-o', help='输出的 XML 文件路径(默认为输入文件名.xml)')
|
||
parser.add_argument('--batch', '-b', action='store_true', help='批量处理目录中的所有 .dat 文件')
|
||
|
||
args = parser.parse_args()
|
||
|
||
# 批量处理模式
|
||
if args.batch:
|
||
success = batch_process(args.input_path)
|
||
sys.exit(0 if success else 1)
|
||
|
||
# 单个文件处理模式
|
||
# 如果没有提供密钥,尝试自动检测,如果检测失败则提示用户输入
|
||
key = args.key
|
||
if key is None:
|
||
detected_key = detect_key_from_path(args.input_path)
|
||
if detected_key:
|
||
key = detected_key
|
||
print(f"自动检测到密钥: {key}")
|
||
else:
|
||
key = input("无法自动检测密钥,请输入解密密钥(用户ID): ").strip()
|
||
if not key:
|
||
print("错误: 密钥不能为空")
|
||
sys.exit(1)
|
||
|
||
# 执行解密
|
||
success = decrypt_dat_file(args.input_path, key, args.output)
|
||
|
||
if not success:
|
||
sys.exit(1)
|
||
|
||
if __name__ == '__main__':
|
||
main()
|
||
|