返回知识中心
指纹

噪声种子可复现性:让指纹运行结果可重复

固定的整数噪声种子如何让 Canvas、WebGL、Audio 和文本测量输出在会话、重启和 CI 运行之间保持重复,以及如何验证这一点。

BotBrowser Team

文档中心

想直接看维护中的产品文档?

这篇文章对应的主题已经有文档中心页面。需要规范流程、当前参数和长期参考时,优先看 docs。

噪声种子让 Canvas、WebGL、音频和文本测量的噪声在多次启动之间重复,从而让多次运行可以比较。BotBrowser 的 --bot-noise-seed(ENT Tier2)在配置文件、构建版本和启动参数都相同时实现这一点,但它不决定设备身份、不恢复登录状态,也不会让指纹匿名。

要点摘要

使用 1 到 4294967295 之间的整数,把每个身份的种子与其配置文件名一起存放,并且不要让两个身份共用同一个配置文件和种子。验证方法是用相同种子启动两次(数值应一致)、再比较不同种子并记录变化或碰撞;种子为 0 不保证可重复。

本文目录

为什么可重复的种子让多次运行可以比较

如果一项指纹测试每次运行都给出不同的结果,就很难让人信任。指纹噪声会给 Canvas 像素、WebGL 读回数据、音频采样值和文本测量结果加入细小的变化,使浏览器不会直接呈现某一台机器的原始输出。如果这种变化在每次启动时都重新抽取,你就无法判断结果的变化来自你的配置、被测页面,还是噪声本身。固定的噪声种子可以排除第三种可能。 BotBrowser 可以在受控环境中,让固定配置文件、构建和种子下的 Canvas、WebGL、音频和文本测量输出重复;它不能让这些输出变得匿名,也不能保证在不同构建、配置文件或参数下得到相同结果。

噪声种子是一个用来初始化伪随机数生成器的整数。生成器从相同的起始值出发会产生相同的序列,所以使用相同种子的两次启动,会对同一次绘制或测量施加相同的变化;换成不同的种子,则会施加另一种同样稳定的变化。输出何时应该重复、何时应该改变,由你来决定。 关于如何为稳定身份选择种子并验证种子可重复,请参阅 确定性模式:可重现的浏览器指纹,其中介绍了种子控制的内容以及如何检查可重现性。

下面四类工作通常需要固定种子:

  • 会话连续性。 在多次启动之间回访同一个服务,并且每次呈现相同的渲染输出。
  • 彼此独立且稳定的身份。 给不同的账号、租户或测试用户各自分配一套不会在启动之间变化的噪声模式。
  • 运行之间的对比。 在比较两个构建、两个页面或两个配置文件时,把渲染噪声从变量中排除。
  • CI 复现。 还原早先一次运行的条件,让失败的对比可以被排查,而不是靠猜。

整数种子驱动噪声生成器,决定 Canvas、WebGL、音频和文本测量的输出;相同的种子、配置文件、构建和参数会重复输出,不同的种子则会改变输出

下面的章节会沿着一个种子展开:它作用于哪些接口,如何把种子分配给不同身份,如何做重启检查和 CI 记录,以及汇报结果时应当说明哪些限制。参考数值来自公开的噪声种子文档和命令行参数参考,二者都列在来源部分。

种子会重复什么,不会改变什么

BotBrowser 会对 Canvas 2D、WebGL、WebGPU、AudioContext、ClientRects 和 TextRects 施加受控的噪声。--bot-noise-seed 参数(ENT Tier2)让这些噪声变得确定。在配置文件、构建和启动参数都相同的前提下,固定的种子会让每次会话、每次重启后得到相同的 Canvas 读回数据、相同的 WebGL 和 WebGPU 读回数据、相同的音频采样值,以及相同的文本和元素测量结果。

