搜索全站

输入至少 2 个字符,查找相关内容。

↑↓ 选择 Enter 打开Esc 关闭

浏览器密码库与 Token 提取

浏览器是单设备上凭据密度最高的取证目标。一个普通用户的 Chrome 里可能有上百个站点的账号密码、信用卡信息、Cookie 会话和长期有效的 API Token。在磁盘上能拿到的凭据里,它常常是最集中的一份。

最后更新 2026-10-09版本 v2.0维护 DigiForensics查看历史 1

一、概述

浏览器是单设备上凭据密度最高的取证目标。一个普通用户的 Chrome 里可能有上百个站点的账号密码、信用卡信息、Cookie 会话和长期有效的 API Token。在磁盘上能拿到的凭据里,它常常是最集中的一份。

没有这套方法,案件里最常见的一类问题就悬在半空:"这个人掌握了哪些系统的访问权"。

凭据清单能给出站点的完整覆盖面。而覆盖面在内部调查、账号泄露追源、数据外泄路径分析里,都是决定性事实。

浏览器凭据相比其它类型的加密存储,有三个特点让它在案件里格外重要。

  1. 多站点覆盖。一次提取往往就回答了"这个人掌握哪些系统的访问权",不需要逐个系统单独取证。
  2. Token 常常绕过口令。Local Storage 与扩展存储里的 token 很多是明文,拿到就能直接访问账号,不需要任何解密。

这一点必须先讲清楚,否则你会把大量明文 target 当成"解不开的密文"而放弃。

  1. 防护机制正在变紧。Chrome 127 引入的 App-Bound Encryption(ABE)已经让大量流传的 DPAPI 解密教程失效——这是本篇必须写清的第一个重点,也是本章最容易被旧资料误导的地方。

本篇要写清的最重要一句:如果你在网上找到的教程只做了"读 Local State → CryptUnprotectData → AES-GCM 解 v10",它在 2024 年 7 月之后的 Chrome 上对部分数据(Cookies)会失败。这不是工具的 bug,是防护边界的设计变更。 原因见 2.5。

本篇处理的证据形态:

浏览器 关键文件 加密层 判据
Chrome / Edge / Brave(Chromium 系) Login Data、Cookies、Web Data(SQLite),Local State(JSON),Local Storage/leveldb/ AES-GCM,密文前缀 v10 / v11 / v20 v20 = ABE 保护,传统 DPAPI 流程必然解不开
Firefox logins.json、key4.db、cookies.sqlite NSS 体系,不是 DPAPI key4.db 里 NSS 私钥项的加密方式
Safari(macOS) Keychains 文件 Keychain 钥匙串,受系统钥匙串 ACL 保护 与前两者机制完全不同

记住 Chromium 系和 Firefox 是两套完全独立的体系——加密层、密钥存放、破解前提都不通用。把 Chrome 的方法套到 Firefox 上(或者反过来)会一路碰壁。

而且同一个 Chromium 内部也有代次差异:Chrome 80 改过一次前缀(v10 → v11),Chrome 127 又引入了 v20。

密文前缀本身就是判据。 读任何一条记录之前先看前缀,再决定走哪条解密链。

本篇的操作主链是五步,每一步都有明确的产出与停手条件:

步骤 动作 产出 什么时候就该停
1 定位所有浏览器与所有 Profile 浏览器清单 + Profile 路径清单 只找到一个 Profile 就动手 → 会漏读
2 读 Local State,拿主密钥并判 ABE 状态 是否有 app_bound_encrypted_key(解码后以 APPB 开头) 出现 v20 前缀且无内存镜像 → 转"本阶段不可解"
3 解密 Login Data 并导出 域名 / 用户名 / 口令(脱敏) 遇到 v20 不换工具硬试
4 顺带取更高价值的目标 Cookies、支付数据、Local Storage 里的 token token 若是明文就到此为止
5 记录与脱敏 结构化记录 + 脱敏说明 完整明文不进报告正文

第 4 步是本篇性价比最高的一步。

