Audio Fingerprinting: The Silent Browser Tracker

## TL;DR Audio fingerprinting uses the Web Audio API to make your browser process a silent waveform, then hashes the result. The output varies by CPU, OS audio stack, and browser build — enough to act as a stable identifier even after you clear cookies, switch IPs, or open a private window. It is invisible, runs in iframes, and needs no microphone permission. Tor Browser, Mullvad Browser, Brave, and LibreWolf all defend by default; Chrome and Safari do not. ## What audio fingerprinting actually is Audio fingerprinting is a member of the same family as [canvas fingerprinting](/blog/canvas-fingerprinting-explained) and [WebGL fingerprinting](/blog/webgl-fingerprinting-explained). The common idea: hand the browser a deterministic computation, observe tiny deviations in the result, and use those deviations as a quasi-stable identifier. What makes the audio version notable is that nothing is played. The technique abuses the `OfflineAudioContext` API — a Web Audio interface designed for non-real-time rendering, originally intended for things like generating waveform previews or doing offline mixing. The browser computes a buffer of samples in memory, the script reads the samples back, hashes them, and ships the hash to a tracker. No speakers. No microphone. No prompts. The deviations come from three layers: - The browser's audio DSP implementation (Chromium, Gecko, and WebKit each ship slightly different code). - The math library and SIMD path the CPU happens to use for floating point. - The host OS audio stack and its sample rate / buffer assumptions. Two visitors on identical hardware and OS can produce identical hashes. But once you mix the audio hash with canvas, WebGL, fonts, screen, and timezone, the combined entropy is enough to reach the ~99% uniqueness numbers Panopticlick and AmIUnique have been publishing for over a decade. The audio bit alone contributes roughly 5 to 7 bits of entropy in the wild, depending on the dataset. ## How the attack works (the actual code) The canonical version of the attack uses the `DynamicsCompressorNode`. Here is the technique trackers use, stripped to its essence: ```javascript async function audioFingerprint() { const ctx = new OfflineAudioContext(1, 44100, 44100); const osc = ctx.createOscillator(); osc.type = 'triangle'; osc.frequency.setValueAtTime(10000, ctx.currentTime); const compressor = ctx.createDynamicsCompressor(); compressor.threshold.setValueAtTime(-50, ctx.currentTime); compressor.knee.setValueAtTime(40, ctx.currentTime); compressor.ratio.setValueAtTime(12, ctx.currentTime); compressor.attack.setValueAtTime(0, ctx.currentTime); compressor.release.setValueAtTime(0.25, ctx.currentTime); osc.connect(compressor); compressor.connect(ctx.destination); osc.start(0); const rendered = await ctx.startRendering(); const samples = rendered.getChannelData(0); let sum = 0; for (let i = 4500; i < 5000; i++) { sum += Math.abs(samples[i]); } return sum.toString(); } ``` Why this specific recipe? The `DynamicsCompressorNode` is the most variable node in the Web Audio graph because it runs a stateful, non-trivial DSP algorithm with floating-point math. Sum a narrow window of the rendered buffer and the resulting number differs between, say, Chrome on Intel macOS and Chrome on Apple Silicon, between Firefox on glibc Linux and Firefox on musl Linux, and between any two browser engines. A handful of seconds is enough — the whole thing renders offline at faster than realtime and never touches your speakers. Production trackers do not stop at one node. They run several oscillator-and-filter graphs in parallel and concatenate the hashes. They sometimes also probe the realtime `AudioContext` for its `baseLatency`, `outputLatency`, and `sampleRate` properties, which leak the host audio stack configuration. ## Why it is so effective Audio fingerprinting hits a sweet spot that most other vectors miss: - **Silent.** The user notices nothing. There is no audible playback because the compute path is `OfflineAudioContext`, and the destination buffer is never wired to a real output. - **No permissions.** Unlike `getUserMedia`, the Web Audio API requires no prompt and no user gesture in the OfflineAudio path. It works inside an `