指纹参数
身份由 Chromium 命令行开关控制。SDK 会根据具名选项替你设置这些开关;使用开源版构建时,也可以把它们作为 args 传给你自己的启动器。
大多数开关两种构建都支持,部分开关仅限授权版构建(GitHub 免费版或 Pro)——下方开关列表会为这些开关标注引入它们的引擎修订版本。没有许可证密钥时,SDK 运行的是开源版构建,这些选项在其中不起作用。
种子模型
一切都以 --fingerprint=<seed> 为基础。种子(seed,整数或任意字符串)会确定性地派生出身份画像(persona),并对下文加噪范围中列出的两个渲染表面施加扰动:
自授权版 Chromium 156 构建(r51)起,这一派生在 Clearcote 的 persona 服务中完成:启动器每次启动时携带许可证的运行令牌向其请求,并把已签名的 persona 交给浏览器;没有它浏览器会拒绝启动。相同的 seed 得到与 r50 相同的身份。离线许可证保持在 r50,直到有离线 persona 包。
- 相同种子 ⇒ 相同身份,在同一台机器、同一浏览器版本上每次启动都保持不变——相当于一位回访用户。页面能读取到的一切都完全相同:GPU、屏幕、硬件、字体和语音,以及 canvas、WebGL 和音频的结果。唯一的例外是网络连接速度估算(
navigator.connection中的往返时间和带宽),它跟随你的真实网络,与在 Chrome 中一样。 - 新种子 ⇒ 一个全新且可信的身份。
- Canvas 和 WebGL 噪声按站点派生(灵感来自 Brave 的 farbling),因此这些哈希在不同域名间各不相同。身份画像本身——GPU、屏幕、硬件、字体——在所有站点上都一样,就像一台真实机器那样。
不同机器上的同一个种子
身份画像声称的显卡,来自这台机器实际用于渲染的显卡的厂商(NVIDIA、Intel 或 AMD),因此页面测量到的像素和图形限制值,都属于页面被告知的那一类显卡。在没有显卡的机器上,身份画像会声称集成显卡:从 154 r37 起是 Intel 或 AMD 的,在 r36 上则只有 Intel 的。所以在显卡来自不同厂商的机器上,同一个种子会报告不同的显卡——这是有意为之。
在 154 r36 起的授权版构建上,机器的显卡厂商还可能改变其余硬件。入门级电脑不会配备独立显卡,因此在配有 NVIDIA 显卡的机器上,原本会声称入门级硬件(4 到 6 个处理器线程、4 到 8 GB 内存、小尺寸屏幕)的种子,会改为声称中端或更高的配置,而且不会声称触摸屏。因此,大约一半的种子在 NVIDIA 机器上报告的处理器线程数和内存,与在 Intel、AMD 或无显卡的机器上不同,其中一些还会报告不同的屏幕尺寸。在显卡厂商相同的机器上,种子的身份是相同的。
从 154 r37 起,各厂商机器所抽取的显卡型号范围取自真实的浏览器流量:每个型号出现的概率与它在实际环境中的相同,因此一批身份画像看起来就像一批真实机器。所以大多数种子在 r37 上声称的显卡与在 r36 上不同;其余硬件的派生方式与 r36 相同。
自 2026-10-26 起(r52;r51 的启动也是如此,因为身份由身份画像服务派生),默认使用身份画像 schema 7:由显卡决定机器的类型——笔记本或台式机、处理器代际、线程数、内存和屏幕,包括笔记本实际使用的显示缩放比例。在这一日期之后,一个种子抽到的机器与之前不同。
要让一个种子在不同机器上保持同一个身份:
- 在显卡厂商相同的机器上运行它——例如在生产环境中只使用一种服务器类型。
- 固定显卡厂商:把
--fingerprint-gpu-host-vendor=nvidia(或intel、amd)传入启动器的args。这样无论在哪里运行,身份画像都会像在配有该厂商显卡的机器上那样派生身份。请固定为该身份画像主要运行的那些机器的显卡厂商:在显卡来自其他厂商的机器上,它声称的显卡将不再与实际渲染的显卡相符。 - 保留在 r45 到 r51 上创建的身份(2026-10-26 之前):使用
persona_schema=5/personaSchema: 5(--fingerprint-schema=5);154 r37 到 155 r44 上创建的身份则使用persona_schema=4/personaSchema: 4。这两个 schema 都已冻结:一个种子在它们上面永久派生出同一个身份。 - 保留在 154 r36 上创建的身份:使用
persona_schema=3/personaSchema: 3(--fingerprint-schema=3)。这样每个种子派生出的结果都与 r36 上完全相同,逐字段一致,显卡也包括在内。 - 保留 r36 之前创建的身份:使用
persona_schema=2/personaSchema: 2(--fingerprint-schema=2)。这样每个种子派生出的结果都与 153 r29 到 154 r35 上完全相同,逐字段一致:显卡仍然跟随机器的显卡厂商,其余硬件则不跟随。
没有种子时,就不存在按身份画像区分的身份:同一类机器上的每次启动看起来都一样。请为每个身份使用各自的种子。
比起由种子合成的身份,更想用一台真实机器的身份?用采集器采集一台作为样本的 Chrome,再通过 --fingerprint-profile=<gzip+base64 JSON> 导入(或使用 SDK 的 fingerprint_profile / fingerprintProfile 选项,它会替你完成 gzip+base64 编码),用它代替种子。指纹配置文件中存在的字段会覆盖浏览器原本要上报的值;缺失的字段回退到浏览器默认值。不要在使用指纹配置文件的同时传入 --fingerprint 种子:种子会给 canvas 加上噪声,与真实机器的数值放在一起时,严格的评分会把它判为篡改;而身份已经由指纹配置文件提供(参见 Profile 与持久化)。两种构建都能读取导入的指纹配置文件;在授权版构建上,自 151 r19 起完整生效。如何从 SDK 导入采集到的指纹配置文件,请参阅 Playwright 指南。
哪些表面会加噪,哪些刻意不加
扰动一个表面,换来的是不可关联性,付出的是一致性,这笔交易并非处处都值得。Clearcote 只对两个表面加噪——在这两处,噪声最接近普通硬件之间本来就有的差异;其余表面不做扰动,因为在那些表面上,被扰动过的值比一个与他人相同的值更显眼。不过,不扰动并不等于不改动:少数值仍会整体替换为身份画像中的值,下表及其后的说明会逐一列出。有些检测工具仍能识别出噪声本身;在意这一点时,fingerprintNoise: false 可以关闭噪声并保留身份(参见检测原理)。
| 表面 | 按种子加噪 | 原因 |
|---|---|---|
Canvas 2D——toDataURL、getImageData 以及页面导出 canvas 的其他方式(在授权版构建上,自 152 r21 起所有导出途径的结果一致) | 是 | 真实机器之间的回读结果本就因 GPU、驱动和光栅化路径而异,因此小幅扰动落在自然差异范围之内。 |
WebGL——readPixels、toDataURL | 是 | 理由同上。在 154 r34 起的授权版构建上,disableGpuFingerprint 只改变 GPU 上报的内容:readPixels 和 WebGL canvas 的导出结果都保留种子的噪声,并且彼此一致。在开源版构建和较旧的授权版构建上,它还会停止 readPixels 的噪声,而导出结果仍带有噪声,因此在这些构建上请同时设置 fingerprintNoise: false,让对该 canvas 的每一次读取结果都一致。fingerprintNoise: false 会在所有情况下关闭 WebGL 噪声。 |
音频——AudioContext 采样值 | 否——有意为之 | 对于给定的处理管线,Web Audio 的输出是确定的。要扰动它,要么使用不合规范的采样率(44099.99 而不是 44100),要么产生任何原版渲染器都不会生成的采样——两者都是真实浏览器不可能输出的值,比两个 profile 共用同一个音频哈希更容易暴露。上报的标量是另一回事,它们确实跟随身份画像——见下文。 |
| Client rects 与文本度量 | 否——有意为之 | 矩形是布局的输出:它们取决于字体、设备像素比、缩放级别和区域设置(locale),只有这些输入变化时才会变化。导入一个像素比不同的指纹配置文件,或换一种语言(可能改变默认字体),页面的布局就会不同;同一台机器上的不同种子使用相同的字体和相同的像素比进行布局,因此上报相同的矩形。检测方不需要任何存储的数值就能发现矩形上的噪声,做法是用事先已知答案的测试来检验。CreepJS 要求旋转 45° 的 100×100 方框在像素比为 1 时哈希为 9d9215cc,right - left 必须精确等于 width,两个完全相同的元素测量结果必须一致,空元素的测量结果必须为零。真实的 Chrome 上报的矩形 x、y、width 和 height 都是精确的单精度(float32)数值,而在 JavaScript 中添加的噪声很少能保持这一点。布局之后添加的噪声,无论多小,都无法通过这些检查。Brave、Firefox 和 Tor Browser 的反指纹保护以及 Camoufox 同样不改动矩形;Camoufox 在 2026 年 9 月移除了字形宽度噪声,因为没有任何真实机器会产生那样的宽度。 |
| WebGPU——适配器与限制值 | 否 | 跟随身份画像的 GPU 而不是种子,因此始终与 WebGL 上报的信息一致。同一页面上的两个 GPU API 如果报出不同的厂商,一次调用就能读出来。 |
对多账号场景的实际影响是:canvas 和 WebGL 能区分你的各个身份;渲染出的音频和 client rects 不能。后两者是底层机器的属性——无论加载哪个种子,同一台主机都会跑出相同的音频管线,并用相同的字体、相同的像素比排版文字,正如两个人使用完全相同的硬件时一样。如果目标站点恰恰依据这些信号做关联,解决办法是使用不同的机器,而不是换一个开关。
有一个例外值得了解:AudioContext 的三个标量确实跟随身份画像。只要有身份画像处于启用状态——来自 --fingerprint 种子或导入的指纹配置文件——sampleRate、baseLatency 和 outputLatency 就由身份画像提供,因此它们随身份而变,不会暴露主机的音频后端。两者都未设置时,这三个值直接取自真实机器。页面显式请求的采样率仍会被遵循,outputLatency 也仍会经过 Chrome 自身依赖权限的量化器。自 153 r26 起,baseLatency 还会跟随页面请求的渲染尺寸,尚未开始渲染的 context 报告的延迟也与普通 Chrome 相同。
噪声落在哪里(r57)
从 r57 起,在授权版构建中,canvas 和 WebGL 读取噪声只在一个像素的上下左右四个相邻像素都与它颜色不同时才改动它。平坦区域——纯色形状、填充块、清空的背景——读出来与绘制时完全相同,和真实硬件一样。文字边缘、曲线和渐变仍然带有种子的噪声,所以各配置文件的 canvas 哈希仍然不同。r57 之前,读取范围内的平坦区域也会加噪,因此绘制纯色形状并计算哈希的检测器(PixelScan 的“Masking detected”)可以证明像素被改动过。
WebGL 的部分 readPixels(比 canvas 小的矩形)看不到紧邻其外的像素。因此默认情况下,引擎会在一次 GPU 读取中把矩形连同周围 1 像素的边距一起读出,让每个边缘像素得到与完整读取 canvas 时完全相同的噪声,读取耗时与 Chrome 相同。
| SDK 选项(Node / Python / .NET) | Profile Manager | 默认 | 作用 |
|---|---|---|---|
flatAreaNoise / flat_area_noise / FlatAreaNoise | 平坦区域也加噪(r56 方式) | 关 | true 恢复 r56 的噪声:平坦区域也会加噪。可用于保留某个配置文件在 r57 之前的 canvas 哈希。此时对纯色形状计算哈希的检测器会再次报告遮蔽。开关: --fingerprint-noise-flat-areas. |
webglReadMargin / webgl_read_margin / WebglReadMargin | WebGL 部分读取的边距 | 开 | false 只读取矩形,不含边距。此时部分读取边缘的一两个像素可能与同一 canvas 的完整读取不同。开启平坦区域加噪时无效。开关: --fingerprint-webgl-no-read-margin. |
两个设置都作用于页面读取像素的所有方式(getImageData, toDataURL, toBlob, readPixels),因此同一 canvas 的各次读取始终一致。r57 之前的引擎始终等同于 flatAreaNoise: true,SDK 和 Profile Manager 在这些引擎上会省略这两个设置。在 Docker 中,设置 CC_FLAT_AREA_NOISE 和 CC_WEBGL_READ_MARGIN。这些设置随 SDK 0.68.8 和 Profile Manager 0.40.0 提供。
一致的次级表面
除了加噪的信号,引擎还让身份画像的次级表面与该平台上的真实 Chrome 保持一致。SDK 默认把平台设为你的主机操作系统;下文中 Windows 特有的值适用于 Windows 身份画像。
- WebGL 限制值(WebGL1 + WebGL2)在本机能够支撑的范围内跟随身份画像的 GPU。上报的限制值绝不会高于真实驱动实际执行的上限,因此在软件渲染器上,部分值会低于所声称 GPU 的对应值。
- UNMASKED_RENDERER / UNMASKED_VENDOR 在整个会话内保持不变——所有站点看到的都是同一块跟随身份画像的 GPU,不会成为按源(origin)变化的破绽。
- navigator.getBattery() 报告的是一台接市电的台式机(充电中、电量 1.0、无放电),navigator.connection 报告的是家庭宽带网络特征(effectiveType 4g、取整后的 rtt/downlink、saveData 关闭)。
- navigator.keyboard.getLayoutMap() 返回美式 QWERTY 键位映射;在 Windows 身份画像下,AudioContext 报告 Windows 音频栈的采样率和延迟。
- 浅色或深色——从 154 r32 起,身份画像会偏好一种配色方案(大约三分之一为深色;可用
--fingerprint-color-scheme=light|dark指定),prefers-color-scheme、matchMedia、Sec-CH-Prefers-Color-Scheme请求头和 CSS 系统颜色全都与之一致。在这类构建上,SDK(0.41.0+)不会开启 Playwright 的配色方案模拟,因此不会有任何东西与之矛盾。 - 字体——在 Windows 身份画像下,文本具有 Windows 机器的行高和常见字体宽度,页面按名称请求的字体的表现也与在 Windows 上相同(154 r32)。机器上安装的真正字体会以其本身被使用;在 Linux 上,请用 fontDirs 列出你自己的字体。
- window.getScreenDetails() 报告单个一致的显示器;
@media (pointer: fine)/(hover: hover)与配备鼠标的台式机相符。 - URL——在 Windows 身份画像下,
new URL("C:/").protocol返回file:;自 153 r26 起,页面构造 URL 的所有方式都给出相同结果。navigator.share / canShare 会暴露给页面,以与 Windows UA 相符。 - WebGPU——
navigator.gpu的适配器信息以及 limits/features 与 WebGL 跟随同一块 GPU(在授权版构建上,会收窄到主机实际能提供的范围)。 - 语言区域——
Accept-Language、navigator.language、完整的navigator.languages列表以及Intl(主线程 + worker)全部来自同一个列表(acceptLanguage或geoip)。SDK 会替你设置--accept-lang和--lang;直接使用原始开关时,两者都要传。 - 语音——
speechSynthesis提供身份画像的语音列表,包括该版本 Chrome 会列出的 Google 在线语音(153 r27)。设置fingerprintVoices: false则保留本机自带的语音(152 r22)。 - 硬件——处理器速度读数(
navigator.cpuPerformance)跟随身份画像而不是真实处理器(152 r20);脚本内存上限(performance.memory.jsHeapSizeLimit)跟随身份画像所声称的内存大小(153 r26)。 - 编解码器与设备——
MediaCapabilities.decodingInfo()报告身份画像的编解码器矩阵,enumerateDevices()报告身份画像的媒体设备集(id 随种子保持稳定,授予权限前 label 为空)。 - UA-CH 高熵值——
bitness=64 /wow64=false /model;在 Linux 身份画像下platformVersion为空,与 Linux 上真实 Chrome 的报告一致(153 r26)。设置storageQuota后,navigator.storage.estimate()会报告符合实际的磁盘配额。
实验性功能:真实 GPU canvas 桥接
可以选择把 canvas / WebGL 操作转发到一台配有真实 GPU 的远程主机,让回读结果(getImageData / toDataURL / readPixels / measureText)与你呈现的 GPU 保持一致——即便本地硬件无法渲染出这块 GPU 的结果。由于转发的是操作(而不是预先录制的图像),它能处理大多数 canvas,而不仅仅是已知的探测脚本。用 --canvas-bridge-url=ws://host:port(外加 --no-sandbox)启用;不设置则完全在本地渲染,与以往完全相同。渲染服务器会在真实 GPU 主机上驱动一个浏览器(通过 CDP 连接的普通 Chrome,或无头模式的 Clearcote)。完整配置见 canvas 桥接指南。
原生元数据覆盖与 light_stealth
除了由种子派生的身份画像,还有一组原生单值覆盖,可以直接伪装单个 navigator/screen 值——hardwareConcurrency、deviceMemory、colorDepth、devicePixelRatio、maxTouchPoints,以及(需显式开启的)screen/avail* 尺寸。每个值都由对应的 getter 直接读取,优先级为 参数 > --fingerprint 身份画像 > 真实主机,因此硬编码的值优先于任何种子;而且无论有没有 --fingerprint 种子,这些覆盖都能生效——它们完全不会启用身份画像机制。
SDK 把其中一个轻量子集打包进一个参数 light_stealth:它只通过上述原生开关,应用一小组相互一致的元数据组合中的一个(hardwareConcurrency、deviceMemory、colorDepth、devicePixelRatio、maxTouchPoints)。种子只用来挑选组合,不会传给引擎,因此不会产生 canvas 或 WebGL 噪声,同一台机器上的所有 light_stealth 会话都共用主机的 canvas 哈希——如果这些会话需要彼此不同,请为每个账号使用独立的 fingerprint 种子,并且不要开启 lightStealth。TLS ClientHello 和真实浏览器版本保持不变。screen 尺寸默认刻意不做伪装(可通过 screenWidth 等选项显式开启),因为无法与真实渲染表面对上的伪造屏幕很容易被识破;在未缩放的显示器上,请确认所选的 devicePixelRatio 与之相符,或者自行设置。显式指定的选项始终优先于预设。
import { launch } from "clearcote";
// one coherent metadata bundle; "my-seed" only picks which one — no seed reaches the engine, so no canvas noise:
const browser = await launch({ lightStealth: true, fingerprint: "my-seed" });
// or set individual values by hand (no --fingerprint needed):
const b2 = await launch({
hardwareConcurrency: 8,
deviceMemory: 8,
devicePixelRatio: 1.25,
maxTouchPoints: 0,
});全部开关
可按名称或作用筛选,也可以只看某个子系统。点击某个开关,即可查看它的作用及最简有效写法。
主种子(整数或字符串)。由它推导出整个身份画像(GPU、屏幕、硬件、字体),并驱动按站点的 canvas 和 WebGL 噪声。语言和时区不由种子推导:它们来自 --accept-lang / --timezone(SDK:acceptLanguage、timezone 或 geoip)。渲染出的音频和 client rects 不会被扰动;启用身份画像时,AudioContext 的 sampleRate/baseLatency/outputLatency 标量值由身份画像提供——详见指纹文档。在同一台机器、同一浏览器版本上,相同种子 ⇒ 相同身份;在显卡厂商不同的机器上,身份会有所不同(详见指纹文档和 --fingerprint-gpu-host-vendor)。
示例
以下是原始命令行参数组合。完整的 Python 和 Node 工作流见示例。
一个完整、一致的 Windows 身份:
--fingerprint=acme-tenant-7 \
--fingerprint-platform=windows \
--fingerprint-brand=Chrome \
--accept-lang=en-US,en \
--lang=en-US \
--timezone=America/New_York \
--fingerprint-hardware-concurrency=8使用自定义 GPU 字符串的 Windows 身份,字符串格式与 Windows 上 ANGLE 的上报格式相同:
--fingerprint=42 \
--fingerprint-platform=windows \
--fingerprint-gpu-vendor="Google Inc. (NVIDIA)" \
--fingerprint-gpu-renderer="ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 (0x00002504) Direct3D11 vs_5_0 ps_5_0, D3D11)"这些字符串改变的是上报的内容,而不是绘制的方式:像素仍然来自本机的 GPU。请填写你实际用于渲染的 GPU,或者使用 canvas 桥接。macos 平台在开源版构建上只改变身份字符串(UA、client hints、navigator.platform);在授权版构建上,自 155 r47 起它描述的是一台真实的 Mac,芯片、线程数、内存、屏幕、图形渲染器、macOS 版本和语音彼此匹配。android 则是一个尽力而为的移动端身份画像。
把地理位置和对应的时区一起固定,让地理定位与时钟保持一致:
--fingerprint=nyc-1 \
--timezone=America/New_York \
--fingerprint-location=40.7128,-74.0060导入真实机器的值
除了合成种子,你还可以上报一台真实 Chrome 报告过的原样数值——GPU 字符串 + getParameter 表、屏幕、字体、语音、音频。这些值只是被替换;渲染仍由你自己的机器完成,所以指纹配置文件并不能让两个账号拥有不同的 canvas——为每个账号使用独立的 --fingerprint 种子才可以。两者择一,不要同时使用:要真实机器的数值就用指纹配置文件,要按账号区分渲染就用种子。指纹配置文件可以从精选的 clearcote-profiles 库(数千份真实机器的指纹配置文件,按 GPU 厂商打了标签)中挑一份,或者用采集器自行采集,然后加载它,并确认它确实已加载:
import { launch } from "clearcote";
// path to a captured .json profile, a profile object, or a JSON string
const browser = await launch({ fingerprintProfile: "./real-machine.json" });
// fields present in the profile override what the browser would report;
// absent fields keep the browser's defaults. Pass no fingerprint seed alongside a profile.采集流程:在你想复刻的真实 Chrome 中打开采集器页面,导出 JSON,再通过 fingerprintProfile(SDK)/ --fingerprint-profile(引擎)传入。用 tools/fingerprint-collect/verify_profile.py 验证。
各补丁的作用
开源版构建的每个开关都对应一个可读的引擎补丁:diff 位于 patches/,每个补丁的一行摘要见 patches/README.md。标注为“授权版构建”的开关来自授权版构建自己的补丁集,该补丁集不公开。
一致性比任何单个值都重要:平台、时区、语言区域和 GPU 要作为一个整体合理可信。引擎如何让它们保持一致,见架构;为什么这比 JavaScript 伪装更有效,见检测原理。
原版 Chromium 与 Clearcote Pro 之间相差 38,921 行。
这就是 Pro 版构建与 Google 发布的 Chromium 之间的距离。网站能读取的每个值都在 Chromium 计算它的地方设定,因此页面、它的 Worker 和 iframe 描述的都是同一台设备。
- Canvas
- WebGL
- WebGPU
- 音频
- 字体
- 屏幕与窗口
- WebRTC
- TLS
- Client Hints
- 输入时序
- 语音与编解码器
Pro 版构建,统计自其 r57 补丁栈(Chromium 156.0.8078.47)。免费的开源版构建是一套更小、公开的补丁。
345 files changed, 35915 insertions(+), 3006 deletions(-)
240 个补丁
按模块划分的改动文件
- 渲染引擎(Blink)118
- 浏览器(chrome/)52
- Content 层42
- 组件39
- 网络栈22
- 界面与窗口14
- TLS(BoringSSL)11
- V8 与 URL17
- 其他30
相关阅读