页面正是在读回数据时看到这些效果。HTMLCanvasElement.toDataURL() 会为画布当前持有的像素返回一个数据 URL,除非调用方指定其他类型,否则就是 PNG,因此渲染中的任何变化都会直接体现在返回的字符串里。OfflineAudioContext 会把一组音频节点渲染进缓冲区而不播放,页面再从缓冲区读取采样值。measureText() 和 getBoundingClientRect() 这类调用返回的是数字而不是像素,但读取方式相同:页面提问,浏览器回答,答案要么与上一次一致,要么不一致。种子作用于答案,而不是问题。 关于音频输出如何成为指纹以及如何检查音频一致性,请参阅 音频指纹详解:AudioContext 如何追踪你,其中解释了音频为何能作为追踪信号。

这种重复与内容绑定。页面在种子 42 下于两次会话中绘制同一个场景,读回的像素相同;在种子 43 下绘制同一个场景,读回的像素不同,但同样稳定。如果场景本身在两次运行之间发生变化,输出的变化就与种子无关,所以每次对比都需要固定的场景。 关于同一幅绘图为何在不同设备上结果不同,请参阅 Canvas 指纹:工作原理与应对方法,其中介绍了 Canvas 指纹测量的内容以及如何检查稳定性。

可接受的取值范围是 1 至 4294967295,即无符号 32 位整数的最大值。取值 0 会保留配置文件的默认行为,并且不会给生成器设定种子,因此不保证取值 0 时输出可重复,不应把它用作基线。需要可重复性时,请在每次启动时都传入一个正整数,并确认你的启动器或测试框架确实转发了这个参数,因为误用 0 或者丢失参数,会悄悄地把确定性的设置变成会变化的设置。

种子并不决定呈现哪一类设备。设备身份的数值,例如用户代理字符串、屏幕尺寸和平台,由配置文件定义。种子只是在这些数值之上塑造渲染和测量的变化。配置文件相同而种子不同的两次会话,共享配置文件的数值,只在噪声上有区别;而种子不会把两个不同的配置文件合并成同一个身份。

参数参考中,每个噪声通道都有自己的开关:--bot-noise-canvas、--bot-noise-webgl-image、--bot-noise-audio-context、--bot-noise-client-rects 和 --bot-noise-text-rects。每个开关接受 true 或 false。种子决定噪声的样子,开关决定哪些接口会被施加噪声,所以这两种控制回答的是不同的问题。当结果不再重复时,关闭某一个通道是有用的排查步骤,但在依赖这套设置之前要把它重新打开。

还有几项内容不在种子的控制范围内。Cookie、存储和登录状态保存在 --user-data-dir 指定的目录中,种子不会恢复登录。执行时序有自己的控制:--bot-time-seed 接受 1 至 4294967295 的整数,用于可重复的时序策略,0 表示关闭。单靠噪声种子不能让时序重复,时间种子也不会改变 Canvas 输出。当一项测试需要渲染和时序都重复时,请有意识地设置两个值,并同时记录它们。

为身份分配种子并避免产生关联

把种子当作按身份配置的参数。配置文件加种子 100 是一个稳定的指纹,同一个配置文件加种子 200 是另一个带有自己噪声模式的指纹。这种区分正是为每个账号、租户或测试用户各配一个种子的原因,也让每个身份在下一次访问时能呈现相同的渲染输出。

需要防范的是相反的情况。如果两个身份使用相同的配置文件和相同的种子,它们会产生完全相同的渲染输出,这份共同的输出可能把它们关联起来。种子是配置,而不是能阻止关联的秘密:任何持有相同构建、配置文件和种子的人,都能复现相同的输出。真正能把不同身份分开的,是任何两个身份都不共用同一组配置文件和种子。

请在版本控制中,紧挨着配置文件名保存一张种子与身份的对应表。每一行应包含身份、配置文件名、种子,以及这一组配对的创建日期。

[
  { "identity": "tenant-a", "profile": "profile-a.enc", "seed": 1001 },
  { "identity": "tenant-b", "profile": "profile-a.enc", "seed": 1002 },
  { "identity": "tenant-c", "profile": "profile-b.enc", "seed": 1003 }
]

在这张表中,租户 A 和租户 B 共用一个配置文件,但种子不同,因此它们的噪声模式不同。租户 C 使用另一个配置文件。如果把租户 B 改成种子 1001,就会有两个身份持有相同的配置文件和相同的种子,这正是应当作为关联风险上报的模式,并且要在下一次运行之前为其中一个身份分配新的种子。

