Web Audio API 是如何用于浏览器指纹识别的 Web Audio API 简要概述

2025-05-27

Web Audio API 如何用于浏览器指纹识别

Web Audio API 的简要概述

您是否知道无需使用 cookie 或请求权限即可识别 Web 浏览器?

这被称为“浏览器指纹识别”,其工作原理是读取浏览器属性并将它们组合成一个标识符。该标识符是无状态的,在正常模式和隐身模式下都能正常工作。

浏览器指纹识别图

生成浏览器标识符时,我们可以直接读取浏览器属性,也可以先使用属性处理技术。今天我们要讨论的一项创新技术是音频指纹识别。

音频指纹识别是一项非常有价值的技术,因为它相对独特且稳定。它的独特性源于Web Audio API内部的复杂性和精密性。之所以能够实现这种稳定性,是因为我们所使用的音频源是通过数学生成的数字序列。这些数字稍后将被组合成一个音频指纹值。

在深入研究技术实现之前,我们需要了解 Web Audio API 及其构建块的一些想法。

Web Audio API 的简要概述

Web Audio API 是一个功能强大的音频操作处理系统。它旨在AudioContext通过连接音频节点并构建音频图来在内部工作。单个音频图AudioContext可以处理插入其他节点并形成音频处理链的多种类型的音频源。

音频上下文图

源可以是audio元素、流或通过 数学生成的内存源Oscillator。我们将使用Oscillator来实现我们的目的,然后将其连接到其他节点进行额外的处理。

在深入研究音频指纹实现细节之前,回顾一下我们将要使用的 API 的所有构建块会很有帮助。

音频上下文

AudioContext表示由音频节点链接而成的完整链。它控制节点的创建和音频处理的执行。AudioContext在执行任何其他操作之前,始终先创建一个实例。最好创建一个AudioContext实例,并在所有后续处理中重复使用它。

AudioContext具有目标属性,表示来自该上下文的所有音频的目的地。

还有一种特殊类型的AudioContextOfflineAudioContext。主要区别在于它不会将音频渲染到设备硬件上。相反,它会尽快生成音频并将其保存到 中AudioBuffer。因此,OfflineAudioContext 的目标将是内存数据结构,而常规 AudioContext 的目标将是音频渲染设备。

当创建 的实例时OfflineAudioContext,我们传递3参数:通道数、样本总数和每秒样本数的采样率。

const AudioContext = 
  window.OfflineAudioContext ||
  window.webkitOfflineAudioContext
const context = new AudioContext(1, 5000, 44100)
Enter fullscreen mode Exit fullscreen mode

音频缓冲区

表示AudioBuffer存储在内存中的音频片段。它旨在存储小片段。数据在内部以线性PCM格式表示,每个样本由介于32之间的-bit浮点数表示。它可以存储多个通道,但就我们的目的而言,我们只使用一个通道。-1.01.0.

32位数的示意图

振荡器

处理音频时,我们总是需要一个源。Anoscillator是一个不错的选择,因为它以数学方式生成样本,而不是播放音频文件。其最简单的形式是oscillator生成具有指定频率的周期性波形。

默认形状是正弦波。

正弦波
我们做了一个现场演示!你可以在我们的博客上体验一下。

还可以生成其他类型的波,例如方波、锯齿波和三角波。

默认频率为440Hz,即标准 A4 纸频。

压缩机

Web Audio API 提供了一个DynamicsCompressorNode,它可以降低信号最响部分的音量并有助于防止失真或削波。

DynamicsCompressorNode有很多有趣的属性,我们会用到它们。这些属性将有助于在不同浏览器之间创建更多变化。

  • Threshold- 以分贝为单位的值,高于该值时压缩机将开始生效。
  • Knee- 以分贝为单位的值,表示曲线平滑过渡到压缩部分的阈值以上范围。
  • Ratio- 输出发生 1 dB 变化所需的输入变化量(以1dB 为单位)。
  • Reduction- 浮点数,表示压缩器当前对信号应用的增益衰减量。
  • Attack- 降低增益 dB 所需的时间(以秒为单位)10。该值可以是小数。
  • Release- 增加 dB 增益所需的时间(以秒为单位)10

压缩机属性
我们做了一个现场演示!你可以在我们的博客上体验一下。

如何计算音频指纹

现在我们已经掌握了所有需要的概念,可以开始编写音频指纹代码了。

Safari 不支持无前缀的OfflineAudioContext,但支持
webkitOfflineAudioContext,因此我们将使用此方法使其在 Chrome 和 Safari 中工作:

const AudioContext =
  window.OfflineAudioContext ||
  window.webkitOfflineAudioContex
Enter fullscreen mode Exit fullscreen mode

现在我们创建一个AudioContext实例。我们将使用一个通道、一个44,100采样率和5,000总采样数,这将使其113长度约为 ms。

const context = new AudioContext(1, 5000, 44100)
Enter fullscreen mode Exit fullscreen mode

接下来让我们创建一个声源——一个oscillator实例。它将生成一个三角形的声波,1,000每秒波动次数为(1,000 Hz)。

const oscillator = context.createOscillator()
oscillator.type = "triangle"
oscillator.frequency.value = 1000
Enter fullscreen mode Exit fullscreen mode

现在让我们创建一个压缩器来增加更多变化并转换原始信号。请注意,所有这些参数的值都是任意的,仅用于以有趣的方式更改源信号。我们可以使用其他值,它仍然有效。

const compressor = context.createDynamicsCompressor()
compressor.threshold.value = -50
compressor.knee.value = 40
compressor.ratio.value = 12
compressor.reduction.value = 20
compressor.attack.value = 0
compressor.release.value = 0.2
Enter fullscreen mode Exit fullscreen mode

让我们将节点连接在一起:oscillatorcompressor,并将压缩器连接到上下文目标。

oscillator.connect(compressor)
compressor.connect(context.destination);
Enter fullscreen mode Exit fullscreen mode

现在该生成音频片段了。oncomplete完成后,我们将使用事件来获取结果。

oscillator.start()
context.oncomplete = event => {
  // We have only one channel, so we get it by index
  const samples = event.renderedBuffer.getChannelData(0)
};
context.startRendering()
Enter fullscreen mode Exit fullscreen mode

Samples是一个浮点数组,表示未压缩的声音。现在我们需要从该数组中计算出一个值。

让我们通过简单地对数组值的一部分进行求和来实现这一点:

function calculateHash(samples) {
  let hash = 0
  for (let i = 0; i < samples.length; ++i) {
    hash += Math.abs(samples[i])
  }
  return hash
}

console.log(getHash(samples))
Enter fullscreen mode Exit fullscreen mode

现在我们可以生成音频指纹了。我在 MacOS 的 Chrome 上运行它,得到了以下值:

  • 101.45647543197447

就是这样。我们的音频指纹就是这个数字!

您可以在我们的开源浏览器指纹库中查看生产实现。

如果我尝试在 Safari 中执行代码,我会得到不同的数字:

  • 79.58850509487092

在 Firefox 中得到另一个独特的结果:

  • 80.95458510611206

我们测试笔记本电脑上的每个浏览器都会生成不同的值。这个值非常稳定,在隐身模式下也保持不变。

该值取决于底层硬件和操作系统,并且在您的情况下可能会有所不同。

为什么音频指纹因浏览器而异

让我们仔细看看为什么这些值在不同浏览器中会有所不同。我们将分别在 Chrome 和 Firefox 中检查单个振荡波。

首先,让我们将音频片段的持续时间缩短至1/2000th一秒,这对应于单个波,并检查构成该波的值。

我们需要将上下文持续时间改为23样本,大致相当于1/2000th一秒。我们暂时跳过压缩器,只检查未修改oscillator信号的差异。

const context = new AudioContext(1, 23, 44100)
Enter fullscreen mode Exit fullscreen mode

现在,单个三角振荡在 Chrome 和 Firefox 中的外观如下:

一波

3但是,这两个浏览器的底层值是不同的(为了简单起见,我仅显示第一个值):

Chrome: Firefox:
0.08988945186138153 0.09155717492103577
0.18264609575271606 0.18603470921516418
0.2712443470954895 0.2762767672538757

让我们看一下这个演示来直观地看到这些差异。

浏览器的音频波
我们做了一个现场演示!你可以在我们的博客上体验一下。

从历史上看,所有主流浏览器引擎(Blink、WebKit 和 Gecko)的 Web Audio API 实现都基于最初由 Google 在 WebKit 项目中开发的2011代码2012

Google 对 Webkit 项目的贡献示例包括:
创建OfflineAudioContext
创建OscillatorNode创建 DynamicsCompressorNode

从那时起,浏览器开发者就做了很多细微的改动。这些改动,加上大量的数学运算,导致了指纹识别的差异。音频信号处理使用浮点运算,这也导致了计算结果的差异。

您可以看到这些功能现在在三大浏览器引擎中是如何实现的:

此外,浏览器会针对不同的 CPU 架构和操作系统使用不同的实现,以利用SIMD等功能。例如,Chrome 在 macOS 上使用单独的快速傅里叶变换实现(产生不同的oscillator信号),并在不同的 CPU 架构上使用不同的矢量运算实现(这些实现用于 DynamicsCompressor 实现)。这些特定于平台的更改也会导致最终音频指纹的差异。

指纹结果还取决于 Android 版本(例如,在 Android 中9和在同一设备上是不同的)。10

根据浏览器源代码,音频处理不使用专用音频硬件或操作系统功能 - 所有计算都由 CPU 完成。

陷阱

当我们开始在生产环境中使用音频指纹识别时,我们的目标是实现良好的浏览器兼容性、稳定性和性能。为了实现更高的浏览器兼容性,我们还考虑了注重隐私的浏览器,例如 Tor 和 Brave。

离线音频上下文

正如您在caniuse.com上看到的OfflineAudioContext它几乎可以在任何地方使用。但有些情况需要特殊处理。

第一种情况是 iOS11或更早版本。它支持OfflineAudioContext,但渲染仅在由用户操作(例如按钮点击)触发时启动。如果context.startRendering不是由用户操作触发,则context.state将会suspended,并且渲染将无限期挂起,除非您添加超时。目前仍在使用此 iOS 版本的用户并不多,因此我们决定为他们禁用音频指纹识别。

第二种情况是 iOS12或更新版本的浏览器。如果页面在后台运行,它们可能会拒绝启动音频处理。幸运的是,浏览器允许您在页面返回前台时恢复处理。
当页面激活时,我们会尝试context.startRendering()多次调用,直到context.state变为running。如果多次尝试后处理仍未启动,代码将停止。我们还会在重试策略的基础上使用常规方法,setTimeout以防出现意外错误或卡顿。您可以在此处查看代码示例

Tor

在 Tor 浏览器中,一切都很简单。Web Audio API 在那里被禁用,因此无法进行音频指纹识别。

勇敢的

对于 Brave 来说,情况就更加微妙了。Brave 是一款基于 Blink 的注重隐私的浏览器。它以对音频样本值进行轻微随机化而闻名,它称之为“farbling”。

Brave 的术语“Farbling”是指对半识别性浏览器功能的输出进行轻微随机化,使其难以被网站检测到,但不会破坏良性的、为用户提供服务的网站。这些“farbled”值是使用每个会话、每个 eTLD +1 种子确定性生成的,因此,一个网站在同一会话中每次尝试指纹识别时都会获得完全相同的值,但不同的网站会获得不同的值,而同一个网站在下一次会话中也会获得不同的值。这项技术源于先前的隐私研究,包括PriVaricator(Nikiforakis 等人,WWW 2015)和FPRandom(Laperdrix 等人,ESSoS 2017)项目。

Brave 提供三个级别的 farbling(用户可以在设置中选择他们想要的级别):

  • 已禁用 — 不应用任何 farbling。指纹与其他 Blink 浏览器(例如 Chrome)相同。
  • 标准 — 这是默认值。音频信号值会乘以一个固定值,称为“模糊”因子,该值在用户会话中对于给定域是稳定的。实际上,这意味着音频波听起来和看起来都一样,但存在一些细微的变化,这使得其难以用于指纹识别。
  • 严格——声波被伪随机序列取代。

farbling通过转换原始音频值来修改原始的 Blink 。AudioBuffer

恢复勇敢的标准

要恢复模糊数据,我们首先需要获取模糊因子。然后,我们可以通过将模糊值除以模糊因子来恢复原始缓冲区:

async function getFudgeFactor() {
  const context = new AudioContext(1, 1, 44100)
  const inputBuffer = context.createBuffer(1, 1, 44100)
  inputBuffer.getChannelData(0)[0] = 1

  const inputNode = context.createBufferSource()
  inputNode.buffer = inputBuffer
  inputNode.connect(context.destination)
  inputNode.start()

  // See the renderAudio implementation 
  // at https://git.io/Jmw1j
  const outputBuffer = await renderAudio(context)
  return outputBuffer.getChannelData(0)[0]
}

const [fingerprint, fudgeFactor] = await Promise.all([
  // This function is the fingerprint algorithm described
  // in the “How audio fingerprint is calculated” section
  getFingerprint(),
  getFudgeFactor(),
])
const restoredFingerprint = fingerprint / fudgeFactor
Enter fullscreen mode Exit fullscreen mode

遗憾的是,浮点运算缺乏精确获取原始样本所需的精度。下表展示了不同情况下恢复的音频指纹,并显示了它们与原始值的接近程度:

操作系统、浏览器 指纹 目标指纹之间的绝对差异
macOS 11、Chrome 89(目标指纹) 124.0434806260746
macOS 11、Brave 1.21(相同设备和操作系统) 浏览器重启后各种指纹:
124.04347912294482
124.0434832855703
124.04347889351203
124.04348024313667
0.00000014% – 0.00000214%
Windows 10,Chrome 89 124.04347527516074 0.00000431%
Windows 10,Brave 1.21 浏览器重启后各种指纹:
124.04347610535537
124.04347187270707
124.04347220244154
124.04347384813703
0.00000364% – 0.00000679%
Android 11,Chrome 89 124.08075528279005 0.03%
Android 9,Chrome 89 124.08074500028306 0.03%
ChromeOS 89 124.04347721464 0.00000275%
macOS 11,Safari 14 35.10893232002854 71.7%
macOS 11,Firefox 86 35.7383295930922 71.2%

如您所见,恢复后的 Brave 指纹与其他浏览器的指纹相比,更接近原始指纹。这意味着您可以使用模糊算法进行匹配。例如,如果一对音频指纹数字之间的差异大于0.0000022%,则可以假设它们是不同的设备或浏览器。

表现

Web Audio API 渲染

让我们来看看 Chrome 在音频指纹生成过程中的底层原理。在下面的屏幕截图中,横轴表示时间,行表示执行线程,条形表示浏览器繁忙时的时间片。您可以在这篇Chrome 文章中了解有关性能面板的更多信息。音频处理开始于809.6 ms,完成于814.1 ms

主线程(在图片中标记为“Main”)负责处理用户输入(鼠标移动、点击、轻触等)和动画。当主线程繁忙时,页面会卡住。建议避免在主线程上运行超过几毫秒的阻塞操作。

如上图所示,浏览器将部分工作委托给线程OfflineAudioRender,从而释放了主线程。
因此,在大部分音频指纹计算过程中,页面都能保持响应。

Web Audio API 在Web Worker中不可用,因此我们无法在那里计算音频指纹。

不同浏览器的性能总结

下表显示了在不同浏览器和设备上获取指纹所需的时间。该时间是在冷页面加载后立即测量的。

设备、操作系统、浏览器 指纹采集时间
MacBook Pro 2015(Core i7),macOS 11,Safari 14 5毫秒
MacBook Pro 2015(Core i7),macOS 11,Chrome 89 7毫秒
宏碁 Chromebook 314,Chrome OS 89 7毫秒
Pixel 5、Android 11、Chrome 89 7毫秒
iPhone SE1、iOS 13、Safari 13 12毫秒
Pixel 1,Android 7.1,Chrome 88 17毫秒
Galaxy S4,Android 4.4,Chrome 80 40毫秒
MacBook Pro 2015(Core i7),macOS 11,Firefox 86 50毫秒

音频指纹识别只是整个识别过程的一小部分。

音频指纹识别是我们开源库用来生成浏览器指纹的众多信号之一。然而,我们不会盲目地整合浏览器中所有可用的信号。相反,我们会分别分析每个信号的稳定性和独特性,以确定它们对指纹准确性的影响。

对于音频指纹,我们发现信号对独特性的贡献很小,但非常稳定,导致指纹准确度的净增长很小。

您可以在我们的浏览器指纹识别初学者指南中了解有关稳定性、独特性和准确性的更多信息

亲自尝试浏览器指纹识别

浏览器指纹识别是各种反欺诈应用中一种有效的访客识别方法。它尤其适用于识别那些试图通过清除 Cookie、隐身模式浏览或使用 VPN 来规避追踪的恶意访客。

您可以尝试使用我们的开源库自行实现浏览器指纹识别。FingerprintJS 是最流行的浏览器指纹识别库,在 GitHub 上拥有超过 100 个12Kstar。

为了提高识别准确率,我们还开发了FingerprintJS Pro API,它利用机器学习将浏览器指纹识别与其他识别技术相结合。您可以免费试用 FingerprintJS Pro 数10日,没有任何使用限制。

联系我们

文章来源:https://dev.to/savannahjs/how-the-web-audio-api-is-used-for-browser-fingerprinting-4oim
PREV
面向 Web 开发者的最佳 GitHub Repos Node.js 最佳实践 HTML5 Boilerplate RealWorld 你还不了解 JS Airbnb JavaScript 指南 Storybook 前端清单灵感与资源总结
NEXT
大 O 符号 - 简介