很多人把全部精力耗在 Login Data 上,而 Local Storage 与扩展存储里的 token 大量是明文的——它们不需要任何解密就能读出来,却往往比口令更直接地指向访问权。

反过来,第 3 步的失败也不该被当成失败:v20 在关机镜像上解不开是符合预期的正确结果。写成"该数据受 App-Bound Encryption 保护,在现有检材范围内不可解"就是完整结论。

在取证流程中的位置:本篇属于分析环节,紧跟在采集之后。

上游依赖是镜像制作——浏览器 profile 分散在用户目录下,必须从镜像里取,不能在目标机现场翻;只读挂载与安全接入提供只读访问;SQLite 基础与结构是解 Login Data 与 Cookies 的前置。

DPAPI 机制的完整背景见EFS 文件系统加密取证。浏览器密码库用的就是同一套 DPAPI 主密钥,主密码一旦解开,EFS 与浏览器两条线同时打开。

下游支撑的是浏览器取证里的历史、下载与扩展痕迹,与本篇的凭据侧互为印证。涉及"这个人访问过什么"的问题,则延伸到时间线重建。

这台机器上装过哪些浏览器、有几个用户 Profile、每个 Profile 的凭据覆盖面(域名 + 用户名)、哪些字段是可读的明文、哪些受 ABE 或系统钥匙串保护、Cookie 会话与本地存储里有哪些 token、以及它们的可用期限。

v20 加密的数据在不改变检材状态的前提下解不开——这是 ABE 的设计目标,不是能力不足。Firefox 在不知道主密码且无内存镜像时同样受阻。

更不能回答的是**"这个人有没有实际使用过某个账号"**。凭据存在与使用是两条独立的证据链,后者要走浏览器取证的历史与下载记录。

本篇也明确不涉及任何在线验证行为。

读者前提:你需要先掌握 SQLite 的基本读写(SQLite 基础与结构)、DPAPI 的工作方式与主密钥存放(EFS 文件系统加密取证)、以及 AES-GCM 的密文结构(前缀 + 12 字节 IV + 密文 + 16 字节 tag)。

合规前提同样要先对齐:提取出的凭据默认只做固定与上报、不做使用,相关要求见合规授权与法律边界。

如果你的检材只有一台已关机设备、没有内存镜像,遇到 v20 密文就应当把结论写成"在现有检材范围内不可解"并列出未覆盖范围,而不是换工具继续试。

最后提醒一个范围问题:一个 Profile 里的凭据清单,只覆盖这台机器上这个浏览器。

同一个人可能有第二个浏览器、另一台设备、手机端的 App 登录态。

报告里写"发现 N 组凭据"时必须同时写明范围,否则"未发现的凭据"这个推论会在质证时被推翻——尤其当案件本身涉及多设备时。

二、核心原理

2.1 Chromium 系的文件布局

所有 Chromium 系浏览器结构完全相同,只是路径不同——理解一次即可通用于全部:

浏览器 User Data 根目录(%LOCALAPPDATA% 下,Opera 例外)
Chrome Google\Chrome\User Data
Edge Microsoft\Edge\User Data
Brave BraveSoftware\Brave-Browser\User Data
Vivaldi Vivaldi\User Data
Opera %APPDATA%\Opera Software\Opera Stable
360 等国产套壳 \360Chrome\Chrome\User Data(结构同 Chrome)

根目录下:

文件/目录 内容 取证价值
Local State JSON,存放主密钥 解密链的起点
<Profile>\Login Data SQLite,口令与自动填充用户名 最高
<Profile>\Web Data SQLite,自动填充、信用卡 高(含卡号)
<Profile>\Cookies SQLite,会话 Cookie 高(token 常在此)
<Profile>\History SQLite,访问历史 高
<Profile>\Local Storage\leveldb\ LevelDB,token 常明文 最高之一
<Profile>\Session Storage LevelDB,会话态 中
<Profile>\Local Extension Settings\<id> 扩展数据 LevelDB 中(可含扩展保存的凭据)
<Profile>\Preferences JSON,搜索词、主页、扩展列表 中(搜索历史是主观意图的直接证据)

