指纹

语音合成指纹:语音列表追踪

SpeechSynthesis API 语音列表如何揭示你的操作系统和平台,以及控制基于语音的指纹信号的技术。

文档中心

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

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

简介

Web Speech API 的 SpeechSynthesis 接口设计用于为 Web 应用提供文本转语音能力。它允许开发人员将文本转换为语音音频,实现辅助功能、语言学习工具和交互式语音体验。speechSynthesis.getVoices() 方法返回可用语音列表,每个语音都有名称、语言和是否在本地运行等属性。

虽然 API 服务于明确的辅助功能目的,但它暴露的语音列表在操作系统、浏览器版本和安装的语言包之间差异显著。Windows 11 系统可能报告 40 多个语音,包括 Microsoft David、Zira 和各种 Cortana 语音。macOS 系统报告完全不同的语音,如 Alex、Samantha 和一系列基于 Siri 的语音。使用 speech-dispatcher 的 Linux 系统报告又一组不同的语音。这种平台特定的变化使语音列表成为识别底层操作系统和配置的可靠信号。

隐私影响

SpeechSynthesis 语音列表是一个特别有效的指纹向量,因为它结合了高熵和低认知度。大多数用户不知道网站可以枚举他们安装的文本转语音语音,当这种情况发生时没有权限提示或通知。

隐私问题超越了简单的操作系统识别。语音列表不仅因操作系统而异,还因以下因素变化:

  • 操作系统版本:Windows 10 和 Windows 11 附带不同的默认语音集。macOS Ventura 和 macOS Sonoma 包含不同的 Siri 语音。
  • 语言包:安装额外语言包的用户获得新语音,创建更独特的指纹。
  • 第三方 TTS 软件:Balabolka、NaturalReader 或 NVDA 屏幕阅读器等应用可以向系统添加语音,进一步区分设备。
  • 浏览器版本:Chrome、Firefox 和 Edge 各自暴露系统可用语音的不同子集。

2021 年爱荷华大学研究人员的研究表明,当语音合成语音列表与其他浏览器信号结合时,与没有语音数据的指纹识别相比,可以将指纹唯一性提高 12-18%。语音列表尤其有价值,因为它揭示了在 User-Agent 精简努力之后难以通过其他方式获得的操作系统信息。

onvoiceschanged 事件增加了另一个维度:通过观察语音列表何时以及如何加载,追踪器可以推断浏览器内部初始化序列的信息,这在平台间不同。

技术背景

语音列表如何生成

当浏览器启动时,它查询操作系统的文本转语音子系统以获取可用语音。在 Windows 上,这意味着 Speech API(SAPI)和更现代的 OneCore 语音平台。在 macOS 上,浏览器查询 NSSpeechSynthesizer 框架。在 Linux 上,它通常使用 speech-dispatcher 或直接查询已安装的引擎如 eSpeak、Festival 或 Piper。

speechSynthesis.getVoices() 返回的每个语音对象有几个属性:

  • name:语音的显示名称(如 "Microsoft David - English (United States)")
  • lang:BCP 47 语言标签(如 "en-US")
  • localService:语音是在本地运行(true)还是需要网络连接(false)
  • voiceURI:标识语音的 URI
  • default:是否为其语言的默认语音

平台特定语音签名

语音列表充当平台签名。标准 Windows 11 安装报告以 "Microsoft" 开头的语音名称并包含平台特定的变体。macOS 报告 Apple 特定名称的语音,包括较新版本上的 Siri 语音。Android 上的 Chrome 报告完全不同的集合,通常包括 Google 品牌的语音。

这构成了一个标识符矩阵:语音数量、精确名称、语言覆盖范围以及本地/远程分类都构成平台指纹的一部分。甚至语音在数组中出现的顺序在平台间也可能不同。

异步加载行为

在大多数浏览器中语音加载是异步的。初始调用 getVoices() 可能返回空数组,完整列表在 voiceschanged 事件触发后才可用。此事件的时间以及 getVoices() 是否最初返回空列表,在浏览器和平台间不同。这种加载行为本身是指纹信号。

网络语音

一些浏览器包含需要互联网连接的基于网络的语音。这些语音的可用性取决于浏览器、用户的 Google 账户状态(对于 Chrome)和网络连接状况。网络语音的存在与否为指纹增加了另一层信息。