每个种子只生成一次并保存下来。不要用时间戳或会重置的计数器来推导种子,因为这会悄悄地把确定性的设置变成每次启动都不同的值。种子无法从输出中反推出来,所以这张表是唯一的记录。有效范围内的任何正整数都可以使用,选用很大、质数或带规律的数值没有任何好处。连续的数值也没有问题,只要这张表能说明谁持有哪一个。

当一个浏览器进程需要承载多个身份时,按上下文的指纹配置(ENT Tier3)可以让每个 BrowserContext 拥有自己的 --bot-noise-seed。文档中的做法是在上下文创建第一个页面之前设置这些参数,这样渲染器一开始就带着正确的数值。第二个上下文使用不同的种子,会产生另一种同样可重复的模式。

跨重启验证同一个种子

下面的检查用来确认:同一个种子会重复输出,另一个种子会改变输出。它需要一个由你控制的页面,或者一个你信任的、每次都绘制相同场景的测试页面。页面应当绘制一个固定的 Canvas 场景并用 toDataURL() 读回,读回一个固定的 WebGL 场景,渲染一段固定的离线音频缓冲区,并测量一个固定的字符串。请记录每个值或它的简短摘要,这样对比才是精确的。每次都以相同的方式加载页面,窗口尺寸和加载顺序保持一致,让种子成为两次运行之间唯一有意改变的因素。

使用全新的用户数据目录,可以把旧的浏览器状态排除在对比之外:

chromium-browser \
  --bot-profile="path/to/profile.enc" \
  --bot-noise-seed=42 \
  --user-data-dir="$(mktemp -d)"

请按下面的顺序执行验证:

  1. 使用该配置文件和 --bot-noise-seed=42 启动,加载页面,并记录 Canvas、WebGL、Audio 和文本的值。
  2. 关闭浏览器,用相同的配置文件和种子再次启动,并加载同一个页面。每个记录的值都应与第一次一致。
  3. 把种子改为 43,第三次运行该页面。记录每个值是变化还是与第一次碰撞;变化说明种子影响该信号面,碰撞说明换一个整数并不保证唯一性。
  4. 用 --bot-noise-seed=0 启动两次。不保证这些值重复,因此不要把它们当作基线,因为 0 不会给生成器设定种子。
  5. 把构建、配置文件、宿主操作系统和完整的参数列表,与结果一起保存。

第五步是人们最容易跳过的一步,也是日后能解释不匹配的那一步。只有当构建、配置文件、种子和启动参数都相同时,一个结果才能与另一个结果比较。

当第二次会话与第一次不一致时,请先排除常见的原因,再怀疑种子:

  • 场景变了:两次运行之间文本、画布尺寸或绘制顺序不同。
  • 种子没有送达:启动器丢掉了参数,或者取值是 0。
  • 加载了不同的配置文件或不同版本的配置文件。
  • 使用了不同的 BotBrowser 构建或不同的宿主操作系统目标。
  • 某个框架或包装脚本添加了额外的参数。
  • 某个噪声通道在一次运行中被关闭,在另一次中保持开启。

对于在另一台机器上表现不同的设置,文档给出了同样的建议:比较 BotBrowser 构建、配置文件版本、宿主操作系统目标和完整的启动参数。文档还建议在比较运行结果时,保持网络路径、语言区域与时区以及机器负载稳定,因为即使种子没有变,这些因素也可能改变页面的行为。

不匹配是一项发现,而不是需要被平均掉的噪声。在证明并非如此之前,请把它记为构建、配置文件或参数的差异,并把两组结果连同各自的记录一起保存。反复重跑直到数值碰巧一致,只会掩盖原因,让下一次运行同样没有把握。

不靠猜地收窄不一致

步骤先比较什么这里一致意味着
1完整参数列表种子和通道开关都送到了
2配置文件摘要加载的是同一份配置文件字节
3构建串同一个二进制产生了两次运行
4场景输入页面两次画出了同样的东西

上面六种原因容易列出来却难以分开,因为它们的症状完全一样:两份记录对不上。按这个顺序走完四项输入,每项只花一次比较,就能在动页面之前排除掉那些廉价的解释。

