确定性模式:可重现的浏览器指纹
如何使用噪声种子控制在不同会话间产生相同的浏览器指纹,实现一致的 Canvas、WebGL 和 Audio 输出。
简介
浏览器指纹追踪依赖于在会话内稳定但在设备间变化的信号。这个问题的另一面是可重现性:当你需要多个浏览器会话呈现相同的身份,或者需要验证你的隐私保护是否正常工作时,你需要确定性行为。一个每次启动都产生略微不同的 Canvas 哈希、Audio 指纹或 WebGL 输出的浏览器,使得维护稳定身份或可靠测试配置变得不可能。BotBrowser 的确定性模式通过 --bot-noise-seed 标志启用,让你完全控制所有指纹变化来源。相同的配置文件和种子组合始终产生相同的指纹,跨会话、跨机器、跨操作系统。
隐私影响
确定性浏览器行为的需求源于指纹保护中的一个根本矛盾。噪声对隐私很重要。给 Canvas 输出、Audio 处理和 WebGL 渲染添加受控的变化可以防止指纹被简单地关联到单个配置文件。但不受控的噪声,即每个会话产生不同指纹,会产生另一个问题:每个会话看起来都是唯一用户,这与融入群体恰恰相反。
INRIA 隐私团队的研究表明,指纹不稳定性(跨会话的指纹变化)本身就是一个追踪信号。如果指纹在每次访问时都变化,但其他信号(IP、登录状态、行为模式)保持稳定,变化的指纹就成为标记而非屏障。
确定性模式通过给你明确的控制来解决这个问题。你选择指纹何时应保持相同(相同种子)以及何时应不同(不同种子)。这对以下场景特别重要:
- 会话连续性。 跨多个会话以相同指纹返回网站。
- 多实例协调。 运行需要不同但稳定身份的多个浏览器实例。
- 测试和验证。 确认你的指纹保护产生预期结果。
- 研究可重现性。 运行需要消除指纹变化作为变量的受控实验。
技术背景
指纹变化的来源
现代指纹保护涉及向多个渲染和处理管线添加受控噪声:
- Canvas 2D。 文本渲染、形状绘制和图像操作产生设备特定的像素输出。噪声添加亚像素变化以防止精确匹配。
- WebGL/WebGPU。 着色器执行、纹理采样和帧缓冲区读回产生 GPU 特定的输出。噪声修改渲染输出中的像素值。
- Audio。 OfflineAudioContext 渲染和 AnalyserNode 频率数据产生平台特定的浮点值。噪声改变处理输出。
- 文本指标。
measureText()、getBoundingClientRect()和getClientRects()返回依赖于字体渲染的尺寸。噪声添加亚像素变化到测量值。
没有种子时,每个噪声源在每次浏览器启动时生成全新的随机变化。有种子时,随机数生成器被初始化为已知状态,每次都产生相同的变化模式。
噪声种子的工作原理
噪声种子是一个单独的整数,用于初始化确定性伪随机数生成器(PRNG)。PRNG 产生一个看起来随机但完全由种子决定的值序列。两个具有相同种子的会话产生相同的序列,因此对每个指纹表面应用相同的噪声。
BotBrowser 的噪声种子(通过 --bot-noise-seed 设置)控制:
- Canvas 2D 图像噪声
- WebGL 图像噪声
- WebGPU 渲染噪声
- Audio 上下文处理变化
- 文本指标变化(客户端矩形、文本矩形)
- 文本布局变化
种子不影响非噪声属性如 navigator.hardwareConcurrency、screen.width 或用户代理字符串。这些直接来自配置文件,始终是确定性的。
时序确定性
除了指纹噪声之外,BotBrowser 还提供额外的时序控制:
--bot-time-seed- 控制 27 个浏览器操作的执行时序多样性。每个种子产生独特、稳定的性能配置文件。这覆盖performance.now()间隔、performance.getEntries()、导航时序等。--bot-time-scale- 缩小performance.now()间隔以归一化因不同服务器负载造成的时序差异。
噪声种子和时序种子结合使用,提供对所有可变浏览器输出的全面确定性控制。
常见保护方案及其局限性
完全不加噪声意味着你的指纹直接反映配置文件的基础值。这是确定性的,但消除了变化带来的隐私收益。具有相同配置文件的多个会话都有完全相同的指纹,意味着它们按定义是可关联的。
没有种子的随机噪声提供变化,但以可重现性为代价。每个会话都有唯一的指纹。你无法维护稳定的身份,无法可靠地验证配置,也无法协调多个实例。
基于会话的噪声(每个会话生成随机种子并存储)是一种手动解决方案,增加了复杂性。你需要管理种子值,将它们与会话关联,并确保一致应用。BotBrowser 通过将种子作为 CLI 标志接受来消除这种开销。
基于扩展的噪声通常在 JavaScript 级别应用随机噪声,没有任何种子机制。每次页面加载产生不同的结果。噪声不是确定性的、不可控的,也不是跨 API 表面一致的。
BotBrowser 的引擎级方案
BotBrowser 的确定性模式在渲染引擎级别运行,确保从单个种子值出发,噪声在所有 API 表面一致应用。
单一种子,完整覆盖
--bot-noise-seed 标志接受从 1 到 4294967295(UINT32_MAX)的整数。这个单一值同时控制所有噪声源:
- Canvas 2D 的
toDataURL()、toBlob()和getImageData()对相同种子产生相同输出。 - WebGL 的
readPixels()对相同绘制场景和种子返回相同的帧缓冲区数据。 - OfflineAudioContext 对相同的音频图和种子渲染相同的缓冲区。
measureText()和getClientRects()对相同内容和种子返回相同的测量值。
跨机器可重现性
由于噪声从种子派生并在引擎级别应用,相同的配置文件 + 种子组合无论底层硬件、操作系统或 GPU 如何都产生相同的指纹。一个使用 NVIDIA GPU 的 Linux 服务器上的会话和一个使用 Apple Silicon 的 Mac 笔记本上的会话,在使用相同配置文件和种子时产生相同的指纹。
种子作为身份
实际上,你可以将每个种子视为在配置文件内定义唯一子身份。配置文件 A 加种子 42 是一个稳定、可重现的身份。配置文件 A 加种子 43 是另一个稳定、可重现的身份。配置文件定义设备类别(操作系统、浏览器、硬件),种子定义该类别中的个体。
这个模型自然映射到多账户或多会话工作流:
- 会话 1:配置文件 A,种子 100
- 会话 2:配置文件 A,种子 200
- 会话 3:配置文件 B,种子 100
会话 1 和 2 看起来是具有相似硬件的不同用户。会话 3 看起来是使用不同硬件的不同用户。
时序确定性
使用 --bot-time-seed,执行时序也变得可重现。性能测量、导航时序和资源时序都遵循给定种子的确定性模式。与 --bot-noise-seed 结合使用,这提供了对所有可变浏览器输出的完整控制。
配置和用法
基本确定性模式
chrome --bot-profile="/path/to/profile.enc" \
--bot-noise-seed=42 \
--user-data-dir="$(mktemp -d)"
完整确定性控制
chrome --bot-profile="/path/to/profile.enc" \
--bot-noise-seed=42 \
--bot-time-seed=42 \
--bot-stack-seed=profile \
--user-data-dir="$(mktemp -d)"
使用不同种子的多实例
# 实例 1 - 稳定身份 A
chrome --bot-profile="/path/to/profile.enc" \
--bot-noise-seed=1001 \
--user-data-dir="/tmp/session-a" &
# 实例 2 - 稳定身份 B
chrome --bot-profile="/path/to/profile.enc" \
--bot-noise-seed=1002 \
--user-data-dir="/tmp/session-b" &
Playwright 集成
const { chromium } = require('playwright');
const browser = await chromium.launch({
executablePath: '/path/to/botbrowser/chrome',
args: [
'--bot-profile=/path/to/profile.enc',
'--bot-noise-seed=42',
'--bot-time-seed=42'
]
});
const page = await browser.newPage();
await page.goto('https://example.com');
// 此 Canvas 指纹在每次运行中都完全相同
const hash = await page.evaluate(() => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.fillText('deterministic test', 10, 10);
return canvas.toDataURL();
});
console.log(hash);
Puppeteer 集成
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch({
executablePath: '/path/to/botbrowser/chrome',
defaultViewport: null,
args: [
'--bot-profile=/path/to/profile.enc',
'--bot-noise-seed=42',
'--bot-time-seed=42'
]
});
const page = await browser.newPage();
await page.goto('https://example.com');
禁用特定噪声通道
如果你需要某些信号的确定性输出但其他信号使用原生输出:
chrome --bot-profile="/path/to/profile.enc" \
--bot-noise-seed=42 \
--bot-config-noise-canvas=false \
--bot-config-noise-audio-context=false
验证
跨会话可重现性。 使用相同配置文件和种子在两个独立会话中运行相同的指纹测试。比较 Canvas 哈希、Audio 指纹、WebGL 输出和文本指标。所有值应完全相同。
跨机器可重现性。 在不同机器上使用相同配置文件和种子运行相同测试。指纹应完全匹配。
种子变化。 更改种子并再次运行测试。指纹应不同,确认种子确实在影响输出。
种子 0 的行为。 使用 --bot-noise-seed=0 保持噪声激活(使用配置文件默认值),但不进行确定性种子设置。验证这会在会话之间产生变化的输出。
最佳实践
- 为每个身份使用唯一种子。 每个种子定义一个稳定的子身份。不要对应该呈现为不同用户的会话重用相同种子。
- 存储种子值。 如果你需要返回某个会话,你需要相同的配置文件和种子。存储会话身份与种子值之间的关联。
- 将
--bot-noise-seed与--bot-time-seed结合使用。 噪声种子控制渲染输出。时序种子控制性能计时。两者都参与完整指纹。同时使用两者以实现完全确定性。 - 避免种子值 0。 零保持噪声激活但变化不确定。使用任何正整数实现确定性行为。
- 测试你的种子分配。 在部署之前,验证不同种子产生不同指纹,相同种子产生相同指纹。
常见问题
问:--bot-noise-seed 的有效范围是什么? 答:从 1 到 4294967295(UINT32_MAX)的任何整数。值 0 保持噪声激活但不确定。
问:噪声种子是否影响浏览器性能? 答:不影响。噪声操作极其轻量。PRNG 计算增加的开销可以忽略不计。
问:两个不同的配置文件使用相同种子能产生相同的指纹吗? 答:不能。配置文件定义基础指纹(GPU、屏幕、navigator 等),种子定义噪声变化。不同配置文件使用相同种子会产生不同的指纹,因为基础值不同。
问:如果我不设置 --bot-noise-seed 会怎样? 答:每次启动时用随机种子应用噪声。每个会话有不同的指纹。配置文件的基础属性(navigator、screen 等)保持不变。
问:确定性模式是否影响 Cookie 或本地存储?
答:不影响。确定性模式控制与指纹相关的渲染输出。存储、Cookie 和会话状态通过 --user-data-dir 和 --bot-cookies 管理。
问:我可以在不同的 BotBrowser 版本之间使用相同种子吗? 答:噪声算法可能在主版本之间发生变化。为了精确可重现性,请使用相同的 BotBrowser 版本。在同一版本内,种子行为保证稳定。
问:--bot-time-seed 与 --bot-noise-seed 有何不同?
答:--bot-noise-seed 控制渲染噪声(Canvas、WebGL、Audio、文本指标)。--bot-time-seed 控制执行时序多样性(performance.now() 模式、导航时序)。它们是独立的,可以设置为不同的值。
总结
确定性浏览器行为对于维护稳定的指纹身份、协调多会话工作流和验证隐私保护至关重要。BotBrowser 的 --bot-noise-seed 标志提供对所有指纹变化来源的完整控制,无论底层硬件如何,对相同配置文件和种子组合产生相同的输出。结合用于时序确定性的 --bot-time-seed 和用于栈深度控制的 --bot-stack-seed,BotBrowser 提供了覆盖每个可变浏览器输出的全面确定性模式。
相关主题请参阅什么是浏览器指纹、Audio 指纹保护、Canvas 指纹追踪以及噪声种子可重现性。