
钉钉本地数据库是加密的,但 v2 的密钥就写在目录名上。我实测解开 468 个会话;真正的硬仗是 1MB 的 WAL,以及一次连续 7 天的假阴性。
2026 年 7 月 29 日晚上,项目部的宿舍里,我刚把微信 4.1 那场硬仗的战报收进抽屉,转向这条数据链路的第二块阵地:钉钉。
目标很朴素:项目工作群的通知和文件,每天自动读、自动按日期归档,躺着能查。前提只有一个,读得到钉钉存在我自己电脑上的本地数据库。库是加密的。
我是野生码农,39 岁,程序员。这是"AI 解密攻防"系列的第二篇。上一篇微信,8 轮断点、794 次命中,硬抠出来的。轮到钉钉,我以为怎么也得再熬一晚。
结果像坐过山车。
一、排雷:五条路,先死三条半
程序员的老习惯,先侦察再动手。我把能想到的路全列出来,一条条试:
| 路线 | 结果 |
|---|---|
| 官方 API | 要企业管理员建应用,我在群里只是普通成员,出局 |
| 模拟鼠标点界面 | 微信时代就否掉了,脆弱、丢人 |
| 本机 MCP 服务 | 空欢喜,只会开会议 |
| 公开解密资料前两篇 | 针对 v3,我机器上是 v2,版本对不上 |
| 第三份资料 | 明确支持 v2,成了 |
中间那条最戏剧。AI 翻钉钉目录时发现,钉钉居然在本机 8440 端口跑着一个标准的 MCP 服务。MCP 是让 AI 按标准协议调用外部工具的接口规范,发现它,等于发现官方在本地留了扇门。
我以为中了头奖。
连上去列工具:创建会议、查会议状态、查会议 ID。只会开会议。消息?没有。文件?没有。
头奖是张过期的彩票。
接着是版本低谷。网上能查到的解密资料,前两篇全是针对 v3 的,我电脑上的账号目录后缀是 _v2。版本对不上,照抄就是死路。
二、反转:钥匙和锁,挂在同一扇门上
反转来自第三份资料,它明确支持 v2,而且给出了密钥算法:v2 的密钥就是账号 UID 处理后的结果,而 UID 就写在目录名上。
全部密钥逻辑,三行:
import hashlib
from Crypto.Cipher import AES
def make_key(uid: str) -> bytes:
# uid 就是账号目录名里 "_v2" 前面的那一段
return hashlib.md5(uid.encode()).hexdigest()[:16].encode()
cipher = AES.new(make_key(uid), AES.MODE_ECB)
MD5 就是把任意字符串压成 32 位十六进制指纹的哈希函数。钉钉把你的账号 ID 做了个 MD5,取前 16 位,就是加密你全部聊天记录的钥匙。
钥匙和锁,挂在同一扇门上。
解密动作也朴素。数据库按 4096 字节分页,逐页做 AES-128-ECB 解密,不足一页的尾部原样保留。ECB 是最简单的分组加密模式,每页各解各的,页与页不串联:
def decrypt_pages(data: bytes, cipher) -> bytes:
out = bytearray(data)
full = len(data) // 4096 * 4096
for off in range(0, full, 4096):
out[off:off + 4096] = cipher.decrypt(bytes(out[off:off + 4096]))
return bytes(out)
前一天,微信 4.1 是 8 轮断点、794 次命中硬抠出来的。同是大厂出品,防盗门和挂锁的差距。
但微信那一晚给我留下一个条件反射:解密成没成,不能看程序报不报错,要看钢印。微信的钢印是 HMAC,钉钉这边更直接,看文件头:
if not plain.startswith(b"SQLite format 3\x00"):
sys.exit("解密失败(首 16 字节不是 SQLite 头)")
SQLite format 3 是每个 SQLite 数据库出生就带的魔数,相当于文件的胎记。解对了它才出现,解错了满篇乱码。钢印对上,库应声而开:468 个会话。
程序不报错≠成功,数学验证才算数。这条是微信篇花一下午买的,这次三秒钟用上了。
三、真正的硬仗:1MB 的 WAL
库开了,我以为收工了。拉到最新消息一看,时间是旧的。
原因很快查明:最新消息不在数据库本体里,在 WAL 里。WAL 是 SQLite 的预写日志,相当于主库的暂存账本,新数据先记进账本,攒够一批才誊进主库。我这个账本有 1MB,最近的聊天全在里面。
麻烦在校验。WAL 里每一帧,也就是每一笔账,都盖着 CRC 校验的骑缝章,而且章是对着密文盖的。你把密文翻成明文,章就全花了,数据库一看章不对,整本账作废,当没看见。
没有现成答案。AI 按 SQLite 公开的 WAL 格式,把校验链从头到尾逐帧重算。WAL 文件头里有个魔数,0x377F0682 还是 0x377F0683,差一个字节,决定校验和按小端还是大端算。核心是两个 32 位累加器 s1、s2,每 8 字节滚动一次,帧头接帧数据一路滚到底:
def _wal_cksum(data, s1, s2, little):
fmt = "<" if little else ">"
for i in range(0, len(data), 8):
x0, x1 = struct.unpack_from(fmt + "II", data, i)
s1 = (s1 + x0 + s2) & 0xFFFFFFFF
s2 = (s2 + x1 + s1) & 0xFFFFFFFF
return s1, s2
每一帧的流程:读帧头和 4096 字节页数据,页数据 ECB 解密,帧头前 8 字节滚进校验链,明文页滚进校验链,把新的 s1、s2 盖回帧头的校验位,明文写回。一帧出错,链就断,sqlite 会整本丢弃 WAL。
盖章通过。库里最新消息刷新到当天早上 7 点 20 分。活数据,到手。
四、找文件路径翻了四层,最后在任务表里抄了家
消息到手,还差文件。群里发的文件,本机下载过的那些,落盘路径记在哪?我翻了四层。
明文配置文件,没有。解密的配置库,568 行键值对逐条扫,没有。浏览器内核存储,没有。最后在文件传输任务表里,一次抄了家:每次"下载/另存为"的文件名、大小、落盘路径,全量历史都在。客户端自己的记账习惯,比配置项诚实。
查到路径,归档逻辑就一句话:任务表里状态完成且本地文件还在的,复制归档;从没下载过的,记账待补,不硬拉。
五、管道成型:每天早上 06:52 的闹钟
整条管道最后拼成四步:只读复制数据库快照、解密、增量读消息、归档文件。
增量这块多说两句。钉钉的消息表按 tbmsg_000 到 tbmsg_127 拆成 128 个分片,群消息落在哪几片,先全库扫一遍记下来;之后每次只查水位线以上的新消息,水位线就是上次读到的最大消息 ID,存在一个本地 JSON 里。发送人 uid 自动解析,群昵称优先,其次全局昵称。
战果:群消息 426 条全量回填,2025 年 2 月到 2026 年 7 月,按天排好;群里发过的 380 个文件,149 个当场归档,剩下 231 个我从没下载过,记账待补。Windows 计划任务每天早上 06:52 自动跑。
躺着能查了。我以为这事翻篇了。
六、第二幕:连续 7 天的假阴性
8 月 10 日,我发现归档停在 7 月 31 日。
三层验证定位:解密库里最新消息 7-31 15:13;源库文件 8-3 之后一周没动过;钉钉桌面客户端根本没在运行。
根因一句话:解密管线只读本地库,本地库只在客户端登录时同步。客户端没登录,管道每天 06:52 忠实地解密一份过期数据库,然后报"无新消息"。技术上句句属实,业务上是连续 7 天的假阴性。
设计缺陷也很清楚:我只校验了"解密成功",也就是管道活着;从不校验"源库新鲜度",也就是数据活着。
[ok] 报的是工具活着,不是数据活着。
整改两条。我侧,每天上班先开钉钉客户端;工具侧,加一条源库超过 24 小时没更新就告警。原则沉淀下来:管道活性检查必须配套数据时效检查,报 ok 的前提是"源数据是新的"。
七、四个判断点,谁再踩谁冤
- 先判版本,再选武器。目录后缀三秒定版本,比抄错答案折腾一天强。
- 解密成功要有钢印。文件头必须是 SQLite 魔数,最新消息时间必须刷新到当下。
- 别忽略 WAL。校验链要解密后逐帧重算,不然你读到的永远是旧数据。
- 找不到的东西,去任务表、日志表里找。客户端自己的记账,比配置项诚实。
边界还是那句,系列里每篇都说一遍:全程只在自己电脑、自己账号上操作,只读不写,产物不出本机。读懂自己的数据,天经地义。单位、群名、同事的事一个字不写,上面所有代码都是脱敏后的关键片段,流程只指向一个目的,读自己的工作数据。
有时候门根本没锁,你只需要先把"它一定锁得很死"的预设放下。当然,前提是你得先打赢过一场硬仗,才配享受这种轻松。
下一篇,印象笔记 token 自动换新。token 会过期,自动化断在半路的坑我踩了一圈,8 月 16 日发。
FAQ
问:钉钉 v2 这个算法,现在还能用吗? 我 7 月 30 日出货,到 8 月 10 日排查时管道仍在正常解密。公开资料里 v3 的密钥思路已经完全不同,钉钉大版本升级可能换算法。什么时候变、变成什么样,我不知道,不猜。
问:WAL 不解行不行? 行,脚本里留了降级:WAL 解密失败就弃用,只用主库快照,代价是数据略旧。但群消息这种场景,最新消息恰恰全在 WAL 里,不解等于白解。
问:380 个文件为什么只归档了 149 个? 149 个是我在钉钉里下载过的,落盘路径在任务表里查得到;剩下 231 个我从没下载过,本机没有文件实体。我不为凑数去拉没接收过的数据。
问:这套东西普通人用得上吗? 看消息密度和归档需求。群里一天几十条通知加文件、要按日期归档备查的,值;群只是用来吹水的,别折腾。
我是谁?csdn平台不许说,说我是广告营销!我也不说了,纯分享个技术和AI实战,给我搞这套。

1万+

被折叠的 条评论
为什么被折叠?