Profile 目录名不是 Default 就找不着。 实际是 Default、Profile 1、Profile 2……数量与用户数对应。必须遍历所有 Profile,只读 Default 会漏掉其他账号体系。

2.2 v10/v11 的解密链(Chrome 80+ 的传统路径)

Local State
  └─ os_crypt.encrypted_key  (Base64 字符串)
       │  Base64 解码
       ├─ [0:5]  = b"DPAPI"      ← ASCII 前缀,不是密文,剥掉
       └─ [5:]   = DPAPI 密文    ← CryptUnprotectData  → 32 字节 AES 主密钥
                                              │
Login Data 的 logins.password_value (BLOB)   │
  └─ [0:3]  = b"v10" 或 b"v11"               │
  └─ [3:15] = 12 字节 GCM nonce               │
  └─ [15:-16] = 密文                          ├── AES-256-GCM 解密
  └─ [-16:]  = 16 字节 GCM tag               ┘

需要纠正的常见说法:

  • 主密钥是 32 字节,v10 和 v11 都走 AES-256-GCM。 说"v10 是 AES-128、v11 才是 AES-256"是错的——密钥长度由 Local State 决定,与前缀无关。v11 与 v10 的差异在 Chromium 内部实现,解密方式与前缀无关。
  • 前缀不是加密算法标识。 只需要判断"是 v10/v11 走 GCM"还是"无前缀走旧版 DPAPI"。

判定分支,实际写脚本时按这个顺序:

len(blob) >= 3 且 blob[0:3] in (b"v10", b"v11")  → 取主密钥走 AES-256-GCM
blob[0:3] == b"v20"                                → ABE 保护的 Cookie,见 2.5
blob[0:8] == 01 00 00 00 D0 8C 9D D0              → Chrome 80 之前的旧版 DPAPI

2.3 Linux / macOS 上的 Chromium

同一份 Login Data,密钥来源不同:

