- 网络安全
- 应用安全
- 密码学
- CLI
【免费下载链接】HackBrowserData
Extract and decrypt browser data, supporting multiple data types, runnable on various operating systems (macOS, Windows, Linux).
本指南以 rfcs/006-key-retrieval-mechanisms.md(RFC-006)为骨架,结合 HackBrowserData 仓库中
masterkey、browser、crypto等包的源码实现,系统讲解 Chromium 系浏览器主密钥(Master Key)在 macOS、Windows、Linux 三个平台上的存储方式与检索策略。读完本文,你将掌握Retriever接口与ChainRetriever链式语义、按密文版本前缀分层的MasterKeys(V10/V11/V20)机制、macOS 的三种取钥手段(含 CVE-2025-24204 内存转储)、Windows 的 DPAPI 与 App-Bound Encryption(ABE)双轨方案,以及 Linux 上"peanuts"硬编码密钥与 D-Bus Secret Service 的搭配,并理解 Safari 为何独立于这套体系。
1. 概述:Chromium 主密钥的三种平台存储形态
Chromium 系浏览器(Chrome、Edge、Brave、Opera、Vivaldi 等)并不会直接用明文保存密码、Cookie、信用卡等敏感数据,而是先派生出一个主密钥(master key),再用它加密落盘。主密钥本身按平台存放在不同的"保险柜"中,RFC-006 给出了权威对照表:
| 平台 | 存储位置 | 密钥类型 |
|---|---|---|
| macOS | macOS Keychain(钥匙串) | 密码字符串 → PBKDF2 → AES-128 |
| Windows | Local StateJSON(DPAPI 加密) | 原始 AES-256 密钥 |
| Linux | GNOME Keyring / KDE Wallet(经 D-Bus) | 密码字符串 → PBKDF2 → AES-128 |
要点是:每个平台可能存在多种取钥途径,例如 macOS 既能靠 root 权限的内存转储拿密码,也能用登录密码直接解锁钥匙串,还能退化为调用security命令。HackBrowserData 用Retriever接口与ChainRetriever链式模式把这些策略抽象成"按优先级逐个尝试、先成功者胜出"的管线。
关于 Chromium 密文本身的加密细节(AES-CBC/GCM、密文版本前缀等),属于 RFC-003 的范畴;Firefox 通过key4.db自管密钥,见 RFC-005。
2. Retriever 抽象:Hints、接口与链式取钥
2.1 Hints:显式传参而非位置参数
接口的入参是一个Hints结构体,目的是让调用方的意图显式化,而不是靠一堆位置参数拼凑。源码定义见 masterkey/retriever.go:
type Hints struct { KeychainLabel string // macOS Keychain account / Linux D-Bus Secret Service item label (e.g. "Chrome", "Chrome Safe Storage") WindowsABEKey string // Windows ABE browser key used by ABERetriever to locate the elevation-service COM interface (e.g. "chrome", "edge"). "" → ABE not applicable; ABERetriever returns (nil, nil) silently. LocalStatePath string // path to Local State JSON. Only used on Windows (DPAPI + ABE both read it). }每个 retriever 只读取与自己平台相关的字段,其余字段一律忽略。调用方从BrowserConfig填充Hints:KeychainLabel直接拷贝配置值;WindowsABEKey则在cfg.WindowsABE为 true 时置为cfg.Key。这一组装逻辑实现在 browser/chromium/chromium.go 的buildHints中(V10 的 DPAPI 与 V20 的 ABE 都依赖它)。
2.2 Retriever 接口与返回值语义
// Retriever obtains a Chromium master key from one platform source (DPAPI, Keychain, D-Bus, …). type Retriever interface { RetrieveKey(hints Hints) ([]byte, error) }返回值就是拿来即用的解密密钥:Windows 上是 32 字节原始 AES-256 密钥,macOS/Linux 上则是经 PBKDF2 派生后的 16 字节 AES-128 密钥。也就是说,"取钥"这一层已经把 PBKDF2 派生完成了,下游拿到[]byte即可直接喂给解密器。
2.3 ChainRetriever:先成功者胜出
type ChainRetriever struct { retrievers []Retriever } func (c *ChainRetriever) RetrieveKey(hints Hints) ([]byte, error) { var errs []error for _, r := range c.retrievers { key, err := r.RetrieveKey(hints) if err == nil && len(key) > 0 { return key, nil } if err != nil { log.Debugf("retriever %T failed: %v", r, err) errs = append(errs, fmt.Errorf("%T: %w", r, err)) } } return nil, fmt.Errorf("all retrievers failed: %w", errors.Join(errs...)) }语义有三个关键点(对应 masterkey/retriever.go):
- 先成功者胜出:只要某个 retriever 返回
err == nil且密钥非空,立即返回,后续 retriever 不再执行; - 静默降级:单个 retriever 失败只打
Debugf日志,不中断链路; - 全失败合并错误:所有 retriever 都失败时,用
errors.Join把每个 retriever 的错误合并成一个整体错误返回,方便诊断是哪个环节出了问题。
2.4 MasterKeys / Retrievers:按密文版本分层,而不是单链
RFC-006 的关键设计是:不能只有一个"获胜密钥",因为一个 profile 里可能混有多种密文版本(Windows 的 v10+v20、Linux 的 v10+v11)。因此masterkey包提供了按密文版本前缀分层的容器,见 masterkey/masterkeys.go:
// MasterKeys holds one key per cipher tier; a profile can mix tiers (Win v10+v20, Linux v10+v11), // so each is populated independently. A nil tier = that cipher version can't be decrypted. type MasterKeys struct { V10 []byte `json:"v10,omitempty"` V11 []byte `json:"v11,omitempty"` V20 []byte `json:"v20,omitempty"` } // Retrievers is the per-tier retriever configuration; unused slots are nil. type Retrievers struct { V10 Retriever V11 Retriever V20 Retriever } // NewMasterKeys fetches each non-nil tier and joins per-tier errors. func NewMasterKeys(r Retrievers, hints Hints) (MasterKeys, error) { // ...对每个非 nil 的 tier 独立调用 RetrieveKey,错误用 errors.Join 合并 }NewMasterKeys会独立评估每一个非 nil 的 tier,某个 tier 失败不影响其他 tier 成功;返回的错误是各 tier 错误的errors.Join。日志级别由调用方决定——browser/chromium.(Browser).masterKeys()(browser/chromium/chromium.go)目前把各 tier 的错误统一以Warnf输出,理由是对于一个短生命周期 CLI(默认输出即可见所有 warn 行)而言,"部分失败"与"全部失败"的区分价值不高。
2.5 缓存与注入:每个进程只建一次检索链
检索链在整个进程内只创建一次:browser包按平台分别实现的newCredentialInjector(见 browser/browser_darwin.go、browser/browser_linux.go、browser/browser_windows.go)负责把masterkey.DefaultRetrievers()的产物注入到每一个 Chromium 浏览器、每一个 profile 上,并通过能力接口分派(详见第 8 节)。macOS 的 retriever 内部还用了sync.Once(源码见 masterkey/retriever_darwin.go 的GcoredumpRetriever与KeychainPasswordRetriever),因此多 profile 的浏览器只会触发一次钥匙串弹窗或内存转储。
3. macOS 密钥获取:三种策略与一条链
Chromium 在 macOS 上把加密密码存放在用户登录钥匙串(login keychain)中,账号名与浏览器相关(如"Chrome"、"Brave"、"Microsoft Edge")。
3.1 三种检索策略
① GcoredumpRetriever —— 利用 CVE-2025-24204 漏洞(需 root)
原理:利用gcore二进制持有的com.apple.system-task-ports.read权限绕过 TCC 保护,直接读取securityd(系统安全守护进程)的内存,从中提取钥匙串密钥。整体流程(实现见 masterkey/gcoredump_darwin.go,构建标签为darwin && keychain_gcore):
- 通过
sysctl(kern.proc.all)定位securityd的 PID; - 用
gcore转储该进程内存; - 用
vmmap解析堆区域,在MALLOC_SMALL区域中扫描 24 字节的密钥模式; - 拿每个候选密钥尝试解锁
login.keychain-db。
该策略的显著特征是失败时返回(nil, nil)静默放行——因为"需要 root"是常态而非异常,不值得报警告淹没真正的错误(与 ABERetriever 的设计哲学一致)。
② KeychainPasswordRetriever —— 直接解锁钥匙串(无需 root、非交互)
使用用户的 macOS 登录密码(来自--keychain-pw参数)直接解锁login.keychain-db。底层依赖项目作者自研的keychainbreaker库——一个纯 Go 实现的 macOS Keychain 文件解析与解密器。实现见 masterkey/retriever_darwin.go 的loadKeychainRecords:keychainbreaker.Open()打开钥匙串文件,Unlock(keychainbreaker.WithPassword(password))解锁,再枚举GenericPasswords()记录。
③ SecurityCmdRetriever —— 调用security命令(最后手段)
执行security find-generic-password -wa <label>获取密码。这会触发 macOS 密码对话框,属于交互式操作,所以被放在链路最后。实现上有三个工程细节(masterkey/retriever_darwin.go):
- 使用
exec.CommandContext包裹,设置30 秒超时(securityCmdTimeout),防止用户挂起对话框导致进程卡死; - 按存储名(label)做结果缓存(
sync.Mutex+map[string]securityResult),每个浏览器的密钥只弹一次框; - 对"用户拒绝授权或输错密码"的模糊错误(
security以非零退出但 stderr 为空)做了可读化处理,而不是抛出晦涩的exit status 128 ()。
3.2 链式顺序
macOS 只有 V10 这一层,由DefaultRetrievers(keychainPassword)组装(masterkey/retriever_darwin.go):链的首元素永远是GcoredumpRetriever;仅当密码非空时才插入KeychainPasswordRetriever;最后固定追加SecurityCmdRetriever。顺序如下:
| 优先级 | 策略 | 前置条件 | 是否交互 |
|---|---|---|---|
| 1 | Gcoredump(CVE-2025-24204) | Root | 否 |
| 2 | 钥匙串密码 | --keychain-pw参数 | 否 |
| 3 | security命令行 | 无 | 是(弹窗) |
3.3 PBKDF2 派生参数
无论走哪条路,最终得到的都是原始密码字符串,必须经 PBKDF2 派生为 AES-128 密钥。参数在 masterkey/retriever_darwin.go 的darwinParams中硬编码,与 Chromium 上游os_crypt_mac.mm一致:
| 参数 | 值 | 说明 |
|---|---|---|
| 盐值(Salt) | "saltysalt" | Chromium 上游 os_crypt_mac.mm 中定义的固定盐 |
| 迭代次数 | 1003 | 刻意压低,保证解密性能 |
| 密钥长度 | 16 字节(AES-128) | |
| 哈希算法 | HMAC-SHA1 |
派生由 masterkey/params.go 的pbkdf2Params.deriveKey统一封装(底层调crypto.PBKDF2Key)。
3.4 Keychain 标签映射
每个浏览器在钥匙串里用一段短账号字符串标识自己的条目,通常是浏览器基础名("Chrome"、"Brave"、"Arc"),Edge 是"Microsoft Edge"。变体浏览器共享标签而非各自定义:Chrome Beta 别名到"Chrome",Opera GX 别名到"Opera"。权威映射表位于platformBrowsers()各条目的KeychainLabel字段,见 browser/browser_darwin.go(例如 Chrome 的KeychainLabel: "Chrome"、Edge 的KeychainLabel: "Microsoft Edge")。
4. Windows 密钥获取:DPAPI 与 App-Bound 双轨
4.1 先决认知:Windows 上有两把主密钥
Chromium 在 Windows 的Local StateJSON 里可能存两把主密钥:
- v10 旧版密钥(
os_crypt.encrypted_key,DPAPI 包裹); - v20 App-Bound Encryption 密钥(
os_crypt.app_bound_encrypted_key,IElevator 包裹,Chrome 127+ 引入)。
两者可以在同一个 profile 中共存:Chrome 127+ 对新 Cookie用 v20 加密,但升级前已有的密码和旧 Cookie 仍留在 v10 上。因此取钥层必须独立获取两把密钥,而不是用 ChainRetriever 串起来(详见 4.4 与 issue #578)。
4.2 DPAPI 背景
Windows Data Protection API(DPAPI)是Crypt32.dll提供的内置对称加密服务,以当前登录用户的 Windows 凭据(由其登录密码派生)作为根密钥材料。应用只需调用CryptProtectData加密、CryptUnprotectData解密,无需自行管理密钥。三个关键特性:
- 用户作用域:一个 Windows 用户加密的数据,另一个用户即使在同一台机器上也解不开;
- 机器绑定:加密的 blob 无法在其他机器上解密(除非使用漫游凭据);
- 无密码提示:只要调用进程运行在正确的用户会话下,解密对进程是透明的。
4.3 检索流程
Local State → os_crypt.encrypted_key (base64 字符串) | "DPAPI" 前缀 | DPAPI 加密的 AES 密钥 | |--------------|-----------------------| | 5B (ASCII) | 剩余字节 | → 去掉前缀 → CryptUnprotectData (Crypt32.dll) → 32 字节 AES-256 主密钥实现层面,DPAPIRetriever(masterkey/retriever_windows.go)的步骤是:读取Local State→ 用gjson取os_crypt.encrypted_key→ base64 解码 →校验"DPAPI"前缀(前缀不符或长度过短都会报错,防止拿到损坏数据)→ 调用crypto.DecryptDPAPI解开剩余字节。运行时通过syscall.NewLazyDLL加载Crypt32.dll,以DATA_BLOB结构传入密文,Windows 内部用用户凭据派生解密密钥并返回明文主密钥。
4.4 无需 PBKDF2
与 macOS/Linux 不同,DPAPI直接给出最终的 AES-256 密钥:没有中间密码、没有派生步骤。该密钥原样用于 AES-256-GCM 解密(算法细节见 RFC-003)。
4.5 双 Tier 检索器(V10 + V20)
Windows 的masterkey.Retrievers结构体填两个槽,二者独立运行、互不干扰,而不是"先成功者胜出"的链:
| 槽位 | 检索器 | 数据来源字段 | 机制 |
|---|---|---|---|
| V10 | DPAPIRetriever | os_crypt.encrypted_key | CryptUnprotectData(Crypt32.dll) |
| V20 | ABERetriever | os_crypt.app_bound_encrypted_key | IElevator 反射注入(见 RFC-010) |
V11 在 Windows 上保持 nil(Chromium 在 Windows 不产生 v11 前缀)。装配发生在browser/browser_windows.go的newCredentialInjector中:masterkey.DefaultRetrievers()返回Retrievers{V10: &DPAPIRetriever{}, V20: &ABERetriever{}},再通过Browser.SetRetrievers(r)注入。提取时masterkey.NewMasterKeys独立跑每个槽——一个 tier 失败不影响另一个 tier 成功,因为从 pre-127 升级而来的混合 tier profile 需要"部分成功"才有价值。
为什么不用 ChainRetriever?ChainRetriever是"先成功者胜出"语义:一旦 ABE 返回密钥,DPAPI 就永远不被调用。这对正交的两个 tier是错误的语义——这正是 issue #578 的根因:升级后的 profile 其 v10 加密密码因只取到 v20 密钥而静默解密失败。NewMasterKeys独立评估各 tier 并返回errors.Join合并的逐 tier 错误。
非 ABE 的 Chromium 分支(Opera、Vivaldi、Yandex、360、QQ、Sogou)在platformBrowsers()中不设WindowsABE(默认 false)。调用方因此把Hints.WindowsABEKey留空,ABERetriever对空值直接返回(nil, nil)(masterkey/abe_windows.go 第 31-34 行),NewMasterKeys将其视为"不适用"——对这些分支尝试 ABE 是无操作(no-op)而非失败,它们的 V10 DPAPI 密钥照常工作。
5. Linux 密钥获取:peanuts 与 D-Bus 双槽
5.1 双 Tier 检索器(V10 + V11)
Linux 的Retrievers结构体填两个槽,一一对应 Chromium 在本平台可能产生的两个密文前缀:
| 槽位 | 前缀 | 检索器 | 机制 | Chromium 对应物 |
|---|---|---|---|---|
| V10 | v10 | PosixRetriever | PBKDF2("peanuts") | kV10Key(对应上游PosixKeyProvider) |
| V11 | v11 | DBusRetriever | PBKDF2(D-Bus Secret Service 密码) | kV11Key(对应上游FreedesktopSecretKeyProvider) |
V20 在 Linux 上保持 nil(App-Bound Encryption 仅限 Windows)。v12(Chromium 的SecretPortalKeyProvider,Flatpak / xdg-desktop-portal 方案)是独立的一层,尚未实现——decryptValue的CipherV12分支会抛出可操作的"已知缺口"错误(见 browser/chromium/decrypt.go 与 crypto/version.go)。
DBusRetriever(masterkey/retriever_linux.go)查询 D-Bus Secret Service API(由gnome-keyring-daemon或kwalletd提供):连接dbus.SessionBus()→keyring.GetSecretService→OpenSession→ 遍历所有 collections 与 items,查找标签与浏览器存储名匹配的条目 → 取回密钥并派生。它填 V11 槽,因为Chromium 只有在钥匙串访问成功时才会发出 v11 前缀。
PosixRetriever使用硬编码密码"peanuts",派生出的固定 16 字节 AES-128 密钥即 kV10Key。它填 V10 槽,因为 Chromium 对用此密钥加密的数据发出 v10 前缀。该检索器永远确定性成功——即使没有任何钥匙串守护进程。
5.2 为什么是两个槽而不是一条链
一个 profile 完全可以同时携带 v10 与 v11 密文——只要主机在"有钥匙串的桌面会话"与"无钥匙串的 headless 会话"之间切换过(例如一台先跑 headless shell、后进完整桌面的笔记本)。旧实现ChainRetriever{DBus, Fallback}是"先成功者胜出":只要 D-Bus 成功,peanuts 就永远不被调用,导致 v10 密文无法解密。现在的拆分正是对 Windows V10/V20 修复(4.5 节)与 issue #578 根因逻辑的镜像:不同密文前缀对应不同密钥来源,取钥层必须独立产出两把密钥,而不是挑"一个获胜密钥"。
5.3 PBKDF2 派生参数(远弱于 macOS)
两种策略产出的都是密码字符串,经 PBKDF2 派生。参数在 masterkey/retriever_linux.go 的linuxParams中定义,与 Chromium 上游os_crypt_linux.cc一致:
| 参数 | 值 | 说明 |
|---|---|---|
| 盐值(Salt) | "saltysalt" | 与 macOS 相同的固定盐 |
| 迭代次数 | 1 | 与 macOS 的 1003 形成鲜明对比 |
| 密钥长度 | 16 字节(AES-128) | |
| 哈希算法 | HMAC-SHA1 |
迭代次数为 1意味着 PBKDF2 退化成"带密钥的 HMAC",完全没有密钥拉伸(key-stretching)效果。再叠加广为人知的回退密码"peanuts",Linux 上不依赖钥匙串的 Chromium 加密几乎可以被任意破解——这是 RFC-006 明确指出的安全事实,也是取证工具能够工作的基础。
5.4 Keychain 标签映射
Linux 的 D-Bus 标签遵循"<name> Safe Storage"惯例,但多数浏览器别名到一个小集合上,真正的独立标签只有三个:"Chrome Safe Storage"、"Chromium Safe Storage"、"Brave Safe Storage",其余浏览器全部映射到三者之一。权威映射在 browser/browser_linux.go 的platformBrowsers()中,例如:
- Chrome / Chrome Beta / Vivaldi →
"Chrome Safe Storage"; - Chromium / Edge / Opera →
"Chromium Safe Storage"; - Brave →
"Brave Safe Storage"。
6. 平台汇总
| 平台 | 检索器(填充的槽位) | PBKDF2 | 密钥尺寸 |
|---|---|---|---|
| macOS | V10 = 链(Gcoredump → KeychainPassword* → SecurityCmd) | 1003 次迭代 | AES-128 |
| Windows | V10 = DPAPIRetriever;V20 = ABERetriever(Chrome 127+) | 无 | AES-256 |
| Linux | V10 = PosixRetriever("peanuts" kV10Key);V11 = DBusRetriever(钥匙串 kV11Key) | 1 次迭代 | AES-128 |
* 仅当解析出非空密码时包含——要么来自--keychain-pw参数,要么来自交互式 TTY 提示。
7. Safari 凭据提取:刻意独立于 Retriever 体系
Safari不是Retriever接口的使用者。它有自己的凭据提取路径browser/safari/extract_password.go,直接使用 keychainbreaker 库列出login.keychain-db中的InternetPassword记录(browser/safari/extract_password.go 的getInternetPasswords:keychainbreaker.Open()→ 若有密码则TryUnlock→ 枚举 InternetPassword 记录)。这是刻意的架构选择,而非疏漏。
7.1 为什么 Safari 不共享 Chromium 链
| 维度 | Chromium 链 | Safari 直接访问 |
|---|---|---|
| 输出 | 16 字节 AES-128 密钥 | InternetPassword记录列表 |
| 用途 | 解密 Login Data 数据库 | 记录本身就是凭据 |
| 消费者数量 | 10+ 个 Chromium 变体 | 1(仅 Safari) |
| 失败模式 | 硬失败(无密钥 → 无法解密) | 软失败(降级为仅元数据) |
| 缓存收益 | 高(多 profile、多浏览器) | 无(单浏览器、单次调用) |
把 Safari 硬塞进Retriever接口需要返回与[]byte不同的类型,与接口"主密钥抽象"的既定用途矛盾;为单一消费者再造一条平行的 "InternetPassword 链" 则属于过度设计——它没有任何值得链式串联的回退策略。
注意"失败模式"一行:Chromium必须拿到主密钥否则提取整体失败,所以需要不断升级的策略链;Safari 可以优雅降级——即使钥匙串无法解锁,仅导出元数据(URL 和用户名,无明文密码)仍有价值,所以"试一次 keychainbreaker,失败就告警"足够。
7.2 通用规则
每个浏览器包拥有自己的凭据获取策略。
masterkey存在的意义仅限于在 Chromium 变体家族间共享检索逻辑。新增浏览器实现应效仿 Safari 与 Firefox——自己掌控凭据代码。
该规则在仓库中已经生效的证据:
- Firefox(browser/firefox/firefox.go)不 import
masterkey或keychainbreaker,从key4.db经内部 NSS PBE 派生密钥,见 RFC-005; - Safari直接用 keychainbreaker 取
InternetPassword记录; - Chromium 变体全部走
masterkey,因为它们共享同一条链并受益于共享的sync.Once缓存。
未来贡献者若要新增一个从 Keychain 读凭据的 macOS 浏览器,应把访问逻辑写进该浏览器的包,而不是扩展masterkey;只有新浏览器是契合现有主密钥链的 Chromium 变体时,才值得扩展masterkey。
7.3--keychain-pw密码的去向:两个刻意不统一的能力接口
macOS 登录密码在启动时由 browser/browser_darwin.go 的resolveKeychainPassword解析一次,然后通过按平台实现的闭包newCredentialInjector分发给两个消费者。该闭包捕获检索链和原始密码,按每个 Browser 恰好满足的能力接口分发:
| 消费者 | 能力接口 | 定义位置 | 载荷 |
|---|---|---|---|
| Chromium 浏览器 | KeyManager | browser/browser.go | masterkey.Retrievers结构体(V10/V11/V20 槽;未用 tier 为 nil) |
| Safari | KeychainPasswordReceiver | browser/browser.go | 原始string |
两个接口刻意不统一:它们承载的抽象不同——一个交给浏览器预先组装好的检索链,另一个交给浏览器一个凭据令牌去解锁自己的访问路径。强行统一会造出一个泄漏抽象的、无真实共享语义的多态接口。
工程细节:resolveKeychainPassword在构建检索链之前还会先用 keychainbreaker 做一次提前TryUnlock校验,于是错误的密码会在启动时就以告警形式暴露,而不是拖到提取中途才失败。为此付出的"打开钥匙串两次"(一次校验、一次在KeychainPasswordRetriever内)的小成本,换来了明显更好的用户体验。此外该函数遵循严格的降级逻辑:无--keychain-pw且 stdin 非 TTY 时直接返回空字符串,受钥匙串保护的数据将以元数据形式导出。
8. 从密钥到明文:解密链路的版本分派
拿到MasterKeys之后,解密由 browser/chromium/decrypt.go 的decryptValue完成,它按密文版本前缀分派到对应 tier(版本识别见 crypto/version.go 的DetectVersion):
- v10→
masterKeys.V10。这里有一个跨平台精巧设计:v10 密文的算法由密钥长度决定——32 字节密钥走 AES-GCM(Windows),16 字节密钥走 AES-CBC(macOS/Linux)。按密钥长度分派让"跨主机解密"(例如在 macOS 上解密 Windows 导出的密钥)与操作系统无关; - v11→
masterKeys.V11,Linux 专用 AES-CBC,算法与 Linux v10 相同但密钥来自钥匙串(kV11Key)而非 peanuts(kV10Key),因此两个 tier 需要不同密钥; - v20→
masterKeys.V20,跨平台 AES-GCM(Chrome 127+ ABE),线格式与 Windows v10 相同; - v12→ 明确报"未实现"错误(Chromium SecretPortal / Flatpak 的已知缺口),而不是模糊的"不支持";
- 缺失 tier 密钥会以密文级解密错误呈现,提取层将其当作空明文处理而非致命错误(browser/chromium/decrypt.go 注释明确说明)。
从取钥到解密的完整调用链是:DiscoverBrowsersWithKeys→newCredentialInjector(注入Retrievers)→Browser.Extract→b.masterKeys()(sync.Once缓存,内部走ExportKeys→masterkey.NewMasterKeys)→decryptValue。buildHints还会把Local State拷贝进临时目录(Windows DPAPI/ABE 从一个进程拥有的路径读取),见 browser/chromium/chromium.go。
9. 实战:主密钥的跨主机导出与离线解密
主密钥检索能力不止服务于本机提取,还能支撑跨主机解密:在来源主机上导出主密钥(dumpkeys),在分析主机上离线解密拷贝过来的数据,甚至可解出分析主机无法安装引擎的浏览器(如在 macOS 上解密 Sogou/QQ 浏览器数据)。这得益于MasterKeys的 JSON 可序列化结构(masterkey/dump.go 中Dump/Vault类型,每个Vault携带Keys MasterKeys)。标准工作流(详见 README.md 的 Cross-host decryption 一节):
# 来源主机:导出主密钥 + 打包解密所需的文件 hack-browser-data dumpkeys -o keys.json hack-browser-data archive -o browser-data.zip # 拷贝到分析主机后离线解密 hack-browser-data restore --keys keys.json --data-zip browser-data.zipkeys.json包含明文主密钥,须按秘密对待(dumpkeys -o以0600权限落盘)。macOS 场景下,dumpkeys同样接受--keychain-pw参数来免交互解锁钥匙串:
| 标志 | 简写 | 默认 | 说明 |
|---|---|---|---|
--browser | -b | all | 目标浏览器(all|chrome|edge|...) |
--output | -o | stdout | 输出文件(0600权限);省略则输出到 stdout 便于 SSH 管道 |
--keychain-pw | macOS 钥匙串密码 |
restore不依赖分析主机的本地浏览器表,可恢复的浏览器恰好是keys.json中的 vaults;数据可经--data-zip(archive产物)或--data-dir(手工拷贝的 User Data 目录)提供。这是第 2.4 节"按 tier 独立取钥"设计在跨主机场景下的直接红利——一个 profile 若同时含 v10 与 v20 密文,两把密钥都会被完整导出并逐层解密。
10. 小结与延伸阅读
RFC-006 描绘的取钥架构可以概括为三句话:按平台找对保险柜(Keychain / DPAPI / D-Bus),按密文版本分对槽(V10/V11/V20),按失败成本排好队(链式 vs 独立分层)。ChainRetriever只用于 macOS 上"同层不同手段"的递进;Windows 与 Linux 的"不同层不同来源"则用MasterKeys的多槽结构独立求值——这个区分是 issue #578 用教训换来的正确语义。Safari 与 Firefox 则证明:当消费者形态与 Chromium 差异过大时,自管凭据代码反而是更清晰的选择。
关键实现与文档索引:
- 检索抽象与链式实现:masterkey/retriever.go、分层容器 masterkey/masterkeys.go
- macOS 检索器与 PBKDF2 参数:masterkey/retriever_darwin.go、CVE-2025-24204 转储 masterkey/gcoredump_darwin.go
- Windows DPAPI / ABE 检索器:masterkey/retriever_windows.go、masterkey/abe_windows.go
- Linux D-Bus / peanuts 检索器与参数:masterkey/retriever_linux.go
- 注入与平台浏览器表:browser/browser.go、browser/browser_darwin.go、browser/browser_linux.go、browser/browser_windows.go
- 版本分派解密:browser/chromium/decrypt.go、crypto/version.go
- Safari 独立路径:browser/safari/extract_password.go
- 跨主机导出格式:masterkey/dump.go,CLI 用法见 README.md
外部参考资料(RFC-006 原文标注,此处仅作文字说明):Chromium 上游os_crypt_mac.mm/os_crypt_win.cc/os_crypt_linux.cc中的盐值与密钥派生常量;CVE-2025-24204 的公开 PoC 与 Apple 安全公告;微软文档中CryptUnprotectData的 DPAPI 说明。相关 RFC:RFC-003(Chromium 加密机制)、RFC-005(Firefox NSS 加密与密钥派生)、RFC-010(Chrome ABE 集成)。
- 网络安全
- 应用安全
- 密码学
- CLI
【免费下载链接】HackBrowserData
Extract and decrypt browser data, supporting multiple data types, runnable on various operating systems (macOS, Windows, Linux).
相关推荐
HackBrowserData 深度解析:RFC-012 与 Yandex 浏览器两级密钥解密方案
HackBrowserData 深度解析:RFC 012 与 Yandex 浏览器两级密钥解密方案 本篇技术指南以仓库内 RFC 012: Yandex Bro
网络安全应用安全密码学CLIHackBrowserData 源码级解析:Chromium 多平台加密体系(RFC-003)与 v10/v11/v20 密文解密实战
HackBrowserData 源码级解析:Chromium 多平台加密体系(RFC 003)与 v10/v11/v20 密文解密实战 HackBrowserD
网络安全应用安全密码学CLIWuWa-Mod AES密钥获取:加密解密技术的深度解析
WuWa Mod AES密钥获取:加密解密技术的深度解析 欢迎来到WuWa Mod AES密钥获取的终极指南!作为一款专门为《鸣潮》 Wuthering Wav
游戏开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考