字体指纹:控制字体列表与度量
字体枚举和文本度量测量如何创建唯一的浏览器指纹,以及在引擎级别控制字体身份的技术方案。
简介
每个操作系统附带不同的字体集。用户为工作、设计或个人偏好安装额外的字体。这种已安装字体的组合创建了一个令人惊讶的唯一标识符。网站可以通过测量文本在特定大小下的渲染方式来探测哪些字体可用,由此得到的检测字体列表形成了字体指纹。除了字体列表本身之外,文本度量(如字形宽度、高度和边界框)在不同平台和字体渲染引擎之间也有所不同。字体枚举和文本度量结合在一起,给追踪系统提供了一个稳定、高熵的信号,跨会话持续存在。本文解释字体指纹追踪的工作原理以及 BotBrowser 如何在引擎级别控制字体身份。
隐私影响
字体指纹追踪作为可靠的追踪向量已有十多年历史。EFF 的 Panopticlick 研究发现,已安装字体集是熵值最高的浏览器属性之一,能够在其数据集中唯一标识超过 80% 的浏览器。2016 年阿德莱德大学的一项研究确认,字体列表平均提供 8 到 10 比特的识别信息,对于使用专业软件(设计师、开发者、安装了 CJK 语言包的用户)的用户来说显著更多。
随着操作系统的多样化,问题变得更严重。Windows、macOS 和 Linux 各自附带不同的默认字体集,且每个操作系统版本都会更改默认设置。Windows 11 包含 Windows 10 没有的字体。macOS Sonoma 与 macOS Ventura 不同。这些差异意味着字体指纹追踪通常可以在考虑任何其他信号之前就识别你的确切操作系统版本。
企业环境增加了另一个维度。企业字体许可证(Helvetica Neue、Futura、专有品牌字体)创建了与特定组织相关的独特指纹。使用 Adobe Fonts 或 Google Fonts 的创意专业人士拥有独特的大量字体集。
技术背景
字体指纹追踪依赖两种主要技术。
通过测量进行字体枚举
浏览器不暴露直接列出已安装字体的 API(已弃用的 document.fonts.check() 方案覆盖范围有限)。相反,追踪脚本使用基于测量的技术:
- 创建一个隐藏的 HTML 元素,使用已知的后备字体(monospace、serif 或 sans-serif)。
- 测量其渲染的宽度和高度。
- 将 font-family 更改为候选字体加后备。
- 再次测量。如果尺寸发生变化,候选字体已安装。
通过测试数百个字体名称,脚本可以构建已安装/未安装字体的二进制向量。这个向量就是字体指纹。
文本度量指纹追踪
除了字体列表之外,文本渲染的精确测量也因平台而异。包括:
- Canvas 文本度量。 在 Canvas 2D 上下文上使用
measureText()返回width、actualBoundingBoxAscent、actualBoundingBoxDescent、fontBoundingBoxAscent和fontBoundingBoxDescent等属性。这些值取决于字体渲染引擎(Windows 上的 DirectWrite、macOS 上的 CoreText、Linux 上的 FreeType)、微调设置和亚像素渲染配置。 - 元素边界框。
getBoundingClientRect()和getClientRects()返回的尺寸因字体整形、字距调整表和布局引擎行为而异。 - OffscreenCanvas 文本渲染。 绘制到 OffscreenCanvas 的文本产生因平台而异的像素数据,提供另一个测量向量。
字体列表和文本度量的组合创建了具有极高熵值的复合指纹。
为什么输出会变化
字体渲染是涉及多个层级的复杂过程:
- 字体文件格式。 TrueType 和 OpenType 字体包含不同的微调指令,影响小尺寸的渲染。
- 渲染引擎。 DirectWrite(Windows)、CoreText(macOS)和 FreeType(Linux)各自以不同方式实现文本整形、微调和光栅化。
- 亚像素渲染。 ClearType(Windows)、亚像素抗锯齿(macOS)和各种 FreeType 配置在亚像素级别产生明显不同的输出。
- DPI 和缩放。 高 DPI 显示器通过设备像素比缩放影响文本度量。
常见保护方案及其局限性
阻止字体枚举不切实际,因为该技术使用标准 CSS 和 DOM 测量 API。你无法在不破坏 Web 布局的情况下阻止 getBoundingClientRect()。
安装更多字体以融入实际上会适得其反。拥有 500 个字体的系统比只有操作系统默认字体的系统更独特。字体指纹变得更独特而非更少。
浏览器扩展声称保护字体指纹通常通过注入 CSS 覆盖或拦截测量 API 来工作。这些方案很脆弱。扩展可能覆盖了 measureText() 但遗漏了 getClientRects()。或者它可能报告通用字体列表,而实际渲染使用真实字体,造成可检测的不一致。
随机化文本度量会破坏 Web 应用。许多网站依赖准确的文本测量进行布局计算、文本编辑器和响应式设计。随机值会导致视觉故障和损坏的布局。
使用最小字体集(如 Tor Browser 所做的)减少了唯一性,但创建了自身的独特配置文件。拥有完全相同 Tor Browser 字体集的浏览器可以被识别为很可能是 Tor。
BotBrowser 的引擎级方案
BotBrowser 通过两种机制在 Chromium 引擎级别控制字体指纹追踪:字体列表管理和文本渲染控制。
受控的字体列表
当加载指纹配置文件时,BotBrowser 配置字体子系统以精确报告配置设备会拥有的字体。这不是 CSS 覆盖或 JavaScript 拦截。浏览器的内部字体枚举返回配置文件的字体列表。当网站探测特定字体时,响应(已安装或未安装)匹配配置系统。
这覆盖了:
- 系统字体枚举结果
- CSS font-family 解析顺序
document.fontsAPI 响应- 字体后备链行为
文本度量一致性
BotBrowser 的配置文件系统包含目标平台的文本度量数据。当通过任何 API 测量文本时,结果与配置设备的字体渲染引擎一致:
measureText()返回匹配配置平台文本整形和微调的度量。getBoundingClientRect()和getClientRects()返回与配置渲染引擎一致的尺寸。- Canvas 文本渲染产生匹配配置平台的像素输出。
- 客户端矩形噪声(通过
--bot-config-noise-client-rects和--bot-config-noise-text-rects控制)在启用时添加确定性变化。
跨平台字体仿真
BotBrowser 方案的一个关键优势是跨平台字体仿真。在 Linux 上运行时,你可以加载 Windows 配置文件并获得 Windows 典型的字体列表和文本度量。字体指纹匹配真实的 Windows 系统,而非假装拥有 Windows 字体的 Linux 系统。
这是可能的,因为 BotBrowser 在引擎级别控制字体渲染,而非通过字体文件安装。你不需要在你的 Linux 系统上安装 Windows 字体。
配置和用法
基本字体保护
chrome --bot-profile="/path/to/profile.enc" \
--user-data-dir="$(mktemp -d)"
字体配置选项
# 使用配置文件的字体设置(默认,推荐)
chrome --bot-profile="/path/to/profile.enc" \
--bot-config-fonts=profile
# 配置文件字体加系统后备字体
chrome --bot-profile="/path/to/profile.enc" \
--bot-config-fonts=expand
# 使用真实系统字体(无字体保护)
chrome --bot-profile="/path/to/profile.enc" \
--bot-config-fonts=real
控制文本度量噪声
# 启用客户端矩形噪声以实现测量变化
chrome --bot-profile="/path/to/profile.enc" \
--bot-config-noise-client-rects=true \
--bot-config-noise-text-rects=true \
--bot-noise-seed=42
Playwright 集成
const { chromium } = require('playwright');
const browser = await chromium.launch({
executablePath: '/path/to/botbrowser/chrome',
args: [
'--bot-profile=/path/to/profile.enc',
'--bot-config-fonts=profile',
'--bot-noise-seed=42'
]
});
const page = await browser.newPage();
await page.goto('https://example.com');
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-config-fonts=profile',
'--bot-noise-seed=42'
]
});
const page = await browser.newPage();
await page.goto('https://example.com');
验证
字体列表检查。 使用指纹测试网站查看检测到哪些字体。列表应匹配配置文件的目标操作系统和配置,而非你的实际系统。
文本度量检查。 使用 measureText() 测量标准字符串(例如 16px Arial 的 "Hello World"),并在会话和机器之间比较结果。使用相同的配置文件和噪声种子,值应完全相同。
平台一致性检查。 如果你在 Linux 上加载 Windows 配置文件,验证检测到的字体是 Windows 典型的(Segoe UI、Calibri、Consolas)而非 Linux 典型的(Liberation Sans、DejaVu Sans)。
跨 API 检查。 比较 CSS 测量、Canvas measureText() 和 getClientRects() 的字体检测结果。所有都应与相同的字体集一致。
最佳实践
- 使用
--bot-config-fonts=profile(默认)。 这提供最完整的字体保护。expand选项添加系统字体作为后备,可能引入本地变化。 - 将字体保护与 Canvas 噪声结合。 文本度量和 Canvas 渲染密切相关。两者都启用以实现全面保护。
- 使用匹配目标区域的配置文件。 CJK 字体集、RTL 字体和地区特定的默认字体差异很大。使用匹配你预期地区的配置文件。
- 使用多个字体探测列表进行测试。 不同的追踪系统探测不同的字体列表。针对多个测试工具验证你的保护。
- 避免安装独特字体。 如果使用
--bot-config-fonts=expand或real,系统上的不寻常字体将可检测。
常见问题
问:追踪脚本通常探测多少字体? 答:常见的指纹库测试 50 到 500 个字体名称。一些全面的脚本探测超过 1,000 个字体,针对特定平台和软件安装。
问:BotBrowser 是否在配置文件中嵌入实际字体文件? 答:BotBrowser 的配置文件包含控制字体枚举和文本度量行为所需的信息。引擎产生与配置平台一致的测量值,无需实际字体文件安装。
问:字体指纹追踪能识别我的特定操作系统版本吗? 答:能。默认字体集在操作系统版本之间变化。Windows 11、Windows 10、macOS Sonoma 和 macOS Ventura 各有不同的默认字体。追踪系统维护将字体集映射到操作系统版本的数据库。
问:expand 字体模式是做什么的?
答:expand 模式使用配置文件的字体列表作为主集,并添加系统字体作为后备。当你的应用需要特定的系统字体来渲染但你仍希望基于配置文件的字体枚举时,这很有用。
问:字体保护是否影响 Web 字体加载? 答:不影响。从服务器下载的 Web 字体(通过 @font-face)不受字体指纹保护的影响。只有本地/系统字体检测受到控制。
问:headless 和 headed 模式下文本度量是否一致? 答:是的。BotBrowser 在引擎级别控制文本度量,与显示模式无关。
总结
字体指纹追踪结合字体枚举和文本度量测量,创建一个高熵的追踪信号,在不同操作系统、版本和个别安装之间有所不同。BotBrowser 在引擎级别控制报告的字体列表和文本渲染度量,产生与加载的配置文件一致的结果,无论你的实际系统字体如何。使用 --bot-config-fonts=profile 和 --bot-noise-seed,字体指纹是稳定、一致且真实的。
相关主题请参阅什么是浏览器指纹、CSS 信号一致性、Canvas 指纹追踪以及屏幕和窗口保护。