先比较参数列表,而且要按文本比较,不要凭记忆。启动器丢了一个参数、包装脚本自己加了一个、shell 把变量展开了另一种样子,留下的痕迹都一样:产生不一致取值的那次运行,从来没有你以为的那个种子。把两次运行实际生效的参数打印出来做 diff。

接着比较配置文件,比摘要而不是比文件名。不同目录下同名的两个文件,或者更新配置文件时被替换掉的同一个文件,造成的不一致看起来像种子问题,实际是配置文件问题。比较摘要把它变成一个「是或否」的回答。

然后是构建串,取完整字符串而不是只看主版本号。同一个主版本号内的次版本更新可以改变某条绘制路径,而所有可见的版本数字看起来都还很熟悉。如果构建不同,就别再把两份记录当作可比,把它们各自保留成独立的基线。

只有这三项都一致,才值得检查场景。比较画布尺寸、文本字符串、绘制顺序和音频缓冲参数,因为其中任何一项都会改变生成器收到的输入,从而改变同一个种子产生的输出。场景变化是唯一一种情形:种子正常工作,而结果依然不同。

要定位某个不再重复的接口,可以一次关闭一个通道,对比之后再恢复:

chromium-browser \
  --bot-profile="path/to/profile.enc" \
  --bot-noise-seed=42 \
  --bot-noise-canvas=false

如果在关闭 Canvas 噪声时其他接口仍然重复,说明发生变化的是 Canvas 场景或它的读回过程。请恢复该开关,并把最终的命令行记在笔记里,方便在下一次会话中直接粘贴同样的参数。

在 CI 中复现结果

CI 任务是一次无人看守的启动,所以它需要一份原本由人记在脑子里的记录。请在任务配置中固定 BotBrowser 构建、配置文件、种子和完整的启动参数,并把它们连同验证页面报告的值一起写入任务输出。当几周后某次对比失败时,这份输出能让你在另一台机器上复现这次运行,而不是靠猜。

每个种子的有用记录包含几个字段:

  • 种子值,以及它所属的身份或测试用例。
  • 配置文件名及其版本。
  • BotBrowser 构建。
  • 宿主操作系统目标和机器类型。
  • 完整的参数列表,包括任何时间种子。
  • 验证页面报告的值,以及运行日期。

每次 CI 运行都使用全新的用户数据目录,就像上面的启动方式一样,这样旧的 Cookie 和存储就不会影响对比。请把种子保存在任务配置里,而不是在任务内部挑选,因为在运行时才选出的种子,每次运行都是不同的值。

把验证页面作为任务的第一步运行,放在依赖它的测试之前。如果它报告的值与保存的基线一致,就继续。如果报告的值不同,就停下来,说明构建、配置文件、种子或参数中哪一项发生了变化,而不是让后面的测试因为难以察觉的原因而失败。

升级是另一个需要再次验证的时刻。在更新 BotBrowser、替换配置文件、换到另一台运行机或修改参数之后,请用保存的种子运行一次该页面,并与保存的值对比。如果一致,就继续。如果不同,就把新值当作新的基线,写下产生它们的构建,并且不要为了找回旧输出而修改种子,因为发生变化的并不是种子。

有时你恰恰想要一种新的模式,例如淘汰一个测试身份并启用另一个。请有意地更换种子,在同一次提交中更新对应表和基线,并保留旧的那一行及其构建号。旧种子仍然是对该构建在该配置文件下产生了什么的正确描述。

跨机器和跨操作系统的可重复性遵循同样的规则。在每台运行机上,用相同的构建、配置文件和参数运行该页面,并对比数值。匹配是对这些机器有效的证据,不匹配则应作为构建、配置文件或参数的不一致去追查,而不是忽略。

区分种子不匹配与夹具漂移

只有重复运行才能把「换种子」和「换夹具」区分开,而比较成立的前提是场景、构建和 profile 全部保持不变。下面的测量来自同一台机器上的同一个探针页:构建为 Chromium 144.0.7559.97,Windows 10 profile,headless Linux 宿主,固定的 220×60 canvas 绘制、固定的 64×64 WebGL clear,以及固定的离线音频缓冲区。摘要是 canvas.toDataURL() 的 SHA-256 前 32 个十六进制字符。

