Skip to content

QQ 数据库解密与聊天记录导出 — 技术文档

🕒 Published at:

QQ 数据库解密与聊天记录导出 — 技术文档 ​

设备: 魅族 M5 Note (Android 7) 备份时间: 2026-08-02 状态: 手机已断开,NAS 为唯一副本 用途: 备份恢复、数据迁移、二次分析


目录 ​

  1. 数据库解密技术分析
  2. 聊天记录导出工具

1. 数据库解密技术分析 ​

1.1 背景 ​

魅族 M5 Note 手机 QQ 数据备份,包含多个 SQLite 数据库(数百 MB 级),覆盖全部消息记录、好友列表、群组信息等。

所有文本类字段(昵称、备注、群名)和数字类字段(QQ号、消息体、时间戳)均被加密,但全部基于 XOR,存在两套不同的加密方案。


1.2 密钥一:数字/消息类字段 ​

特征:加密后为纯 ASCII 可打印字符。

发现过程:

  1. 观察到消息正文和 QQ 号加密后都是 ASCII 可打印字符
  2. 猜测为逐字节 XOR,密钥长度通过已知明文推断
  3. 从备份中的 HTML 导出文件找到已知消息明文,对比其加密密文得到密钥
  4. 验证:解密所有数字字段,得到合法的 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差异
例字1xx xx xxxx xx yy仅第 3 字节不同
例字2xx xx xxxx xx yy仅第 3 字节不同

发现规律:只有 UTF-8 编码的第 3 字节被修改,第 1、2 字节完全相同。

Step 3 — 推导 XOR 密钥

对大量字符做第 3 字节的 XOR,按字符位置排序,得到:

字符位置012345...
XOR 值K0K1K2K0K1K2...

密钥长度 = 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 未破解的残留问题 ​

  1. 同用户多处昵称不一致:不同表更新时机不同,分别记录了不同时间点的昵称。
  2. 部分昵称含 emoji/特殊符号:约 27% 的昵称解密后含有 emoji、特殊符号,这是用户真实昵称。
  3. 群聊成员昵称:群消息只记录发送者 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 字段识别消息类型:

msgtypetype 值说明
-1000text文字消息,XOR 解密后 UTF-8 解码
-2000image图片消息,提取 UUID
-1035image+text图片+文字描述
-2022 / -2055 / -5021fileQQ 文件传输
-1051voice语音消息
-5040recall撤回消息(含原文)
-2011link链接消息,提取 URL
-5008 / -5017app小程序、应用卡片
-2017system入群、退群等系统消息
-2024 / -2026group_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 整合