平台 密钥来源 取证难度
Windows DPAPI(用户凭据绑定) 中,受 2.5 的 ABE 影响
macOS Keychain,需该用户的 keychain 密码 高(详见 [07 之外:Keychain 解锁是独立课题)
Linux 固定口令 peanuts(明文硬编码在源码里) 极低——主密钥可直接算出

Linux 上的 peanuts 值得单独记住:Chromium 在 Linux 上用一个硬编码的固定口令加密主密钥。这在设计上是"依赖 kwallet/gnome-libsecret"的兜底路径,但只要文件可读,密钥就可复现。这是跨平台对比中差异最大的一项。

2.4 Firefox:用另一套体系(NSS)

Firefox 完全不走 DPAPI/AES-GCM,而是 Mozilla 的 NSS(Network Security Services):

文件 内容
%APPDATA%\Mozilla\Firefox\Profiles\<随机名>.default-release\logins.json 凭据清单,字段名是明文,密码是 NSS 加密串
同目录 key4.db SQLite,存放 NSS 主密钥(PKCS#11 格式)
cert9.db(老版本 cert8.db) 证书库
global-magento-db.json 老版本(<58)的密码库,已被 logins.json 取代

解密依赖关系:logins.json 里的 encryptedUsername/encryptedPassword 需要 key4.db 中的密钥,两者同目录。

若用户设置了 Primary Password(主密码),则还需该密码参与解锁,无密码时不可解。

取证顺序:先看 logins.json 里的 hostname 字段——这已经是明文的高价值证据("这台机器访问过哪些站点"),密码本身往往不是必需的。

2.5 ⚠️ App-Bound Encryption:为什么老教程失效了

这是本篇的技术核心,Chrome 127(2024 年 7 月)开始引入。

旧模型的弱点:DPAPI 只能证明"这个数据属于这个 Windows 用户"。

任何以该用户身份运行的程序——包括信息窃取木马——都能调用 CryptUnprotectData 把它解开。

防护边界在用户,不在程序。

ABE 把边界从用户移到了程序:

① Chrome 生成随机 32 字节 app_bound_key
② 把它交给一个以 SYSTEM 权限运行的 COM 服务(Elevation Service)
③ 该服务做信封加密:
   - 把调用者进程的可执行文件路径作为身份数据编入(规范化后比对)
   - 用硬编码在服务内的密钥加密 app_bound_key
   - 内层 CryptProtectData(用户上下文)
   - 外层 CryptProtectData(SYSTEM 上下文)  ← 双层 DPAPI 包装
④ 结果写入 Local State 的 os_crypt.app_bound_encrypted_key
   (Base64,解码后以 ASCII "APPB" 开头)

解密时:Chrome 通过 COM 接口(IElevator::DecryptData)把 APPB 块交给 Elevation Service,服务验证调用者确实是那个签名的 chrome.exe,才返回 32 字节密钥。

别的程序来问,一律被拒。

在 Local State 里怎么认出来:看是否存在 app_bound_encrypted_key 字段(值解码后以 ASCII APPB 开头),命令见第七节 2。

受影响范围——要精确,不要过度宣称:

数据 Chrome 127+ 状态
Cookies 首批迁移到 ABE,密文前缀为 v20
Login Data 口令 127 初始批次尚未全部迁移,多数仍为 v10/v11 + 传统 DPAPI 主密钥
支付数据等 设计上计划后续迁移

准确的表述是"部分失效"而非"全部失效":ABE 首发覆盖 Cookies,Login Data 的迁移在后续版本逐步推进。判据是数据本身的前缀——v20 就是 ABE 保护,传统 DPAPI 流程必然解不开。

三条应对路径,都要写进报告的方法论,说明各自代价:

路径 做法 代价与限制
运行中提取 在目标机器上让浏览器处于运行态,从其进程内存中取得已解密的密钥材料 改变了检材状态,需授权;破坏了"关机镜像"这一取证前提
受控启动参数 以 --disable-features=AppBoundEncryption 启动浏览器 需要用户侧配合或可控账户;改变了配置与可能的运行痕迹,必须记录
维持静态分析 关机镜像上只解 v10/v11 数据,对 v20 数据如实记录为"受 ABE 保护,本阶段未解出" 最保守、零污染,v20 密文本身的存在即是证据

取证原则:"没解出来"是一个合法且应当如实写进报告的结论。 不要为了"拿到明文"而越过授权去改检材状态。密文存在、格式可辨、机制可述——这本身就是充分的证据描述。

一条有用的旁证:Chrome 在 ABE 校验失败时会在 Application 日志的 Chrome 源写 Event ID 257。

日志里出现这条事件,说明当时有程序试图解密该数据但被拒——这本身就是一条独立的攻击痕迹证据。

2.6 Safari 与 Edge Legacy

目标 位置与机制 要点
Safari ~/Library/Keychains/login.keychain-db、~/Library/Safari/History.db 凭据在 Keychain,需该用户 keychain 密码解锁;iOS 备份另有一套 schema
Edge Legacy %LOCALAPPDATA%\Microsoft\Windows\WebCache\WebCacheV01.dat ESE 数据库格式,非 SQLite,必须用专用解析工具,直接读取只会得到乱码

不要把 Edge Legacy 和 Chromium 版 Edge 搞混:新版 Edge 是 Chromium,路径与 Chrome 一致;WebCacheV01.dat 属于 IE 时代的遗留组件,两者机制完全不同。

2.7 合规边界

允许 禁止
提取并脱敏记录"该设备曾登录过某站点" 用提取的凭据登录任何网站
记录 Cookie/token 的存在、有效期、归属站点 使用 token 访问对方账号(包括"只看一下有没有")
用 token 的格式与签发时间做时间线分析 把完整凭据写进报告正文
将凭据固定为证据并移交委托方 私存一份"以备后用"

报告中的正确表述:"在 Chrome Profile 1 的 Local Storage/leveldb/ 中发现某站点会话 token(JWT 格式,签发时间 2024-03-11,有效期 7 天),表明该设备在该时段持有该站点的有效会话。本次分析未使用该 token 访问任何服务。"

错误表述:"恢复出该站点账号密码,可登录查看内容。"——这既超出了委托范围,也可能构成非法访问。


三、操作步骤

步骤 1:定位所有浏览器与所有 Profile