指纹浏览器如何工作:完整的技术解析

指纹面、一致的身份生成、磁盘上的存储隔离、按配置文件分流的网络,以及这套方法坦率的局限。
指纹浏览器做四件事:分别存储每个配置文件的浏览数据,控制每个配置文件所报告的设备身份,独立转发每个配置文件的流量,并让这三者长期保持一致。本文从机制层面逐一讲解每件事是怎么做到的,最后说明这套方法做不到什么。
指纹面
先看页面能读到什么,因为其余一切都是对此的回应。任何网站上的脚本都可以向浏览器提问,而答案描述的就是这台机器。这些指纹面可以自然地分成几组。
| 指纹面 | 页面读取的内容 |
|---|---|
| Navigator | 浏览器名称与版本、平台、语言列表、报告的处理器核数和内存、触控支持 |
| 屏幕 | 宽度、高度、可用工作区、色深、设备像素比 |
| Canvas | 在屏幕外绘制文字和图形所产生的确切像素,经哈希后得到稳定的值 |
| WebGL | 图形厂商与渲染器字符串,以及渲染场景的哈希 |
| 音频 | 振荡器输出经音频处理图后的结果,因软硬件栈不同而有差异 |
| 字体 | 安装了哪些字体,通过文本测量推断得出 |
| 时区与时钟 | 报告的时区和偏移量,并与流量到达的来源地对照 |
| 网络 | 地址本身、WebRTC 眼中的连接候选,以及 TLS 握手的形态 |
没有哪一行能单独识别一台机器。识别靠的是组合,而谎言也恰恰在组合处被识破:无法属于同一台真实机器的数值,比任何单个数值都更扎眼。
采集既廉价又悄无声息。表中的每个值,任何页面都能通过公开文档的 API 获取,无需任何权限提示,而且大部分在访问的第一秒内就已到手。无论你对这种做法持什么立场,它都是现代网络的既定事实,也是指纹浏览器所要对抗的基线。
一致的身份是如何生成的
最朴素的做法是拦截每一个提问,然后把答案随机化。这行不通,原因在于结构:这些答案描述的是同一台机器,彼此相关,而各自独立的随机答案会破坏这种相关性。Windows 的 User-Agent 搭配只有 macOS 才自带的字体、任何笔记本都没出过的屏幕尺寸、从未与所声称平台共存过的图形字符串,每一种都是检测方能轻易验证的矛盾。
所以,一个真正可用的指纹浏览器是把身份当作一个整体来生成的。它从一类真实且常见的机器出发,所有数值都由这个选择推导而来。平台决定合理的字体列表、图形字符串和触控支持;屏幕取自这类机器实际出货的尺寸;语言和时区取自配置文件预期所在的地区。这些数值彼此吻合,因为它们是依据对同一台机器的同一份描述一起选定的。
第二个特性同样重要:这套数值是被保存下来的。它在配置文件创建时就写入其中,每次会话报告的都是相同的值,因为一台每次访问都在变的设备,描述的是没人拥有的硬件。稳定性,几乎就是“可信”的全部含义。
磁盘上的存储隔离
存储这件事最不起眼,却也最绝对。每个配置文件拥有一个目录,浏览器持久化的一切都在其中:Cookie 数据库、Local Storage、IndexedDB、HTTP 缓存、Service Worker 注册信息、扩展状态、历史记录和保存的凭据。两个配置文件就是两个互不相交的目录,每个运行中的浏览器都指向自己的那一个。
要检查的正是完全不相交这一点,因为部分隔离会悄悄失效。Cookie 很容易分开;缓存却能通过它保留的资源识别出回访的浏览器;Service Worker 可以保存标识符,并在两次访问之间持续运行;IndexedDB 则是现代应用存放它们真正信任的记录的地方。只覆盖了那几个有名的存储、却和其他存储共用的隔离,隔离的只是问题中看得见的那一部分。
持久化是另一半。会话会积累价值:已通过的安全验证、已建立的登录历史、回访者的行为轨迹。配置文件在会话之间保留它的目录,这才使账号操作成为可能。关闭时的写出让目录得以持久,这也是为什么在会话中途强行结束配置文件进程,可能会丢掉刚刚建立的会话。
网络路由
每个配置文件都带有自己的路由,在其浏览器启动时生效,所以流量会经由该配置文件的代理出去,而不触及系统设置或其他任何配置文件的路由。
- 代理按配置文件附加,而不是按机器。普通浏览器使用系统代理;这里每个运行中的配置文件都通过自己的端点连接。
- 域名解析同样走这条路由,因为请求来自一个国家、而查询却来自另一个国家,在网络层就是一种矛盾。
- WebRTC 被约束得与路由一致。放任不管的话,它会暴露代理从未经手的连接候选。
- 时区和语言与路由所在国家相匹配,因为地址是唯一无法生成的值,其余的叙述就必须与它保持一致。
路由也是最值得独立验证的部分,因为它是从第三方购买的。在分配之前先测试端点,并读取实际观察到的地址而不是宣传的地址,就能在问题还只是配置问题、尚未变成账号问题时将其发现。
浏览器自身改变了什么
工具改变页面所得答案的方式有两种,二者的区别是这个话题中最具技术性的分岔点。
| 方式 | 工作原理 | 弱点 |
|---|---|---|
| 脚本注入 | 扩展或注入脚本覆盖页面调用的函数,使 Canvas 读取和 navigator 查询返回替换后的值 | 覆盖本身是可见的。被打过补丁的函数可以被识别出打过补丁,而注入方遗漏的任何指纹面仍会返回真实值 |
| 在浏览器自身代码内部 | 浏览器自己的实现,在产生真实值的代码路径上返回配置文件中保存的值 | 构建和维护的工作量要大得多,这正是专门打造的工具有别于扩展的地方 |
注入方式是这个类别的起点,其天花板也早已为人所知:页面可以检查原生函数看上去是否仍然“原生”,还可以通过注入没有覆盖到的路径读取到同一个指纹面。因此,专门打造的指纹浏览器把改动做在浏览器内部,使得无论经由哪条路径读取,实现所报告的都是保存下来的身份。
打开一个配置文件究竟做了什么
- 加载保存的身份:一套一致的设备数值,与上次完全相同。
- 浏览器针对该配置文件自己的目录启动,因此每个持久化存储都属于这个配置文件。
- 应用路由:代理、经由代理的域名解析,以及受其约束的 WebRTC。
- 会话运行,在目录内积累状态。
- 关闭时将状态写出,这样下次打开就能延续同一台设备、同样的存储和同一条路由。
这份清单上的每一项,都可以从外部检查。打开指纹检测页面两次,比较两次之间的数值;查看磁盘上的配置文件目录;读取实际观察到的地址。这一类别的工具应当欢迎这样的检查,因为机制本身就是产品。
坦率的局限
这套方法有其边界,了解边界是称职使用的一部分。
- 它无法让浏览器变得不可识别,况且这样的说法也无从衡量。现实的目标,是让配置文件看起来像一台普通的、内部一致的机器,并保持稳定。
- 它无法修正你自己引入的矛盾。路由在一个国家、时区在另一个国家,属于配置错误,工具只能让正确的配置成为最容易做到的那个。
- 它不涵盖行为。时间、数量和节奏会与环境一并被读取,一批步调一致的账号,无论每个环境多干净,都会暴露自己。
- 它不涵盖声明出来的身份。共用的找回信息、共用的支付工具和重复使用的文字,会通过平台自己的记录把账号关联起来,这发生在远高于浏览器的层面。
- 它不改变平台规则。隔离分开的是合法的环境。平台允许什么由平台决定,对于不受我们控制的平台,这里不承诺任何结果。
请把这些局限当作定义来读,而不是当作失望。指纹浏览器是一件保持一致性的工具:独立的存储、稳定且可信的身份、与之匹配的路由,按配置文件整体维系,并在每次打开时原样重现。这就是全部机制,关于这个类别的其他一切说法,都不过是它的变体。

