指纹参数
身份由 Chromium 命令行开关控制。SDK 会根据具名选项替你设置这些开关;使用开源版构建时,也可以把它们作为 args 传给你自己的启动器。
大多数开关两种构建都支持,部分开关仅限授权版构建(GitHub 免费版或 Pro)——下方开关列表会为这些开关标注引入它们的引擎修订版本。没有许可证密钥时,SDK 运行的是开源版构建,这些选项在其中不起作用。
种子模型
一切都以 --fingerprint=<seed> 为基础。种子(seed,整数或任意字符串)会确定性地派生出身份画像(persona),并对下文加噪范围中列出的两个渲染表面施加扰动:
- 相同种子 ⇒ 相同身份,多次启动之间保持不变——相当于一位回访用户。
- 新种子 ⇒ 一个全新且可信的身份。
- Canvas 和 WebGL 噪声按站点派生(灵感来自 Brave 的 farbling),因此这些哈希在不同域名间各不相同。身份画像本身——GPU、屏幕、硬件、字体——在所有站点上都一样,就像一台真实机器那样。
比起由种子合成的身份,更想用一台真实机器的身份?用采集器采集一台作为样本的 Chrome,再通过 --fingerprint-profile=<gzip+base64 JSON> 导入(或使用 SDK 的 fingerprint_profile / fingerprintProfile 选项,它会替你完成 gzip+base64 编码)。指纹配置文件中存在的字段会覆盖身份画像;缺失的字段回退到 --fingerprint 种子对应的身份画像,没有种子时则回退到浏览器默认值。两种构建都能读取导入的指纹配置文件;在授权版构建上,自 151 r19 起完整生效。如何从 SDK 导入采集到的指纹配置文件,请参阅 Playwright 指南。
哪些表面会加噪,哪些刻意不加
扰动一个表面,换来的是不可关联性,付出的是一致性,这笔交易并非处处都值得。Clearcote 只对两个表面加噪——在这两处,噪声最接近普通硬件之间本来就有的差异;其余表面不做扰动,因为在那些表面上,被扰动过的值比一个与他人相同的值更显眼。不过,不扰动并不等于不改动:少数值仍会整体替换为身份画像中的值,下表及其后的说明会逐一列出。有些检测工具仍能识别出噪声本身;在意这一点时,fingerprintNoise: false 可以关闭噪声并保留身份(参见检测原理)。
| 表面 | 按种子加噪 | 原因 |
|---|---|---|
Canvas 2D——toDataURL、getImageData 以及页面导出 canvas 的其他方式(在授权版构建上,自 152 r21 起所有导出途径的结果一致) | 是 | 真实机器之间的回读结果本就因 GPU、驱动和光栅化路径而异,因此小幅扰动落在自然差异范围之内。 |
WebGL——readPixels、toDataURL | 是 | 理由同上。启用 disableGpuFingerprint 时,readPixels 返回真实像素,但 WebGL canvas 的导出结果(toDataURL() 等)仍带有噪声——请同时设置 fingerprintNoise: false,让对该 canvas 的每一次读取结果都一致。 |
音频——AudioContext 采样值 | 否——有意为之 | 对于给定的处理管线,Web Audio 的输出是确定的。要扰动它,要么使用不合规范的采样率(44099.99 而不是 44100),要么产生任何原版渲染器都不会生成的采样——两者都是真实浏览器不可能输出的值,比两个 profile 共用同一个音频哈希更容易暴露。上报的标量是另一回事,它们确实跟随身份画像——见下文。 |
| Client rects 与文本度量 | 否——有意为之 | Chrome 会把布局量化到原生的 1/512 px 网格上,所以每个真实的矩形值都恰好是 0.001953125 的整数倍。事后再乘上缩放系数,会让所有矩形偏离这个网格;无论系数多小,只需一次测量就能识别出偏离网格的几何值。 |
| WebGPU——适配器与限制值 | 否 | 跟随身份画像的 GPU 而不是种子,因此始终与 WebGL 上报的信息一致。同一页面上的两个 GPU API 如果报出不同的厂商,一次调用就能读出来。 |
对多账号场景的实际影响是:canvas 和 WebGL 能区分你的各个身份;渲染出的音频和 client rects 不能。后两者是底层机器的属性——无论加载哪个 profile,同一台主机都会跑出相同的音频管线和相同的布局网格,正如两个人使用完全相同的硬件时一样。如果目标站点恰恰依据这些信号做关联,解决办法是使用不同的机器,而不是换一个开关。
有一个例外值得了解:AudioContext 的三个标量确实跟随身份画像。只要有身份画像处于启用状态——来自 --fingerprint 种子或导入的指纹配置文件——sampleRate、baseLatency 和 outputLatency 就由身份画像提供,因此它们随身份而变,不会暴露主机的音频后端。两者都未设置时,这三个值直接取自真实机器。页面显式请求的采样率仍会被遵循,outputLatency 也仍会经过 Chrome 自身依赖权限的量化器。自 153 r26 起,baseLatency 还会跟随页面请求的渲染尺寸,尚未开始渲染的 context 报告的延迟也与普通 Chrome 相同。
一致的次级表面
除了加噪的信号,引擎还让身份画像的次级表面与该平台上的真实 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 音频栈的采样率和延迟。
- 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 标量值由身份画像提供——详见指纹文档。相同种子 ⇒ 相同身份。
示例
以下是原始命令行参数组合。完整的 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),但背后并没有对应的 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 the persona;
// absent fields fall back to the --fingerprint seed's persona, or the browser's defaults with no seed.采集流程:在你想复刻的真实 Chrome 中打开采集器页面,导出 JSON,再通过 fingerprintProfile(SDK)/ --fingerprint-profile(引擎)传入。用 tools/fingerprint-collect/verify_profile.py 验证。
各补丁的作用
开源版构建的每个开关都对应一个可读的引擎补丁:diff 位于 patches/,每个补丁的一行摘要见 patches/README.md。标注为“授权版构建”的开关来自授权版构建自己的补丁集,该补丁集不公开。
一致性比任何单个值都重要:平台、时区、语言区域和 GPU 要作为一个整体合理可信。引擎如何让它们保持一致,见架构;为什么这比 JavaScript 伪装更有效,见检测原理。
相关阅读