diff --git a/README.md b/README.md index a5231bc..10df6ce 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@
更新日志(点击展开): +* 2023.11.27 解决相对导入包的问题,完善错误提示 * 2023.11.25 聊天记录查看工具bootstrap更换国内cdn * 2023.11.22 添加all命令中解密错误数据日志写入文件,修复部分bug * 2023.11.16 增加聊天记录导出为html @@ -160,13 +161,13 @@ PyWxDump ## 1. 安装 -### 1.1 从pypi安装 +### 1.1 从pypi安装(安装稳定版) ```shell script pip install pywxdump ``` -### 1.2 从源码安装 +### 1.2 从源码安装(安装最新版)
点击展开 diff --git a/pywxdump/__init__.py b/pywxdump/__init__.py index 92a1c8c..aa34192 100644 --- a/pywxdump/__init__.py +++ b/pywxdump/__init__.py @@ -11,6 +11,8 @@ from .wx_info.get_wx_db import get_wechat_db from .decrypted.decrypt import batch_decrypt, decrypt from .decrypted.get_wx_decrypted_db import all_decrypt, merge_copy_msg_db, merge_msg_db, merge_media_msg_db from .analyse.parse import read_img_dat, read_emoji, decompress_CompressContent, read_audio_buf, read_audio, parse_xml_string +from .show_chat import app_show_chat, get_user_list, export + import os,json VERSION_LIST_PATH = os.path.join(os.path.dirname(__file__), "version_list.json") diff --git a/pywxdump/command.py b/pywxdump/command.py index e9a67a5..40d2a1d 100644 --- a/pywxdump/command.py +++ b/pywxdump/command.py @@ -10,7 +10,7 @@ import importlib.metadata import sys import textwrap -from . import * +from pywxdump import * # version_list_path = os.path.join(os.path.dirname(__file__), "version_list.json") @@ -142,7 +142,6 @@ class MainShowChatRecords(): try: from flask import Flask, request, jsonify, render_template, g import logging - from .show_chat.main_window import app_show_chat, get_user_list except Exception as e: print(e) print("[-] 请安装flask( pip install flask )") @@ -177,7 +176,7 @@ class MainExportChatRecords(): self.mode = "export" # 添加 'decrypt' 子命令解析器 sb_decrypt = parser.add_parser(self.mode, help="聊天记录导出为html[需要安装flask]") - sb_decrypt.add_argument("-u", "--username", type=str, help="微信账号", required=True, metavar="") + sb_decrypt.add_argument("-u", "--username", type=str, help="微信账号(聊天对象账号)", required=True, metavar="") sb_decrypt.add_argument("-o", "--outpath", type=str, help="导出路径", required=True, metavar="") sb_decrypt.add_argument("-msg", "--msg_path", type=str, help="解密后的 MSG.db 的路径", required=True, metavar="") @@ -195,10 +194,9 @@ class MainExportChatRecords(): try: from flask import Flask, request, jsonify, render_template, g import logging - from .show_chat.main_window import app_show_chat, get_user_list, export except Exception as e: print(e) - print("[-] 请安装flask( pip install flask )") + print("[-] 请安装flask( pip install flask)") return if not os.path.exists(args.msg_path) or not os.path.exists(args.micro_path) or not os.path.exists( diff --git a/pywxdump/decrypted/decrypt.py b/pywxdump/decrypted/decrypt.py index 25b419a..1e1d820 100644 --- a/pywxdump/decrypted/decrypt.py +++ b/pywxdump/decrypted/decrypt.py @@ -53,6 +53,8 @@ def decrypt(key: str, db_path, out_path): salt = blist[:16] byteKey = hashlib.pbkdf2_hmac("sha1", password, salt, DEFAULT_ITER, KEY_SIZE) first = blist[16:DEFAULT_PAGESIZE] + if len(salt) != 16: + return False, f"[-] db_path:'{db_path}' File Error!" mac_salt = bytes([(salt[i] ^ 58) for i in range(16)]) mac_key = hashlib.pbkdf2_hmac("sha1", byteKey, mac_salt, 2, KEY_SIZE) @@ -162,6 +164,50 @@ def batch_decrypt(key: str, db_path: Union[str, List[str]], out_path: str, is_lo return True, result +def encrypt(key: str, db_path, out_path): + """ + 通过密钥加密数据库 + :param key: 密钥 64位16进制字符串 + :param db_path: 待加密的数据库路径(必须是文件) + :param out_path: 加密后的数据库输出路径(必须是文件) + :return: + """ + if not os.path.exists(db_path) or not os.path.isfile(db_path): + return False, f"[-] db_path:'{db_path}' File not found!" + if not os.path.exists(os.path.dirname(out_path)): + return False, f"[-] out_path:'{out_path}' File not found!" + + if len(key) != 64: + return False, f"[-] key:'{key}' Len Error!" + + password = bytes.fromhex(key.strip()) + with open(db_path, "rb") as file: + blist = file.read() + + salt = os.urandom(16) # 生成随机盐值 + byteKey = hashlib.pbkdf2_hmac("sha1", password, salt, DEFAULT_ITER, KEY_SIZE) + + # 计算消息认证码 + mac_salt = bytes([(salt[i] ^ 58) for i in range(16)]) + mac_key = hashlib.pbkdf2_hmac("sha1", byteKey, mac_salt, 2, KEY_SIZE) + hash_mac = hmac.new(mac_key, blist[:-32], hashlib.sha1) + hash_mac.update(b'\x01\x00\x00\x00') + mac_digest = hash_mac.digest() + + newblist = [blist[i:i + DEFAULT_PAGESIZE] for i in range(DEFAULT_PAGESIZE, len(blist), DEFAULT_PAGESIZE)] + + with open(out_path, "wb") as enFile: + enFile.write(salt) # 写入盐值 + enFile.write(mac_digest) # 写入消息认证码 + + for i in newblist: + t = AES.new(byteKey, AES.MODE_CBC, os.urandom(16)) # 生成随机的初始向量 + encrypted = t.encrypt(i) # 加密数据块 + enFile.write(encrypted) + + return True, [db_path, out_path, key] + + if __name__ == '__main__': # 创建命令行参数解析器 parser = argparse.ArgumentParser() @@ -179,9 +225,4 @@ if __name__ == '__main__': out_path = args.out_path # 调用 decrypt 函数,并传入参数 - result = batch_decrypt(key, db_path, out_path) - for i in result: - if isinstance(i, str): - print(i) - else: - print(f'[+] "{i[1]}" -> "{i[2]}"') + result = batch_decrypt(key, db_path, out_path, is_logging=True) diff --git a/pywxdump/show_chat/__init__.py b/pywxdump/show_chat/__init__.py index 2ae4528..2a14789 100644 --- a/pywxdump/show_chat/__init__.py +++ b/pywxdump/show_chat/__init__.py @@ -5,3 +5,4 @@ # Author: xaoyaoo # Date: 2023/11/10 # ------------------------------------------------------------------------------- +from .main_window import app_show_chat, get_user_list, export diff --git a/pywxdump/show_chat/main_window.py b/pywxdump/show_chat/main_window.py index 92b0478..aee6496 100644 --- a/pywxdump/show_chat/main_window.py +++ b/pywxdump/show_chat/main_window.py @@ -184,6 +184,8 @@ def export_html(user, outpath, MSG_ALL_db_path, MediaMSG_all_db_path, FileStorag username = user.get("username", "") chatCount = user.get("chat_count", 0) + if chatCount == 0: + return False, "没有聊天记录" for i in range(0, chatCount, page_size): start_index = i @@ -191,7 +193,8 @@ def export_html(user, outpath, MSG_ALL_db_path, MediaMSG_all_db_path, FileStorag FileStorage_path) if len(data) == 0: break - with open(f"{outpath}/{name_save}_{int(i / page_size)}.html", "w", encoding="utf-8") as f: + save_path = os.path.join(outpath, f"{name_save}_{int(i / page_size)}.html") + with open(save_path, "w", encoding="utf-8") as f: f.write(render_template("chat.html", msgs=data)) return True, f"导出成功{outpath}"