在排查蓝牙低功耗(BLE)多点连接断开问题时,一项更值得警惕的发现浮出水面:有网站会通过 Web Audio API 创建几乎听不见的音频流,并分析设备音频链路的处理差异,用来区分用户设备。来源摘要指出,AliExpress 采用了这类静默音频流进行设备指纹识别。
这件事的重点不在于网页是否真的播放了“声音”,而在于浏览器、操作系统、驱动和音频硬件共同形成了一条可测量的信号通道。即使用户没有主动播放音乐,网页也可能从音频上下文的输出结果中获得稳定特征。
静默音频为什么能成为指纹
Web Audio API 允许页面创建 AudioContext,连接振荡器、增益节点、压缩器和分析器等组件。页面可以生成低音量甚至静音的信号,再读取处理后的数值,例如频谱、采样值、压缩器反应或渲染耗时。
这些结果可能受到多种因素影响:
- 操作系统的音频混音和采样率转换策略;
- 浏览器使用的音频后端与实现细节;
- CPU、音频驱动和硬件路径的浮点计算差异;
- 输出设备、蓝牙音频链路以及系统音效处理;
AudioContext初始化时机、状态和延迟特征。
单独一个数值通常不足以识别设备,但多个特征组合后,可能形成较稳定的设备画像。更麻烦的是,用户可能没有看到明显的播放控件,也不会意识到页面初始化了音频上下文。
它和 BLE 多点断连有什么关系
二者不一定存在直接因果关系。更合理的解释是:在调查 BLE 多点断连、音频设备切换或系统输出路径时,开发者观察到了网页音频上下文的异常行为,进而发现了潜在的音频指纹机制。
不过,BLE 设备确实可能改变音频链路的状态。例如蓝牙耳机连接、切换配置文件或发生重连时,系统可能更换输出设备、采样率和混音路径。这既会影响 Web Audio 的延迟与处理结果,也会制造新的可观测特征。因此,调试时应把两个问题分开记录:
- BLE 连接状态、连接参数和断开原因;
- 浏览器音频上下文的创建、输出设备变化和渲染结果。
如果把它们混在一起,很容易把系统音频路径变化误判成网页代码问题,或者忽略网页正在主动初始化音频上下文这一事实。
一个可复现的本地观察实验
下面的页面仅用于本地调试和隐私研究。它在用户点击按钮后创建一个低音量振荡器,通过分析器读取短时间内的音频数据,并输出简单统计值。示例不会上传数据,也不应被直接改造成跨网站跟踪代码。
将内容保存为 audio-observe.html,然后在浏览器中打开:
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Local Web Audio Observation</title>
</head>
<body>
<button id="start">开始本地观察</button>
<pre id="log"></pre>
<script>
const button = document.querySelector('#start');
const log = document.querySelector('#log');
button.addEventListener('click', async () => {
const AudioContext = window.AudioContext || window.webkitAudioContext;
if (!AudioContext) {
log.textContent = '当前浏览器不支持 Web Audio API';
return;
}
const context = new AudioContext();
await context.resume();
const oscillator = context.createOscillator();
const gain = context.createGain();
const analyser = context.createAnalyser();
oscillator.type = 'sine';
oscillator.frequency.value = 440;
gain.gain.value = 0.0001; // 极低音量,仅用于本地观察
analyser.fftSize = 2048;
oscillator.connect(gain);
gain.connect(analyser);
// 不连接 destination,避免向扬声器输出
oscillator.start();
await new Promise(resolve => setTimeout(resolve, 300));
const samples = new Float32Array(analyser.fftSize);
analyser.getFloatTimeDomainData(samples);
let sum = 0;
let peak = 0;
for (const sample of samples) {
sum += sample * sample;
peak = Math.max(peak, Math.abs(sample));
}
const rms = Math.sqrt(sum / samples.length);
log.textContent = JSON.stringify({
sampleRate: context.sampleRate,
state: context.state,
baseLatency: context.baseLatency,
rms,
peak
}, null, 2);
oscillator.stop();
await context.close();
});
</script>
</body>
</html>
这个实验只能说明浏览器提供了可观察的音频上下文参数,不能证明某个结果足以唯一识别设备。不同浏览器、系统版本和音频设备之间重复测试,才可能判断哪些特征稳定、哪些特征只是瞬时噪声。真实的指纹系统还可能使用离线音频渲染、多个节点组合或更长时间的统计分析。
浏览器和网站开发者应该怎么做
隐私浏览器已经开始针对音频指纹采取缓解措施,例如限制音频上下文可获得的信息、延迟初始化、降低精度、在权限或用户操作后才允许创建上下文,或者让不同会话得到不稳定的结果。这些措施也可能影响音频编辑器、在线乐器、视频会议和游戏,因此需要在隐私与功能之间做权衡。
网站开发者则应遵循更明确的边界:
- 只有在确实需要音频功能时才创建
AudioContext; - 将初始化放在清晰的用户操作之后,并向用户说明用途;
- 不要把静默音频当作隐藏式设备识别机制;
- 不要把音频统计值与账号、IP、广告标识符无提示地关联;
- 对浏览器拒绝、权限变化和蓝牙设备切换做好降级处理。
对排查 BLE 问题的工程团队,可以在测试记录中加入浏览器名称、sampleRate、输出设备变化、AudioContext.state 和 BLE 断开时间线,但应把这些数据限定在本地诊断用途。
结语:音频上下文不应成为隐形传感器
这次发现暴露的是一个标准和隐私模型之间的缝隙:Web Audio API 的设计目标是让网页处理声音,但音频处理链路的差异也可能泄露设备信息。网页不需要播放一段用户能听到的音乐,就可能获得可用于区分设备的信号。
采用这类能力前,团队可以用三条检查清单约束风险:是否真的需要音频上下文、是否经过明确的用户触发、是否会把结果用于识别或跨会话关联。对于普通用户,选择具备音频指纹防护的隐私浏览器,并在调试蓝牙或音频设备时关注网页是否主动创建音频上下文,是一个实际而有效的起点。