种子canvas 摘要配对情况说明
42fd0881c19451370e66313f61e760c77c运行两次,完全一致同一个种子可复现
43fd0881c19451370e66313f61e760c77c与种子 42 相同不同种子也可能复现出早先的输出
4406ff31e59678fe4d923a85d73479fe2e新输出不同种子可能产生新输出
4506ff31e59678fe4d923a85d73479fe2e与种子 44 相同这种碰撞不是单次偶然
465877be94c6969400c80033f0a2f4528d新输出第三种不同输出
778138eccb72cb091408fc684572c3504a新输出证实摘要确实随种子变化
123455877be94c6969400c80033f0a2f4528d与种子 46 相同第二次碰撞

由此有两条结论,两者都比看上去更窄。

同一个种子精确复现,这正是该参数承诺的保证。但两个不同的种子也可能产生相同输出:本次七组种子里有三组与更早的一组相同。因此换种子并不是获得不同指纹的可靠方法。应把种子看作有限映射的一个输入,而不是唯一身份标识;当两个身份不能共享渲染输出时,要去验证输出,而不是假定换一个新整数就够了。

同一次运行中,WebGL 摘要、音频样本总和和文本宽度在所有种子下都没有变化,只有 canvas 摘要变动。这是在单一构建、单一 profile、单一宿主下的观察,不是关于该参数在所有平台覆盖哪些面的结论;噪声通道开关是独立的控制项,这次探针并没有切换它们。改用 headful 显示、换成 macOS profile、并打开 WebGL 噪声通道重做一遍,才能看出这种分布是否在别处成立。

匹配的结果能说明什么,不能说明什么

一次通过的验证只支持一个范围很窄的结论:某个构建、配置文件和种子,在某台机器上重复了相同的 Canvas、WebGL、Audio 和文本测量输出,而另一个种子在已测信号面上产生了变化或碰撞。它不支持这样的说法:同样的结果在每台机器上、每次升级之后、每个页面上都成立。按前一种方式记录结果,数月之后这份记录依然准确。

可重复的指纹是一种取舍。每次访问都呈现相同的输出,正是会话连续性的来源,同时也让重复的访问能被识别为重复。固定的种子不会让指纹变得匿名或唯一,也无法说明某个具体站点会如何对待它。渲染只是信号的一部分。来自配置文件的设备身份、网络路径、时序,以及站点如何处理这次访问,都是另外的控制。

BotBrowser 支持固定整数 --bot-noise-seed(取值 1 至 4294967295),让同一配置文件下的 Canvas、WebGL、AudioContext 和文本测量输出在多次会话和重启之间保持重复,团队可据此比较运行结果、复现 CI 结果,并为每个身份保留稳定的渲染基线。BotBrowser 不能让带种子的输出变得匿名,或不受站点自身检查的影响,不能保证在不同构建、配置文件或启动参数下得到相同的结果,也不替代按身份分配种子、网络和站点侧的控制。

相关背景可参阅确定性模式、Canvas 指纹、音频指纹保护和配置文件管理。

相对参考文档新增的内容

参考页列出种子选项、取值范围和作用面。对比时固定配置文件、构建版本、启动参数和测试场景,先用种子 42 启动两次,再用种子 43;这是建议执行的方法,不是已报告的实测结果。数值不同时,可按上方排障步骤区分场景或参数变化与配置文件、构建版本或主机差异,再决定是否调整种子。

来源

实用结论

同一种子的两次运行不一致时,在证明相反之前,把它当作构建、配置文件、参数或测试页面的差异,不要反复重跑直到数值恰好吻合。在 CI 中把种子固定在任务配置里,先运行验证页面,与已存基线不同就停止任务。只有有意更换种子时才改动它,并在同一次提交中更新对照表与基线。

#Noise-Seed#Deterministic#浏览器指纹识别#Reproducibility#隐私#Ci-Cd

本文属于指南: 什么是浏览器指纹?原理、风险、信号与隐私边界

让 BotBrowser 从研究走向生产

先用这些指南理解模型,再进入跨平台验证、隔离上下文和面向规模化的浏览器部署。