常见保护方法及其局限性

VPN 和代理服务器

VPN 改变 IP 地址但对语音合成语音列表没有影响。语音数据来自本地操作系统,而非网络。同一 VPN 后面的两台设备根据各自的操作系统和语言配置报告完全不同的语音列表。

隐身和隐私浏览

隐私浏览模式不改变语音列表。隐身模式下与正常窗口中可用的语音相同,因为语音列表从操作系统读取,而非浏览器存储。

浏览器扩展

修改 speechSynthesis.getVoices() 的扩展面临几个挑战:

  • 伪装返回值:扩展可以覆盖 getVoices() 返回自定义语音列表,但它报告的语音必须可用。如果网站尝试使用一个报告的语音但失败了,不一致性就会暴露。
  • 事件时间voiceschanged 事件行为难以从扩展控制。事件的时间、触发次数以及初始空数组行为都是平台特定的信号,扩展难以准确复制。
  • 属性描述符:通过扩展注入的 JavaScript 覆盖 getVoices() 会改变函数的属性描述符和原型链,这可以被检测到。

阻止 API

完全禁用 speechSynthesis 是可检测的:网站可以检查 API 是否存在以及是否返回结果。一个报告 speechSynthesis 可用但不返回任何语音的浏览器本身就是一个独特的信号。

BotBrowser 的引擎级方法

BotBrowser 在浏览器引擎级别控制语音合成语音列表。当加载指纹配置文件时,语音列表在任何页面代码执行之前就配置为匹配配置文件的目标平台。

配置文件控制的语音列表

chrome --bot-profile="/path/to/profile.enc" \
       --user-data-dir="$(mktemp -d)"

当加载代表 Windows 11 系统的配置文件时,speechSynthesis.getVoices() 返回该平台预期的精确语音列表,包括正确的名称、语言、localService 标志和顺序。无论实际主机操作系统如何,这都是正确的。

跨平台一致性

这是 BotBrowser 引擎级方法提供最大价值的地方。在 Linux 服务器上运行 Windows 配置文件通常会暴露 Linux 原生语音(eSpeak、Festival),立即揭示浏览器不在报告的平台上运行。BotBrowser 用配置文件预期的语音替换语音列表,在所有指纹面保持平台一致性。

真实的语音行为

BotBrowser 不仅返回静态列表。语音加载行为,包括异步 voiceschanged 事件时间和初始 getVoices() 返回模式,匹配配置文件目标浏览器和平台的预期行为。这确保了数据和加载行为都是一致的。

语音对象保真度

返回列表中的每个语音对象都具有准确的属性:正确的 name 格式、适当的 lang 标签、正确的 localService 值和真实的 voiceURI 字符串。配置文件数据从真实设备采集,确保每个属性都与真实安装报告的一致。

配置和使用

基本 CLI 用法

加载配置文件时自动提供语音列表保护:

chrome --bot-profile="/path/to/profile.enc" \
       --user-data-dir="$(mktemp -d)"

不需要额外标志。配置文件包含目标平台的完整语音列表。

Playwright 集成

const { chromium } = require('playwright-core');

(async () => {
  const browser = await chromium.launch({
    executablePath: '/path/to/botbrowser/chrome',
    args: ['--bot-profile=/path/to/profile.enc'],
    headless: true,
  });

  const context = await browser.newContext({ viewport: null });
  const page = await context.newPage();

  const voices = await page.evaluate(() => {
    return new Promise(resolve => {
      const v = speechSynthesis.getVoices();
      if (v.length > 0) return resolve(v.map(voice => ({
        name: voice.name, lang: voice.lang, local: voice.localService,
      })));
      speechSynthesis.onvoiceschanged = () => {
        resolve(speechSynthesis.getVoices().map(voice => ({
          name: voice.name, lang: voice.lang, local: voice.localService,
        })));
      };
    });
  });

  console.log(`Voice count: ${voices.length}`);
  console.log('Voices:', JSON.stringify(voices, null, 2));
  await browser.close();
})();

Puppeteer 集成

const puppeteer = require('puppeteer-core');

