QQ 数据库解密与聊天记录导出 --- 技术文档
设备 : 魅族 M5 Note (Android 7)
备份时间 : 2026-08-02
状态 : 手机已断开,NAS 为唯一副本
用途: 备份恢复、数据迁移、二次分析
目录
1. 数据库解密技术分析
1.1 背景
魅族 M5 Note 手机 QQ 数据备份,包含多个 SQLite 数据库(数百 MB 级),覆盖全部消息记录、好友列表、群组信息等。
所有文本类字段(昵称、备注、群名)和数字类字段(QQ号、消息体、时间戳)均被加密,但全部基于 XOR,存在两套不同的加密方案。
1.2 密钥一:数字/消息类字段
特征:加密后为纯 ASCII 可打印字符。
发现过程:
- 观察到消息正文和 QQ 号加密后都是 ASCII 可打印字符
- 猜测为逐字节 XOR,密钥长度通过已知明文推断
- 从备份中的 HTML 导出文件找到已知消息明文,对比其加密密文得到密钥
- 验证:解密所有数字字段,得到合法的 QQ 号码序列
加密公式:
密文[i] = 明文[i] XOR Key[i % KeyLen]
- 密钥长度固定(17 位),循环使用
- 适用字段:消息体、QQ 号、时间戳等所有 latin1/ASCII 编码的字段
Python 实现:
python
KC = b'864499037161840'
def dec_str(s):
"""解密数字/消息类字段(密钥一)"""
if not s: return ''
return bytes(x ^ KC[i % len(KC)] for i, x in enumerate(s.encode('latin1'))) \
.decode('latin1').rstrip(chr(0))
1.3 密钥二:昵称/备注类字段
特征:加密后看起来像正常的中文字符(合法的 UTF-8),不像密钥一那样全是 ASCII。
发现过程:
Step 1 --- 识别编码方式
加密后的昵称仍是合法 UTF-8,推断加密是在 UTF-8 编码层面操作的。
Step 2 --- 对比密文与明文的字节差异
取已知的密文(数据库中)和对应的明文(HTML 导出文件名),逐字节比较:
| 字符 | 明文 UTF-8 | 密文 UTF-8 | 差异 |
|---|---|---|---|
| 例字1 | xx xx xx |
xx xx yy |
仅第 3 字节不同 |
| 例字2 | xx xx xx |
xx xx yy |
仅第 3 字节不同 |
发现规律:只有 UTF-8 编码的第 3 字节被修改,第 1、2 字节完全相同。
Step 3 --- 推导 XOR 密钥
对大量字符做第 3 字节的 XOR,按字符位置排序,得到:
| 字符位置 | 0 | 1 | 2 | 3 | 4 | 5 | ... |
|---|---|---|---|---|---|---|---|
| XOR 值 | K0 | K1 | K2 | K0 | K1 | K2 | ... |
密钥长度 = 3 ,三个字节 [0x38, 0x36, 0x34] 按字符位置循环使用。
Step 4 --- 为什么只改第 3 字节
UTF-8 中文编码为 3 字节:1110xxxx 10xxxxxx 10xxxxxx
- 第 1 字节:固定前缀
1110+ 编码高位 - 第 2 字节:固定前缀
10+ 编码中位 - 第 3 字节:固定前缀
10+ 编码低位(变化最大的部分)
只修改第 3 字节的效果:
- 字符仍保持 3 字节 UTF-8 格式,仍是合法中文
- 但字符编码范围大幅偏移,变成另一个不相关的字
- ASCII 字符不受影响(只有 1 字节,没有第 3 字节)
Python 实现:
python
def dec_name(s):
"""解密昵称/备注类字段(密钥二)"""
if not s: return ''
b = s.encode('utf-8')
result = bytearray(b)
cycle = [0x38, 0x36, 0x34]
for i in range(2, len(result), 3):
char_pos = (i - 2) // 3
result[i] ^= cycle[char_pos % 3]
return result.decode('utf-8', errors='replace')
1.4 为什么这套加密能成功破解
| 要素 | 说明 | 影响 |
|---|---|---|
| 已知明文 | HTML 导出文件含明文昵称,提供多组 (密文, 明文) 对 | 已知明文攻击成功 |
| 字节位置固定 | UTF-8 中文编码结构固定,第 3 字节总是第 3 字节 | 能精确定位被加密字节 |
| XOR 密钥固定 | 同一份数据使用相同密钥,无随机化 | 一次性破解可复用 |
| 密钥极短 | 密钥一 17 字节,密钥二仅 3 字节 | 少量样本即可推导 |
| 无完整性校验 | 修改后仍是合法 UTF-8,不会触发异常 | 暴力破解可行 |
安全评估:这是一个非常弱的加密方案,本质上是单表替换密码的变体,无密钥分发/轮换,无加密标识。设计目标可能是防君子不防小人,或者仅仅是防数据库直接以明文存储。
1.5 未破解的残留问题
- 同用户多处昵称不一致:不同表更新时机不同,分别记录了不同时间点的昵称。
- 部分昵称含 emoji/特殊符号:约 27% 的昵称解密后含有 emoji、特殊符号,这是用户真实昵称。
- 群聊成员昵称:群消息只记录发送者 QQ 号,群昵称存储在独立表中,需交叉关联。
1.6 附录:技术细节
UTF-8 第 3 字节 XOR 的完整逻辑:
字符 C 的 UTF-8 编码:B1 B2 B3
加密后:B1 B2 (B3 XOR K[i % 3])
其中 K = [0x38, 0x36, 0x34],i 从 0 开始
ASCII 字符不受影响的原因:
ASCII 字符的 UTF-8 编码为单字节(值 < 0x80),没有第 3 字节:
'A'=0x41(1 字节)→ 不满足处理条件 → 原样输出 ✅'1'=0x31(1 字节)→ 同上 ✅
这意味着所有英文昵称、数字昵称在解密时不需要额外处理。
2. 聊天记录导出工具
2.1 概览
导出聊天记录_v2.py 是一个 Python 3 脚本,用于从 QQ 数据库提取聊天记录,输出 JSONL 格式文件。
输入 : /tmp/db_scan/databases/*.db
输出 : /mnt/shared/QQ_备份_20260802/exports_v2/account_{QQ号}/
输出结构:
exports_v2/
└── account_{QQ号}/
├── private/
│ ├── 备注(昵称)-{对方QQ}.jsonl
│ └── ...
└── group/
├── {群QQ}.jsonl
└── ...
2.2 JSONL 消息格式
每行一个消息,字段:
| 字段 | 说明 |
|---|---|
t |
时间,格式 YYYY-MM-DD HH:MM:SS(北京时间 UTC+8) |
from |
发送者(自己显示为"你") |
qq |
发送者 QQ 号 |
self |
是否为自己发送 |
type |
消息类型(见下表) |
text |
文本内容(文字消息) |
uuid |
图片 UUID(32 位 hex,可在媒体目录查找对应文件) |
urls |
链接地址列表(链接消息) |
group |
群号(群聊消息才有) |
2.3 消息类型解析
脚本通过 msgtype 字段识别消息类型:
| msgtype | type 值 | 说明 |
|---|---|---|
| -1000 | text |
文字消息,XOR 解密后 UTF-8 解码 |
| -2000 | image |
图片消息,提取 UUID |
| -1035 | image+text |
图片+文字描述 |
| -2022 / -2055 / -5021 | file |
QQ 文件传输 |
| -1051 | voice |
语音消息 |
| -5040 | recall |
撤回消息(含原文) |
| -2011 | link |
链接消息,提取 URL |
| -5008 / -5017 | app |
小程序、应用卡片 |
| -2017 | system |
入群、退群等系统消息 |
| -2024 / -2026 | group_notice |
群公告更新 |
2.4 处理流程
1. 扫描所有 .db 文件
2. 构建昵称表
- 从 Friends 表提取 QQ号 → 昵称 / 备注
- 从 CardProfilev4 表提取历史昵称
3. 对每个账号数据库:
a. 读取所有 mr_friend_*_New / mr_troop_*_New 表
b. 对每条消息:
- 解密发送者 QQ(密钥一)
- 解析消息体(根据 msgtype)
- 查找发送者昵称(优先用备注,其次用昵称)
- 转换时间戳(UTC → 北京时间 UTC+8)
c. 同时处理 slowtable 数据库(延迟消息)
d. 按对方/群分组,写 JSONL 文件
4. 文件名规则:备注(昵称)-{QQ号}.jsonl(特殊字符已过滤)
2.5 源码结构
#!/usr/bin/env python3
├── 密钥常量 KC = b'864499037161840'
├── dec_str() # 密钥一解密(数字/消息)
├── dec_name() # 密钥二解密(昵称/备注)
├── safe_fn() # 文件名安全化(过滤特殊字符)
├── parse_msg() # 消息体解析(24 种 msgtype)
├── build_nicks() # 构建昵称表
├── resolve() # QQ号 → 昵称
├── process() # 处理单个数据库
├── 主流程 # 扫描 → 构建昵称表 → 逐个处理
2.6 运行方法
bash
python3 导出聊天记录_v2.py
前置条件:Python 3,数据库已提取到 /tmp/db_scan/databases/,标准库即可,无需第三方包。
附录
A. 两个密钥速查表
| 密钥 | 字节 | 适用 | 加密方式 |
|---|---|---|---|
| 密钥一 | 864499037161840(17 字节) |
QQ号、消息体、时间戳 | 逐字节 XOR |
| 密钥二 | [0x38, 0x36, 0x34](3 字节) |
昵称、备注、群名 | UTF-8 第 3 字节 XOR,按字符位置循环 |
B. UUID 与媒体文件对应
图片消息中 uuid 字段(32 位 hex)对应媒体目录中的文件:
Cache_{uuid}.jpg或Cache_{uuid}(无后缀)- 可在
聊天缓存/目录中查找
文档生成时间: 2026-08-17
基于 QQ数据库解密技术分析.md + 导出聊天记录_v2.py 整合