添加用户消息记录接口;
This commit is contained in:
389
backend/scripts/decrypt_chat_data.py
Normal file
389
backend/scripts/decrypt_chat_data.py
Normal file
@@ -0,0 +1,389 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
企业微信会话存档解密脚本
|
||||
使用RSA私钥解密加密的会话数据
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import base64
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# 加载环境变量
|
||||
load_dotenv()
|
||||
|
||||
# 添加当前目录到路径,以便导入wework_ctypes
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from wework_ctypes import WeWorkFinanceSDK, WeWorkSDKError
|
||||
|
||||
def load_rsa_private_key(key_path: str) -> str:
|
||||
"""
|
||||
加载RSA私钥
|
||||
|
||||
Args:
|
||||
key_path: 私钥文件路径
|
||||
|
||||
Returns:
|
||||
私钥内容字符串
|
||||
"""
|
||||
try:
|
||||
with open(key_path, 'r') as f:
|
||||
key_content = f.read()
|
||||
return key_content
|
||||
except Exception as e:
|
||||
raise Exception(f"加载RSA私钥失败: {e}")
|
||||
|
||||
def rsa_decrypt_base64(encrypted_data_base64: str, private_key: str) -> str:
|
||||
"""
|
||||
使用RSA私钥解密Base64编码的数据
|
||||
|
||||
Args:
|
||||
encrypted_data_base64: Base64编码的加密数据
|
||||
private_key: RSA私钥字符串
|
||||
|
||||
Returns:
|
||||
解密后的字符串
|
||||
"""
|
||||
try:
|
||||
# 使用cryptography库进行RSA解密
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
import base64
|
||||
|
||||
# 加载私钥
|
||||
private_key_obj = serialization.load_pem_private_key(
|
||||
private_key.encode('utf-8'),
|
||||
password=None,
|
||||
backend=default_backend()
|
||||
)
|
||||
|
||||
# Base64解码
|
||||
encrypted_data = base64.b64decode(encrypted_data_base64)
|
||||
|
||||
# RSA解密 (使用PKCS1v15填充)
|
||||
decrypted_data = private_key_obj.decrypt(
|
||||
encrypted_data,
|
||||
padding.PKCS1v15()
|
||||
)
|
||||
|
||||
return decrypted_data.decode('utf-8')
|
||||
|
||||
except ImportError:
|
||||
raise Exception("需要安装cryptography库: pip install cryptography")
|
||||
except Exception as e:
|
||||
raise Exception(f"RSA解密失败: {e}")
|
||||
|
||||
def decrypt_chat_message(encrypt_random_key: str, encrypt_chat_msg: str,
|
||||
rsa_private_key_path: str, publickey_ver: int = None) -> tuple:
|
||||
"""
|
||||
解密聊天消息
|
||||
|
||||
Args:
|
||||
encrypt_random_key: 加密的随机密钥(Base64编码)
|
||||
encrypt_chat_msg: 加密的聊天消息
|
||||
rsa_private_key_path: RSA私钥文件路径或目录
|
||||
publickey_ver: publickey_ver版本号,用于动态匹配私钥
|
||||
|
||||
Returns:
|
||||
(解密后的消息字典, 实际使用的私钥路径)
|
||||
"""
|
||||
print(f"正在解密消息...")
|
||||
print(f"加密密钥长度: {len(encrypt_random_key)}")
|
||||
print(f"加密消息长度: {len(encrypt_chat_msg)}")
|
||||
|
||||
# 动态确定私钥路径
|
||||
actual_key_path = rsa_private_key_path
|
||||
|
||||
# 检查是否使用默认私钥路径(用户未指定自定义路径)
|
||||
# 通过检查路径是否指向默认文件来判断
|
||||
default_key_path = Path("cert/1_rsa.key")
|
||||
specified_key_path = Path(rsa_private_key_path)
|
||||
|
||||
# 如果使用默认私钥路径且提供了publickey_ver,则动态匹配
|
||||
if publickey_ver is not None and specified_key_path == default_key_path:
|
||||
# 构建动态私钥路径:cert/{publickey_ver}_rsa.key
|
||||
dynamic_key_path = f"cert/{publickey_ver}_rsa.key"
|
||||
dynamic_path = Path(dynamic_key_path)
|
||||
if dynamic_path.exists():
|
||||
actual_key_path = dynamic_key_path
|
||||
print(f"检测到publickey_ver={publickey_ver},使用动态私钥: {actual_key_path}")
|
||||
else:
|
||||
print(f"警告: 动态私钥文件不存在: {dynamic_key_path},使用默认私钥: {actual_key_path}")
|
||||
|
||||
# 1. 加载RSA私钥
|
||||
print(f"加载RSA私钥: {actual_key_path}")
|
||||
private_key = load_rsa_private_key(actual_key_path)
|
||||
print(f"✓ RSA私钥加载成功")
|
||||
|
||||
# 2. 使用RSA私钥解密encrypt_random_key得到encrypt_key
|
||||
print("正在使用RSA解密随机密钥...")
|
||||
try:
|
||||
encrypt_key = rsa_decrypt_base64(encrypt_random_key, private_key)
|
||||
print(f"✓ RSA解密成功,encrypt_key长度: {len(encrypt_key)}")
|
||||
except Exception as e:
|
||||
print(f"✗ RSA解密失败: {e}")
|
||||
# 尝试直接使用encrypt_random_key作为encrypt_key(某些情况下可能已经解密)
|
||||
print("尝试使用原始encrypt_random_key作为encrypt_key...")
|
||||
encrypt_key = encrypt_random_key
|
||||
|
||||
# 3. 使用SDK解密聊天消息
|
||||
print("正在使用SDK解密聊天消息...")
|
||||
try:
|
||||
# 初始化SDK(只需要解密功能,不需要corpid和secret)
|
||||
with WeWorkFinanceSDK("dummy", "dummy") as sdk:
|
||||
decrypted_msg = sdk.decrypt_data(encrypt_key, encrypt_chat_msg)
|
||||
print("✓ SDK解密成功")
|
||||
|
||||
# 尝试解析JSON
|
||||
try:
|
||||
result = json.loads(decrypted_msg)
|
||||
return result, actual_key_path
|
||||
except json.JSONDecodeError:
|
||||
# 如果不是JSON,返回原始文本
|
||||
return {"raw_message": decrypted_msg}, actual_key_path
|
||||
|
||||
except WeWorkSDKError as e:
|
||||
raise Exception(f"SDK解密失败: {e}")
|
||||
except Exception as e:
|
||||
raise Exception(f"解密过程出错: {e}")
|
||||
|
||||
def decrypt_file(input_file: str, rsa_private_key_path: str, output_file: str = None):
|
||||
"""
|
||||
解密文件中的聊天数据
|
||||
|
||||
Args:
|
||||
input_file: 输入文件路径(JSON格式)
|
||||
rsa_private_key_path: RSA私钥文件路径
|
||||
output_file: 输出文件路径(可选)
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("企业微信会话存档解密脚本")
|
||||
print("=" * 60)
|
||||
|
||||
# 检查输入文件
|
||||
input_path = Path(input_file)
|
||||
if not input_path.exists():
|
||||
print(f"错误: 输入文件不存在: {input_file}")
|
||||
return 1
|
||||
|
||||
# 检查RSA私钥文件(只检查用户指定的路径,动态路径在解密时检查)
|
||||
rsa_path = Path(rsa_private_key_path)
|
||||
if not rsa_path.exists():
|
||||
print(f"错误: 指定的RSA私钥文件不存在: {rsa_private_key_path}")
|
||||
return 1
|
||||
|
||||
print(f"输入文件: {input_path.absolute()}")
|
||||
print(f"指定私钥: {rsa_path.absolute()}")
|
||||
print("-" * 60)
|
||||
|
||||
# 用于记录实际使用的私钥路径
|
||||
actual_key_paths_used = set()
|
||||
|
||||
try:
|
||||
# 读取输入文件
|
||||
with open(input_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
print(f"文件加载成功")
|
||||
|
||||
# 检查数据格式
|
||||
if "chatdata" not in data:
|
||||
print("错误: 文件中没有chatdata字段")
|
||||
return 1
|
||||
|
||||
chatdata = data.get("chatdata", [])
|
||||
chat_count = len(chatdata)
|
||||
print(f"共发现 {chat_count} 条聊天记录")
|
||||
|
||||
if chat_count == 0:
|
||||
print("没有可解密的聊天记录")
|
||||
return 0
|
||||
|
||||
# 解密所有消息
|
||||
decrypted_messages = []
|
||||
success_count = 0
|
||||
fail_count = 0
|
||||
|
||||
for i, msg in enumerate(chatdata):
|
||||
|
||||
# print('msg', type(msg))
|
||||
|
||||
seq = msg.get("seq", i + 1)
|
||||
msgid = msg.get("msgid", f"msg_{i}")
|
||||
encrypt_random_key = msg.get("encrypt_random_key", "")
|
||||
encrypt_chat_msg = msg.get("encrypt_chat_msg", "")
|
||||
|
||||
print(f"\n[{i+1}/{chat_count}] 解密消息 seq={seq}, msgid={msgid[:20]}...")
|
||||
|
||||
if not encrypt_random_key or not encrypt_chat_msg:
|
||||
print(f" 跳过: 缺少加密数据")
|
||||
fail_count += 1
|
||||
decrypted_messages.append({
|
||||
"seq": seq,
|
||||
"msgid": msgid,
|
||||
"error": "缺少加密数据",
|
||||
"original": msg
|
||||
})
|
||||
continue
|
||||
|
||||
try:
|
||||
# 获取publickey_ver
|
||||
publickey_ver = msg.get("publickey_ver")
|
||||
|
||||
# 解密消息
|
||||
decrypted_msg, actual_key_path = decrypt_chat_message(
|
||||
encrypt_random_key,
|
||||
encrypt_chat_msg,
|
||||
rsa_private_key_path,
|
||||
publickey_ver
|
||||
)
|
||||
|
||||
# 记录实际使用的私钥路径
|
||||
actual_key_paths_used.add(actual_key_path)
|
||||
|
||||
# 添加元数据
|
||||
if isinstance(decrypted_msg, dict):
|
||||
decrypted_msg["_metadata"] = {
|
||||
"seq": seq,
|
||||
"msgid": msgid,
|
||||
"decrypt_time": datetime.now().isoformat(),
|
||||
"original_has_encryption": True
|
||||
}
|
||||
|
||||
decrypted_messages.append({
|
||||
"seq": seq,
|
||||
"msgid": msgid,
|
||||
"decrypted": decrypted_msg,
|
||||
"original_summary": {
|
||||
"publickey_ver": msg.get("publickey_ver"),
|
||||
"encrypt_key_len": len(encrypt_random_key),
|
||||
"encrypt_msg_len": len(encrypt_chat_msg)
|
||||
}
|
||||
})
|
||||
|
||||
success_count += 1
|
||||
print(f" ✓ 解密成功")
|
||||
|
||||
# 显示解密后的消息摘要
|
||||
if isinstance(decrypted_msg, dict):
|
||||
msg_type = decrypted_msg.get("msgtype", "unknown")
|
||||
from_user = decrypted_msg.get("from", "unknown")
|
||||
to_user = decrypted_msg.get("tolist", ['unknown'])[0]
|
||||
if isinstance(decrypted_msg.get("tolist"), list) and len(decrypted_msg.get("tolist")) > 1:
|
||||
to_user += f" 等 {len(decrypted_msg.get('tolist'))} 人"
|
||||
|
||||
print(f" 类型: {msg_type}, 来自: {from_user}, 发送给: {to_user}")
|
||||
|
||||
# 成功解密后继续处理下一条消息
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
print(f" ✗ 解密失败: {e}")
|
||||
fail_count += 1
|
||||
decrypted_messages.append({
|
||||
"seq": seq,
|
||||
"msgid": msgid,
|
||||
"error": str(e),
|
||||
"original": msg
|
||||
})
|
||||
|
||||
print("\n" + "-" * 60)
|
||||
print(f"解密完成: 成功 {success_count} 条, 失败 {fail_count} 条")
|
||||
|
||||
# 显示实际使用的私钥信息
|
||||
if actual_key_paths_used:
|
||||
print(f"实际使用的私钥文件:")
|
||||
for key_path in sorted(actual_key_paths_used):
|
||||
print(f" - {key_path}")
|
||||
|
||||
# 生成输出文件名
|
||||
if output_file is None:
|
||||
timestamp = datetime.now().strftime("%y%m%d_%H%M%S")
|
||||
output_file = f"data/decrypted_{input_path.stem}_{timestamp}.json"
|
||||
|
||||
output_path = Path(output_file)
|
||||
|
||||
# 准备输出数据
|
||||
output_data = {
|
||||
"metadata": {
|
||||
"input_file": str(input_path.absolute()),
|
||||
"decrypt_time": datetime.now().isoformat(),
|
||||
"total_messages": chat_count,
|
||||
"successful_decrypts": success_count,
|
||||
"failed_decrypts": fail_count,
|
||||
"specified_key": str(rsa_path.absolute()),
|
||||
"actual_keys_used": sorted([str(Path(p).absolute()) for p in actual_key_paths_used])
|
||||
},
|
||||
"decrypted_messages": decrypted_messages,
|
||||
"original_metadata": data.get("_metadata", {})
|
||||
}
|
||||
|
||||
# 保存解密结果
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(output_data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"解密结果已保存到: {output_path.absolute()}")
|
||||
|
||||
# 显示一些解密后的消息示例
|
||||
if success_count > 0:
|
||||
print("\n解密消息示例:")
|
||||
for i, msg in enumerate(decrypted_messages[:3]): # 显示前3条
|
||||
if "decrypted" in msg:
|
||||
decrypted = msg["decrypted"]
|
||||
if isinstance(decrypted, dict):
|
||||
seq = msg.get("seq", "N/A")
|
||||
msg_type = decrypted.get("msgtype", "unknown")
|
||||
content = decrypted.get("content", "无内容")[:100]
|
||||
print(f" [{i+1}] seq={seq}, 类型={msg_type}")
|
||||
print(f" 内容: {content}...")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("解密完成")
|
||||
|
||||
return 0
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"JSON解析错误: {e}")
|
||||
return 1
|
||||
except Exception as e:
|
||||
print(f"解密过程出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="企业微信会话存档解密脚本",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
使用示例:
|
||||
# 解密data目录下的文件
|
||||
%(prog)s data/chat_0_100_251203.json
|
||||
|
||||
# 指定RSA私钥路径
|
||||
%(prog)s data/chat_0_100_251203.json --key /path/to/private_key.pem
|
||||
|
||||
# 指定输出文件
|
||||
%(prog)s data/chat_0_100_251203.json --output decrypted_result.json
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument('input_file', help='输入文件路径(JSON格式)')
|
||||
parser.add_argument('--key', '-k', default='cert/1_rsa.key',
|
||||
help='RSA私钥文件路径,默认: cert/1_rsa.key')
|
||||
parser.add_argument('--output', '-o', help='输出文件路径,默认自动生成')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
return decrypt_file(args.input_file, args.key, args.output)
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user