(async () => {
  const browser = await puppeteer.launch({
    executablePath: '/path/to/botbrowser/chrome',
    args: ['--bot-profile=/path/to/profile.enc'],
    headless: true,
    defaultViewport: null,
  });

  const page = await browser.newPage();
  await page.goto('about:blank');

  const voiceCount = await page.evaluate(() => {
    return new Promise(resolve => {
      const check = () => {
        const voices = speechSynthesis.getVoices();
        if (voices.length > 0) resolve(voices.length);
        else speechSynthesis.onvoiceschanged = () =>
          resolve(speechSynthesis.getVoices().length);
      };
      check();
    });
  });

  console.log('Voices available:', voiceCount);
  await browser.close();
})();

验证

使用配置文件启动 BotBrowser 后,验证语音列表:

function getVoices() {
  return new Promise((resolve) => {
    const voices = speechSynthesis.getVoices();
    if (voices.length > 0) return resolve(voices);
    speechSynthesis.onvoiceschanged = () =>
      resolve(speechSynthesis.getVoices());
  });
}

const voices = await getVoices();
console.log(`Voice count: ${voices.length}`);
voices.forEach(v =>
  console.log(`${v.name} (${v.lang}) local: ${v.localService}`)
);

需要检查:

  1. 语音数量匹配配置文件目标平台的预期数量
  2. 语音名称使用正确的平台命名约定(如 Windows 的 "Microsoft" 前缀)
  3. 语言标签适合目标区域配置
  4. localService 属性与平台预期的语音类型一致
  5. 语音列表不包含来自主机操作系统的语音
  6. 指纹测试工具显示语音数据和其他平台指标之间没有跨信号不一致

最佳实践

  1. 始终使用完整配置文件。 语音列表保护依赖于配置文件为目标平台提供准确的语音数据。
  2. 验证跨平台一致性。 在与目标不同的操作系统上运行配置文件时,检查语音列表匹配目标平台而非主机。
  3. 考虑区域对齐。 配置为日语区域的配置文件应包含日语语音。
  4. 不要在 BotBrowser 旁边安装 TTS 扩展。 第三方 TTS 浏览器扩展可能注册额外的语音,与配置文件的受控语音列表冲突。

常见问题

所有浏览器暴露相同的语音列表吗?

不。Chrome、Firefox、Edge 和 Safari 各自暴露操作系统可用语音的不同子集。BotBrowser 配置文件是浏览器特定的,Chrome 配置文件返回适合 Chrome 的语音,Edge 配置文件返回适合 Edge 的语音。

网站实际上能使用这些语音进行合成吗?

BotBrowser 的语音列表旨在确保指纹一致性。实际的文本转语音功能取决于主机系统的能力。在大多数自动化工作流程中,不需要 TTS 播放,但语音列表必须存在且准确以确保指纹一致性。

语音列表在浏览器版本之间会变化吗?

是的。浏览器更新有时会添加或移除语音支持。BotBrowser 配置文件带有版本控制,包含配置文件所代表的特定浏览器版本预期的语音列表。

语音列表保护在 headless 模式下工作吗?

是的。BotBrowser 无论浏览器在 headed 还是 headless 模式下运行都应用配置文件的语音列表。这很重要,因为 headless 环境通常没有 TTS 子系统,headless 模式下的空语音列表是强检测信号。

典型平台报告多少语音?

Windows 11 通常报告 30-50 个语音(取决于安装的语言包)。macOS 报告 60-80 个语音(包括 Siri 变体)。Android 上的 Chrome 报告 5-15 个语音。精确数量是 BotBrowser 控制的指纹信号之一。

voiceschanged 事件时间怎么处理?

BotBrowser 控制 voiceschanged 事件的时间和行为,使其匹配配置文件目标平台的预期模式。这包括 getVoices() 是否最初返回空数组以及事件在页面加载后多快触发。

总结

SpeechSynthesis API 的语音列表是一个高熵指纹信号,揭示操作系统、浏览器版本和语言配置。标准隐私工具无法解决它,因为语音数据来自操作系统而非网络或浏览器存储。BotBrowser 通过其配置文件系统在引擎级别控制语音列表,确保跨平台一致性并与所有其他指纹信号对齐。相关保护请参阅 Navigator 属性保护字体指纹控制时区和区域设置配置

#Speech-Synthesis#浏览器指纹识别#Privacy#Tts#Voice#Platform

让 BotBrowser 从研究走向生产

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