微信DAT文件转JPG:异或密钥反推与Python批量恢复脚本

📅 发布时间:2026/9/29 13:27:46
微信DAT文件转JPG:异或密钥反推与Python批量恢复脚本
微信电脑版用久了WeChat Files能涨到几十个G打开一看FileStorage\Image下面全是.dat双击打不开手动把后缀改成.jpg又提示文件损坏。很多人第一反应是文件坏了、被清理工具搞残了其实这些.dat里装的就是你平时在聊天里点开看过的图片微信在落盘时给每个字节做了一层很轻的异或编码。微信DAT文件转JPG图片这件事核心说到底就一句话让每个字节跟一个固定值做一次异或原始 JPG、PNG、GIF 就回来了。这篇内容写给三类人看一是想把自己聊天记录里的老照片捞回来的普通用户二是做微信、企业微信数据归档、需要批量跑几十万文件的人三是单纯好奇微信本地存储到底怎么设计的同学。我会从原理讲到脚本脚本可以直接复制去跑也会把踩过的坑一条条列出来——比如为什么别人张口就说的 0x37 密钥到你这就失灵、为什么解出来的图一半能看一半是花屏、新版微信路径变了以后老脚本为什么直接失效。全程只针对你自己机器上、你自己账号产生的缓存文件。1. 先弄明白DAT文件是什么原理不通脚本白写1.1 微信为什么把图片存成.dat而不是直接jpg微信PC端在接收和查看聊天图片时会把图片落地缓存到本地方便下次点开秒开、断网也能看。它没直接存成.jpg而是统一改名成.dat原因有两层。第一层是统一管理微信不太关心这张图原本是 jpg、png 还是 gif它在自己的数据库里记录图片的元信息和索引磁盘上只需要一个扁平的、同构的文件名池子命名用十六进制数字串天然去重、天然排序。第二层是轻量混淆早期版本给这些缓存文件加了一个非常简单的单字节异或让普通人不能直接双击打开、不能用图片查看器一眼看穿起到一点点防手滑和防傻瓜式抓取的作用。需要强调的是.dat不只是图片会有。FileStorage下面还有视频、文件、语音等缓存有些也用了类似处理但视频mp4和大部分文件的编码方式和图片并不完全一样有的只动了头部有的干脆是原始文件没动。所以在批量转换前先确认你要处理的是Image相关目录下的图片缓存而不是把整个FileStorage一股脑丢进脚本否则会解出一堆四不像的文件。我一般先按目录缩范围只处理文件名是纯十六进制、扩展名为.dat、体积在几KB到几MB之间的文件这几条筛下来命中率非常高。1.2 异或这层锁到底长什么样异或是一种位运算规则很简单两个相同的位异或得 0不同的位异或得 1。它有个非常好用的性质——A ^ K ^ K A。也就是说一个字节被某个密钥 K 异或一次是加密再异或同一个 K 一次就还原。微信用的就是这种运算磁盘上存的每个字节 原图字节 ^ KK 是一个 0 到 255 之间的整数在绝大多数老版本里这个 K 就是 0x37也就是十进制的 55。真正要重视的是K 不是永远固定的。不同微信版本、不同平台下的客户端可能用不同的 K甚至同一台电脑上不同时间段落盘的缓存用了不同的 K也不是没有出现过的现象。所以我从来不建议死记密钥就是 0x37正确做法是从文件内容里把 K 反推出来。反推原理很优雅所有 JPG 都以固定的三个字节开头FF D8 FF所有 PNG 都以89 50 4E 47 0D 0A 1A 0A开头GIF 是47 49 46 38。拿磁盘上的前几个字节去和这些标准文件头异或如果得到的值都相同这个值就是密钥。举个具体例子打开一个 dat读到前三个字节是C8 EF C8。拿它和 JPG 文件头异或C8 ^ FF 37EF ^ D8 37C8 ^ FF 37。三个结果都是 0x37那么密钥就是 0x37把这个文件每一个字节都跟 0x37 异或原图就出来了。这个过程用计算器手算都行代码里不过几行。理解之后你就明白了密钥根本不神秘也就明白了为什么脚本里必须带一个自动探测密钥的函数而不是写死一个常量。注意异或还原是逐字节、无损的。如果算出来的几个异或值不相等说明要么这个 dat 根本不是图片要么文件头被截断损坏了别强行套一个密钥去解解出来只会是垃圾数据。2. 动手准备路径、环境和工具选型2.1 先把微信的图片缓存目录找对这是整个流程里最容易被忽视、也最容易卡住的一步。老版本微信3.x 系列Windows的默认存储根目录一般在文档下C:\Users\你的用户名\Documents\WeChat Files\。进去以后找到以你微信号命名的文件夹形如wxid_xxxxxxxxxxxx也可能显示为你自定义的微信号里面有个FileStorage。图片相关缓存散落在两处需要分开处理FileStorage\Image\下面按年月分子目录比如2024-05里面多是缩略图和小图文件名是十六进制数字串加.datFileStorage\MsgAttach\下面有一堆以哈希命名的子目录再往里有Image\年月\这里往往放着聊天气泡里点开看的大图、原图。新版微信4.x 系列做了比较大的改版存储根目录换成了xwechat_files结构和老版本不再一一对应升级过的机器上老数据可能还留在原来的WeChat Files里没动也可能被迁移了一部分。最稳的做法是先全盘搜一次在资源管理器地址栏或者命令行里搜*.dat看结果集中在哪些目录再决定处理范围。企业微信的思路类似它自己的数据目录一般在文档或 AppData 下同样能找到存放图片缓存的文件夹因为目录名和微信不同直接套微信脚本的路径会找不到文件但解码逻辑完全通用改一下输入目录就行。小技巧在资源管理器里按大小排序JPG 原图通常几十KB到几MB缩略图往往只有几KB。先把几KB的那一批挑出来试解成功了再处理大图能省很多等待时间。2.2 工具怎么选现成查看器还是自己写脚本网上搜微信dat文件查看器能搜到一堆带界面的小工具双击 dat 或者选一个文件夹就能批量转成 jpg确实省事。这类工具适合只想恢复几十张图、不想折腾命令行的人。但它有几个现实问题来源不好确认、有的捆绑了乱七八糟的东西、对几万张图的批量支持较弱、遇到密钥不是 0x37 的直接歇菜。所以如果你要处理成百上千张、或者以后还想反复用我强烈建议自己写一个脚本一百来行 Python逻辑透明出问题好排查密钥探测也能做得比现成工具聪明。环境准备非常简单装一个 Python 3Windows 上从官网下安装包务必勾选Add Python to PATH这个脚本只用标准库不需要pip install任何东西。如果你实在不想装 Python也可以找一个支持批处理的命令行工具但 Python 的可读性和可改造性是最好的。下面所有代码我都实测过Python 3.8 以上都能跑。注意处理前务必对原始FileStorage目录做一次完整备份复制一份到移动硬盘或者另一个盘。脚本只读不写、理论上不动原文件但任何批量操作都建议留后路万一脚本有 bug 误删哭都来不及。3. 核心实操密钥反推与批量还原全流程3.1 用文件头反推密钥手算和代码两条路手算那条路刚才演示过了适合临时验证一两张图。真正处理成批文件得靠代码。核心思路是读文件前 8 个字节分别按 JPG、PNG、GIF、BMP 四种文件头去试哪种文件头算出来的异或值全部一致就认定是哪种格式并把这个一致值当作密钥。这里有个细节要注意JPG 只校验前 3 个字节因为有的 JPG 第 4 个字节内容并不固定PNG 和 GIF 前缀长校验 4 个字节更稳妥误判概率更低。BMP 只有42 4D两个字节校验信息太少容易把别的格式误认成 BMP所以我把它放在最后兜底判断而不是优先判断。还有一个容易被忽略的点微信会用同一把钥匙处理同一批缓存但不同月份、不同版本的缓存真有可能换钥匙。所以脚本必须做到每个文件独立探针而不是整批用第一次探测出来的那个密钥。这样即使目录里混了两把钥匙的文件也能各自还原正确。很多网上流传的脚本就是把 0x37 写死遇到换钥匙的版本就全军覆没这也是别人能解我解不了的最常见原因。3.2 可直接抄的 Python 批量转换脚本下面这份脚本包含密钥探测、类型判断、时间戳还原文件名、批量遍历、结果统计拿走改成自己的路径就能跑# -*- coding: utf-8 -*- import os import datetime def detect_key(data): 从文件头反推异或密钥返回 (key, ext)无法识别返回 (None, None) # JPG: FF D8 FF if len(data) 3: k1, k2, k3 data[0] ^ 0xFF, data[1] ^ 0xD8, data[2] ^ 0xFF if k1 k2 k3: return k1, jpg # PNG: 89 50 4E 47 if len(data) 4: k1 data[0] ^ 0x89 k2 data[1] ^ 0x50 k3 data[2] ^ 0x4E k4 data[3] ^ 0x47 if k1 k2 k3 k4: return k1, png # GIF: 47 49 46 38 if len(data) 4: k1 data[0] ^ 0x47 k2 data[1] ^ 0x49 k3 data[2] ^ 0x46 k4 data[3] ^ 0x38 if k1 k2 k3 k4: return k1, gif # BMP: 42 4D if len(data) 2: if (data[0] ^ 0x42) (data[1] ^ 0x4D): return data[0] ^ 0x42, bmp return None, None _TABLE_CACHE {} def xor_bytes(data, key): 用 translate 表加速异或比逐字节循环快很多 if key not in _TABLE_CACHE: _TABLE_CACHE[key] bytes(i ^ key for i in range(256)) return data.translate(_TABLE_CACHE[key]) def ts_name(name): 把十六进制文件名还原成可读时间不像时间戳就保留原名 base os.path.splitext(name)[0] try: ts int(base, 16) if 946684800 ts 2524608000: # 2000-01-01 ~ 2050-01-01 return datetime.datetime.fromtimestamp(ts).strftime(%Y%m%d_%H%M%S) except ValueError: pass return base def convert_folder(src_dir, dst_dir): os.makedirs(dst_dir, exist_okTrue) total ok skip 0 for root, _, files in os.walk(src_dir): for name in files: if not name.lower().endswith(.dat): continue src_path os.path.join(root, name) total 1 try: with open(src_path, rb) as f: raw f.read() except OSError: skip 1 continue key, ext detect_key(raw) if key is None: skip 1 continue decoded xor_bytes(raw, key) out_name {}_{}.{}.format(ts_name(name), hex(key), ext) out_path os.path.join(dst_dir, out_name) i 1 while os.path.exists(out_path): out_path os.path.join( dst_dir, {}_{}_{}.{}.format(ts_name(name), hex(key), i, ext)) i 1 with open(out_path, wb) as f: f.write(decoded) ok 1 print(扫描 {} 个 dat成功还原 {} 个跳过 {} 个.format(total, ok, skip)) if __name__ __main__: SRC rC:\Users\你的用户名\Documents\WeChat Files\wxid_xxxx\FileStorage\Image DST rD:\wechat_restore convert_folder(SRC, DST)几个设计点值得说清楚。第一输出文件名里我带了hex(key)比如20240512_183000_0x37.jpg这样万一某个文件钥匙特殊你一眼能看出来也方便按钥匙分组排查。第二用translate建异或表再整块转换比bytes(b ^ key for b in data)的逐字节推导快几倍处理几万张图时体感差距明显。第三还原出来的文件名用时间戳还原是因为微信的 dat 文件名大多就是十六进制 Unix 时间戳转成20240512_183000这种格式后照片自然按时间排好序比一堆乱码名字好整理得多。提示如果文件名不是十六进制时间戳int(base, 16)会抛异常脚本里已经用try/except兜住保留原文件名不会中断整批任务。3.3 多格式混杂时的判别与命名还原真实目录里不会只有 JPG。聊天里发的表情包是 GIF截图是 PNG偶尔还有 BMP。如果只按 JPG 文件头去探针PNG 文件就会被跳过日志里那堆跳过 N 个就是这么来的。所以按四种格式依次尝试是必要的。反过来如果某类图你根本不关心也可以在detect_key里去掉对应分支让它们被跳过减少噪声。我个人习惯是 JPG、PNG、GIF 全留BMP 保留但排在最后因为它的两个字节前缀最容易误判。命名还原还有个小坑微信同一秒内可能落盘多张图时间戳会撞车。如果直接拿时间戳命名后处理的会覆盖先处理的。脚本里用while os.path.exists加了序号后缀保证不覆盖。另外十六进制时间戳转成本地时间时用的是系统时区如果你跨时区整理数据出来的时间可能差几小时这个自己心里有数就行不影响图片本身。4. 踩坑记录常见问题与排查速查表4.1 解出来打不开或花屏先查这几条处理几百个文件几乎一定会遇到解出来但打不开的情况。我把最典型的几种原因和对应判断整理成表遇到问题直接对着查现象最可能原因排查方法解出来是花屏、颜色怪异密钥错了打印文件头前 4 字节手算异或值是否一致文件变大几十字节打不开原 dat 头部带了额外元数据用十六进制编辑器看前几十字节跳过非图像头部分文件直接跳过没有被处理不是图片格式或文件头损坏看跳过文件的体积几字节的多半是空壳或索引解出来的图只有半张原缓存未下载完整检查 dat 体积明显小于正常图的属正常残缺文件名时间全是 1970 或 2100 年文件名不是时间戳关闭时间戳还原保留原名花屏几乎 100% 是密钥问题。有一种情况特别迷惑人整批文件大部分能解少数解出来是花屏。大概率是那少数几个用了不同的密钥或者根本不是图片而是别的类型被误判。我的习惯是先把它们单独拎出来打印前 8 个字节手动跟标准文件头异或算一遍多数时候一眼就看出真相了。4.2 原图和缩略图混杂、去重与排序微信缓存里同一张图往往会存多份缩略图一份、点开看的大图一份、发送时压缩过的版本可能又是一份。全解出来之后你会看到同一张图好几个分辨率版本。清理的办法有两个一是按文件体积过滤把几KB的缩略图批量删掉只留大图二是按图片尺寸二次筛选用Pillow读一下宽高比如只保留宽度大于 800 的。后者更准但要装第三方库取舍看你的需求。排序方面靠时间戳还原出来的文件名天然带年月日时分秒直接按名称排序就是时间顺序。如果你还想要按聊天对象分文件夹那就得结合微信的消息数据库去关联了复杂度上一个台阶普通恢复照片的需求其实用不到。我的建议是先把所有图混着还原到一个目录再用系统的图片查看器按时间浏览比折腾目录结构高效得多。实操心得还原目录里出现大量纯色小图或者长条图别急着删那多半是聊天气泡里的表情、分割线、头像缓存。先把这些筛掉剩下的才是真正有价值的聊天照片。5. 场景延展备份、迁移与整理5.1 换电脑、手机迁移后图片丢失的补救换电脑或者重装系统前很多人会把整个WeChat Files目录拷走但装好新微信后发现历史图片打不开——因为新版本微信不认老结构的缓存这时候这些.dat就成了最后的数据源。用上面的脚本把它们批量还原成 jpg再按时间整理进相册就能把几年的聊天图片救回来。手机迁移到电脑的情况类似电脑端往往会同步生成一份图片缓存同样是一堆 dat处理逻辑一模一样。这个场景下有个重要提醒趁早处理。微信的缓存目录会被自动清理策略影响长期不登录的账号或者磁盘空间紧张时老缓存可能被删。我见过朋友等了两年才想起来恢复结果目录里只剩最近的几个月份早期图片早没了。所以只要你在意某段聊天记录里的照片越早备份越好。5.2 处理自己数据的边界别越线这里必须说清楚这套方法只适用于你自己账号、你自己设备上产生的缓存文件。它解决的是我自己的数据我自己打不开的问题不是去获取别人的东西。任何试图去解密、访问他人账号数据的做法都不在本文讨论范围内也不应该去做。技术本身是中性的用在数据自救上是正途用在别处就是另一回事了。另外用脚本批量处理时尽量在本地完成不要把原始缓存或者还原出来的图片上传到不明来历的在线工具上——那些在线 dat 转换网站你上传的是自己的聊天照片风险不用我多说。6. 最后分享几个我常用的提速小技巧处理几万张图的时候慢是真的慢几个小优化能省不少时间。第一把异或表缓存起来上面脚本里的_TABLE_CACHE就是干这个的别每个文件都重新生成表。第二用os.scandir替代os.walk的某些场景遍历更快尤其是在 SSD 上处理几十万小文件时。第三输出目录和源目录放在不同的物理磁盘上读写不互相抢 IO。第四先拿一个月的目录试跑确认结果正确、密钥探测没问题再对整个FileStorage开跑别一上来就全量。我自己的习惯是先跑一个2024-05这样的小目录随机抽几张图用看图软件确认能正常打开再放开手脚。踩过几次密钥不对的坑之后我现在每次都会把探测出来的密钥分布打印出来如果整批只有一两种密钥说明很干净如果出现几十种奇怪的值那多半是有文件损坏或者混进了非图片 dat这时候就得回头逐个排查别硬着头皮全解。这套流程我前后用在小十台机器上从老版本微信到新版本从个人微信到企业微信核心逻辑没变过变的一直只是目录位置和偶尔出现的特殊密钥。把自动探测密钥 多格式判别 独立文件名还原这三件事做扎实剩下的就是耐心等脚本跑完了。