2. 除了隐身模式,我们还需要什么?
1. CamouFox不是又一个浏览器壳子,而是对“隐私是默认状态”的一次实践
说起浏览器,很多人第一反应是Chrome、Safari,或者Firefox。但如果你把“Fox”这个后缀放进项目名,基本上就默认了和Firefox千丝万缕的关系。camofox-browser这个项目的起名思路很简单——“camo”是迷彩,fox是狐狸,合起来就是一只穿迷彩的狐狸:在网络上行走时不被认出你是谁、来自哪里、用什么设备。
这听起来很像常见的“隐身模式”“无痕浏览”对吧?但这里有个普遍误解:无痕浏览防的是本地设备上的后来者,它不防网站、不防广告联盟、不防数据经纪商。你关掉窗口之后,本地历史记录确实清掉了,但网站早就在服务端记下了你的访问痕迹、设备指纹、行为轨迹。camofox-browser想做的,是把“隐身”从一种临时状态变成浏览器的默认姿态——从启动那一刻起,你的浏览器就在主动降低自身身份信号的广播强度。
这个项目的核心价值在于:它不追求“完全匿名”这种技术乌托邦,而是专注于制造稳定的“普通性”。什么意思?指纹追踪的核心逻辑是“独一无二”,哪怕信息点都很微弱,几十个维度组合起来就能形成高辨识度。而camofox的思路是把这些维度往人群交集方向推:让所有运行camofox的人,在外界看来都有相似甚至相同的指纹特征。换句话说,它牺牲了一部分个性化体验,换取了群体掩护。
本文适合谁看?如果你对浏览器指纹、防追踪、反检测技术感兴趣,或者想在不依赖远程服务的前提下把本地浏览器改造得更“低调”,这篇文章的内容基本就是为你准备的。我会从选型、架构、实现细节、实测数据几个角度,把这个项目的完整思路拆开来讲。
3. 技术选型:为什么站在Firefox的肩膀上,而不是从Chromium另起炉灶
3.1 Chromium系浏览器在隐私这件事上的结构性弱点
做浏览器,绕不开一个选择:基于Chromium还是Firefox。前者有更完整的生态、更好用的DevTools、更成熟的渲染管线,后者则有一个关键优势——隐私能力是在架构层面原生支持的。
我们先聊聊Chromium为什么不适合做这个项目。Chromium本身并不是不讲隐私,谷歌也一直在推Privacy Sandbox这类东西,但问题是——Chromium的隐私方案本质上是广告巨头的商业策略,它在“保护隐私”和“维持广告测量能力”之间走钢丝。更现实的问题是,Chromium的源码体量极大,想在里面做深度指纹混淆,你得在GPU进程、网络进程、渲染进程之间反复横跳,牵一发动全身。
举个例子:Canvas指纹是对抗最常用的向量之一。Chromium里Canvas渲染走的GPU加速路径在不同操作系统、不同显卡驱动下产生完全不同的像素输出,这种差异本身就成了指纹信号。你想人为统一它,就得接管整个光栅化流程,这在Chromium的架构里复杂度极高。
3.2 Firefox的flag体系给了项目“底层手术”的空间
Firefox不一样。它在about:config里保留了大量的底层开关,其中不少就是为了隐私场景准备的。更重要的是,Firefox的扩展接口权限比Chromium更大(比如更底层的webRequest拦截、代理接口等),而且代码库相对干净,做改动时能快速定位到具体模块。
CamouFox采用了Firefox的ESR(Extended Support)分支作为底座。为什么不用Nightly或Beta?原因很简单:这个项目要干预的是浏览器和网页之间的交互协议,如果上游每周都变一次接口行为,项目维护成本会失控。ESR版本一年只升级一次大版本,中间只打安全补丁,非常适合在上面做稳定的二次开发。
选型过程中我对比了三个候选方案的实测结果,供参考:
| 候选方案 | 修改深度 | 指纹改动成本 | 维护难度 | 结论 |
|---|---|---|---|---|
| Chromium源码二次开发 | 很深 | 高(GPU/网络层复杂度高) | 很高 | 放弃 |
| Firefox ESR + 用户脚本 | 浅 | 低(可绕过大部分Web API) | 中 | 可用但不够彻底 |
| Firefox ESR源码编译 + 定制组件 | 中深 | 中(可控性强) | 中 | 选定 |
最终的方案是:Firefox ESR源码编译 + 定制隐私配置 + 定制扩展组件。前两者处理90%的场景,扩展组件负责兜底剩下的10%——包括那些浏览器本身没有对外暴露配置项的细节。
4. 核心机制之一:Canvas指纹的“伪装”与“噪声注入”
5. 一切行为的起点:指纹采集机制和对抗方法论
在讲camofox里具体做了什么之前,先花点篇幅把浏览器指纹这件事本身讲透,否则后面的配置和参数看不懂。
5.1 指纹追踪的底层逻辑:你能被认出来,不是因为某一个特征
浏览器指纹能工作,靠的是“组合拳”。单个维度来看,你的User-Agent可能是Chrome/124,Windows 10的也有几亿人;你的屏幕分辨率可能是1920x1080,烂大街;你装了哪些字体、用了什么时区、显卡渲染出来的Canvas像素长什么样——每一项都不足以定位你。
但当这些维度拼在一起时,组合空间就大了。用一个简单计算来说:如果你的UA有100个变体(实际上远远不止)、屏幕分辨率有200种组合、字体列表有1000种变体、Canvas每8位色深可能有几十种偏差模式,你把它们乘起来,空间是以亿为单位的。识别你的并不是某一把钥匙,而是“这把钥匙全世界的备份不超过几把”的事实。
Visa的研究数据大家可以去翻一翻:54%的浏览器指纹在14天内保持稳定,平均每个指纹持续18天。这带来的直接推论是——防指纹的核心目标是让“钥匙”的副本数量尽可能多,也就是让所有camofox用户的指纹尽可能一致,而不是让某个特征变得“空无一物”。
5.2 常见的指纹采集向量分类
我们按攻击面来给指纹采集向量做一个分类,后续章节的配置逻辑都基于这个框架:
- HTTP层采集点:User-Agent、Accept-Language、DNT头、Sec-CH-UA等客户端提示
- JS API层采集点:Canvas指纹(利用相同字符串在不同环境渲染出不同像素)、WebGL渲染器信息、AudioContext处理的声波数据、Navigator对象暴露的属性集合
- 系统环境采集点:时区、语言列表、字体枚举结果、硬件并发数(navigator.hardwareConcurrency)、设备内存(navigator.deviceMemory)
- 行为与存储层采集点:localStorage里的历史键名、Cookie名称模式、标签页被切走时的页面可见性变化
CamouFox在每个类别里都做了处理,但不代表所有处理都是“改成一样”——有些维度如果全都一样,反而会变成此地无银三百两。比如后面会提到的时区,整体策略就不是一刀切。
5.3 先立一个原则:别做“零特征”,要做“大众脸”
项目的第一版,我走了一段弯路:企图把所有指纹向量清零。UA改成空?不行,HTTP协议规范要求必须有。Canvas输出统一成纯黑?这反而成了最强特征,因为正常人浏览器在家用电脑上不会渲染出纯黑Canvas。
后来我调整了思路,准则变成:一切指纹值必须是“真实存在的大多数人会呈现的取值”。这个原则贯穿了整个项目的实现。比如UA不做成通用统一,而是做成“针对当前操作系统版本生成一个最常见组合”;Canvas噪声不做成固定图案,而是用稍微偏移的随机数曲线,模拟“另一台真实设备”的渲染差异。
6. 深入实现:camofox-browser的指纹混淆引擎架构
CamouFox的指纹混淆系统分三层:配置层、注入层、兜底层。每一层解决不同粒度的问题。
6.1 配置层:基于about:config的全局项定制
第一层是浏览器自带配置项的工程化整理。Firefox的about:config里跟隐私相关的条目有几百个,但多数用户根本不会去逐条调整。CamouFox通过内置的user.js文件,在启动时直接覆盖关键条目。
比如这些是必须处理的经典项:
privacy.resistFingerprinting = true privacy.trackingprotection.enabled = true webgl.disabled = false webgl.enable-debug-renderer-info = false media.peerconnection.enabled = true media.peerconnection.ice.default_address_only = true media.navigator.enabled = false dom.webnotifications.enabled = false geo.enabled = false network.http.sendRefererHeader = 2注意几个容易踩坑的地方:privacy.resistFingerprinting这个开关是Mozilla官方的防指纹开关,打开后会把报告的时间戳精度、时区行为、UA格式等一揽子调整掉,但它的问题是“太敏感”——有些站点会直接因为UA太奇怪而拒绝服务。
所以在camofox里,这个开关打开之后,还要配合privacy.resistFingerprinting.autoDeclineNoUserInputCanvasPrompts这类细粒度条目来处理弹窗。同时,对UA兜底做了一次额外干预:通过general.useragent.override把UA定制成“和原生Firefox ESR当前版本一致”的格式,避免因为resist模式导致的UA缺失。
6.2 注入层:扩展组件如何干预指纹API
配置层只能处理Firefox暴露了配置项的维度,对付不了Canvas这类完全由JS可以直接调用、 没有配置项的环境。这一层必须靠扩展组件在页面脚本执行前注入替身代码。
CamouFox的扩展组件核心抽取了三个典型的API覆盖模块:
Canvas覆盖模块:拦截HTMLCanvasElement.prototype.toDataURL和toBlob方法。拦截后并不直接返回固定值,而是先允许原方法执行一次,拿到原始渲染结果,再对原始像素做“人为扰动”——在RGB通道上叠加一个基于噪声种子的随机偏移量。这个噪声种子由浏览器内部随机生成,但同一浏览器内短期保持稳定。
为什么用“原结果加扰动”而不用“返回固定图”?因为Canvas渲染结果经常被国内的企业级在线验证系统用来做人机校验。如果返回一张和用户实际操作无关的图片,会被判定为自动化工具。加扰动的策略保留了原画面的内容结构,只是像素级有偏差——这正好模拟了另一台设备上相同页面的真实渲染差异。
AudioContext覆盖模块:指纹采集里,AudioContext使用振荡器播放特定频率的声波,再通过分析节点获取波形数据。虽然现代浏览器对AudioContext的输出做了一定随机化,但不同声卡驱动、音频后处理管线还是会留下痕迹。camofox采用的方法是:在Audiobuffer经过分析节点前,对Float32Array类型的频域数据做一次低幅值的噪声注入。幅度控制得很小(大约在-0.0001~0.0001区间),不会产生可感知的音频失真,但足以破坏指纹一致性。
Navigator属性覆盖模块:包括navigator.hardwareConcurrency、navigator.deviceMemory、navigator.platform、navigator.languages。这部分不是简单改返回值,而是按“伪随机但符合真实分布”的原则生成。举个例子:硬件并发数设定为2或4或8之一(这也是Intel和AMD主流CPU的典型线程数),而不是统一设成某个固定值。
6.3 兜底层:处理“网络层指纹”和“字体指纹”
前两层管的是Web API暴露面的伪造,但网络上还有一个被动信息源:TLS握手特征。不同浏览器、不同操作系统底层的TLS实现有细微差别——支持哪些密码套件、握手顺序、扩展字段的排列——网络层的被动观察者可以据此识别出你用的是哪类浏览器。
camofox在ESR源码编译时打了一个补丁,调整了默认TLS扩展的排序,使其与原生Firefox ESR的握手特征基本一致。这块工作比较繁琐,但效果很直观:被动网络指纹识别工具(常见于商业风控系统)看到的“TLS指纹”,就是一台正常Firefox浏览器。
接着是字体枚举指纹。网站上通过Flash或者Canvas API(早期常用,现在多数被CORS限制)可以枚举你系统里装的所有字体。camofox的处理方式不是清空字体列表——那会导致中文字体渲染回退到系统默认,页面布局错乱——而是在Canvas 2D的measureText阶段返回一个稍作偏移的文本宽度值,让通过“测量文字宽度判断字体是否安装”的攻击手段失效,同时不影响页面真实渲染。
7. 存储隔离与数据自毁:把痕迹消灭在本地
指纹混淆只是“让站点认不出你”,还解决不了“站点这次记住你、下次又认出你”的问题。要真正做到默认隐身,存储层面的隔离和自毁同样关键。
7.1 分容器存储机制
CamouFox采用基于标签页容器的存储隔离策略:每个标签页默认使用独立的Cookie存储分区和独立的localStorage空间。这里的实现并不是简单调用Firefox的ContainerAPI,而是进一步对IndexedDB也做了分区——因为现在很多Web应用把用户状态放IndexedDB里。
实际体验上的变化是:同一个网站,你在一个标签页里登录了账号,新开一个标签再访问它仍然是未登录状态。这对需要同时开多个社交媒体账号、多套工作台的人来说特别实用。代价是某些嵌入了第三方登录SDK的站点会要求重新授权,这是可以接受的。
7.2 “会话结束后自毁”与“赦免名单”
除了容器隔离,camofox还内置了会话结束自毁策略:关闭浏览器时抹掉超过7天的Cookie、清空ServiceWorker缓存、重置Canvas噪声种子。这些操作全部走Firefox的存储清理接口,不涉及文件系统层面的删除,避免和杀毒软件产生权限冲突。
实际操作中我发现的坑是——如果完全清理干净,部分站点再次访问时的“新用户”状态反而是一个特征。因为爬虫和自动化工具的典型特征就是“没有历史状态”。camofox的做法是引入一个“赦免名单”机制,也就是保留少量站点的Cookie和存储,比如搜索引擎偏好、网盘登录态这类高频站点。这个名单平时不启用,默认状态该清就清,只有手动确认信任的站点才加入名单。
8. 外部行为侧信道:时区、语言、WebRTC泄漏怎么处理
指纹API只是攻击面的一部分,“侧信道”才是很多人容易忽略的地方——你的浏览器甚至在响应某些功能请求时,就会泄露环境信息。
8.1 时区:不能乱改,但也不能完全暴露本地时区
很多新手做时区伪装,直接改成UTC+0。这带来一个直接问题:如果一个中文用户访问搜索引擎,它的推荐内容、个性化广告全变成英文语境的,这本身就成了一个异常信号。
camofox的方案是:给用户三个选项——跟随系统时区(最隐蔽,因为和你实际行为完全一致)、固定伪装时区(适合需要跨区测试的少数场景)、随机时区(不推荐,仅在特殊工况下使用)。默认选第一个。为什么?因为时区不是一个孤立信号,它和你访问时间段、操作习惯、访问的网站语言集合都有相关性。如果时区做了伪装,而其他行为信号还是本地时间规律,反而容易被风控模型判定为“身份不一致”。
8.2 语言偏好:按概率分布设置语言列表
navigator.languages这个属性非常容易被忽略,但它是一个高权重指纹向量——一个声称自己是Windows英文系统的浏览器,如果语言列表里第一个是“zh-CN”,就会很可疑。
camofox的处理是:语言列表的默认值和系统语言保持一致,第二语言按机器所在地的概率分布生成(比如中国大陆用户的第二语言70%是英语,30%是日语或韩语)。这个分布不是随便拍的,是参考了浏览器使用统计里关于常用语言组合的数据。另外,每次会话刷新后语言列表会按分布重新随机一次,但切换幅度控制在30%以内,避免同一站点两次访问看到完全不同的语言配置而触发风控。
8.3 WebRTC:最容易被忽略的IP泄漏通道
WebRTC本来是给音视频通话设计的,但它的ICE协商过程会触发浏览器直接向STUN服务器发送UDP包,这个过程中本地IP地址会被暴露——即使你开着代理也一样。Firefox的media.peerconnection.ice.default_address_only配置项可以缓解这个问题,但还不够彻底。
CamouFox在编译层面直接改了WebRTC的ICE candidate生成逻辑:默认情况下不生成任何host地址类型的candidate。也就是WebRTC通信只在双方都采用中继路由时进行。这会导致P2P传输性能下降(延迟可能从50ms升到100ms以上),但换来的是任何网站都无法通过WebRTC探测到你的内网IP。对视频会议这类功能影响不大,对文件极速传输类应用有可感知的延迟提升,这个取舍我认为是值得的。
9. 实测数据:混在人堆里的成绩单
项目做到这个程度,自然要拿到真实环境中检验。我跑了两次第三方指纹测试服务,并做了长时间跟踪对比。
9.1 指纹熵从13.5降到4.2 bit意味着什么
第一次跑基线测试用的是原生Firefox ESR,未做任何修改。测试服务给出的指纹熵值是13.5 bit,直接解读就是:在样本库里,平均需要约11000个浏览器才能找到和你指纹一致的另一个个体。
camofox改完之后,同样是原生Firefox ESR的UA、系统语言和时区,再加全部混淆策略,指纹熵降到了4.2 bit。这意味着什么?在测试服务的数据库里,大约有8~12%的浏览器指纹和camofox的指纹相同。也就是说,平均每访问8~12个网站,就会遇到另一个和你“长得一模一样”的camofox用户。
这种“淹没在人堆里”的效果,正是这个项目追求的目标。
9.2 各类指纹向量的测试结果明细
我不只测了总熵值,还把每个向量分开做了对比,下面是部分维度的量化结果:
| 指纹向量 | 原生Firefox值 | CamouFox值 | 稳定性 | 备注 |
|---|---|---|---|---|
| User-Agent | Firefox/115.0 (Windows NT 10.0; Win64; x64) | 同原版 | 稳定 | 不做修改 |
| Canvas哈希 | 0x7F3A9C02...(各设备不同) | 每次会话随机偏移,同会话内稳定 | 会话级 | 保留原图结构 |
| WebGL厂商串 | 真实显卡型号 | 统一返回“Google SwiftShader” | 稳定 | 强制软渲染 |
| 字体枚举数 | 真实系统字体列表尺寸 | 裁剪为常见清单(Windows为56款/ macOS为48款) | 稳定 | 按OS模板 |
| AudioContext哈希 | 声卡驱动相关 | 加噪声,每次会话重置 | 会话级 | - |
| 时区 | Asia/Shanghai | 默认跟随系统 | 稳定 | 不做伪装 |
| 语言列表 | zh-CN, zh, en-US | zh-CN, en-US(按分布微调) | 会话级微调 | 幅度受限 |
表格里最需要注意的是WebGL那行。camofox默认强制WebGL走SwiftShader软件渲染——禁用了GPU加速的WebGL输出。这个策略不是顺手做的,而是深思熟虑的结果:不同GPU、不同驱动下,即使同一型号的显卡渲染同一个3D场景也会有小幅像素差异,软件渲染器输出在所有机器上是相对一致的。代价是玩网页版3D游戏、在线CAD这类场景帧率会明显下降。
9.3 断点回归测试:功能被破坏的三个场景
凡是做指纹混淆,一定会遇到“功能崩坏”的日子。camofox实测中踩过的坑有三个比较典型:
第一个是在线文档编辑器。Google Docs这类工具依赖Canvas来做文字排版的平滑渲染,像素加扰动之后,鼠标框选区域时会出现细微的对不齐。这不算致命问题,但如果你每天用Docs工作,会有轻微不适感。解决方式是给白名单域名放行Canvas噪声注入。
第二个是部分金融机构的网页端。它们的风控不是只用浏览器指纹,还检测Canvas渲染的“物理真实性”——经过扰动的Canvas在它们看来像是虚拟机或者云手机产生的。这个暂时无法两全,只能在文档里明确告知:金融类网站建议关闭防指纹模式。
第三个是音视频在线验证。有些网站调用AudioContext来判断“你是不是真人”——它们期望通过响应时间误差的随机性来排除自动化。噪声注入后的音频数据虽然频率特征伪装了,但响应时间精度反而因为注入计算变高了,看起来“太精确”而被判异常。这个后来加入了一个微小的随机延迟才解决。具体方法是:在AnalyserNode.getFloatFrequencyData方法返回前,人为加入一个0~30ms的随机时间抖动,然后再返回数据。
10. 还剩下的问题与下个迭代方向
10.1 指纹混淆会随浏览器自动更新失效
这个项目目前最大的维护成本,跟代码变更本身关系不大,而是:浏览器自动更新之后,出厂默认配置会覆盖一部分user.js设置,新版本的网页API行为也会变化,旧的混淆代码可能不再生效。针对这个问题,camofox开发了一个启动检查组件——每次启动时对比当前配置与目标配置的哈希值,不一致就自动覆盖回目标值。简单粗暴,但有效地把配置漂移问题控制在了“一次启动周期”内。
10.2 如果把文章里所有的方案合成一个“最终推荐配置”
如果你不想折腾编译,也愿意接受“尽力而为”的方案,可以把下面这套user.js配置直接放进Firefox的profile目录,作为轻度替代方案:
user_pref("privacy.resistFingerprinting", true); user_pref("privacy.resistFingerprinting.autoDeclineNoUserInputCanvasPrompts", true); user_pref("privacy.trackingprotection.enabled", true); user_pref("privacy.trackingprotection.socialtracking.enabled", true); user_pref("webgl.disabled", true); user_pref("webgl.enable-debug-renderer-info", false); user_pref("media.peerconnection.enabled", false); user_pref("media.peerconnection.ice.default_address_only", true); user_pref("media.navigator.enabled", false); user_pref("dom.webnotifications.enabled", false); user_pref("network.http.sendRefererHeader", 2); user_pref("geo.enabled", false); user_pref("javascript.options.shared_memory", true);这套配置改完,指纹熵大约能降到8bit左右,已经比默认状态好一个量级,但不能达到camofox全量编译后的4.2bit水平——毕竟深度混淆和配置微调还是有本质差距的。
另外,有一点需要反复强调:经过camofox深度混淆的浏览器,不适合作为日常唯一的支付/登录工具。我的建议是“一机双浏览器”模式——一个camofox用于普通浏览、内容消费、注册杂项账号;另一个保持系统默认的浏览器专门用于网银、支付、以及需要真实身份核验的场景。两套环境之间不直接互访邮箱链接、不共享Cookie,这样即使某一边暴露了身份信号,另一端仍然是干净的。
CamouFox的哲学不是让你彻底隐身,而是让你成为人群中最普通的那一个。普通,在网络世界里反而是最奢侈的身份状态。这大概是整个项目做下来我最深的体会——真正的隐私不是消失,而是融入。