# How the OfflineAudioContext fingerprint is computed

> An oscillator through a compressor, rendered silently and summed: the OfflineAudioContext fingerprint reads your machine's floating-point audio path.

Source: https://mosaicbrowser.com/blog/offlineaudiocontext-explained

## How the OfflineAudioContext fingerprint is computed

An oscillator through a compressor, rendered silently and summed: the OfflineAudioContext fingerprint reads your machine's floating-point audio path.

![How the OfflineAudioContext fingerprint is computed](https://mosaicbrowser.com/covers/offlineaudiocontext-explained.webp)

The OfflineAudioContext fingerprint is a number your browser computes without making a sound. A script wires a 10 kHz triangle wave into a dynamics compressor, renders about a tenth of a second of it into memory, and adds up 500 of the samples. What comes out depends on how this build of the browser does floating-point maths on this CPU and operating system. Run it twice on one laptop and you get the same digits; run it on a different kind of machine and the last few change.

## The OfflineAudioContext fingerprint, step by step

The version most pages run comes from the open-source FingerprintJS library, which credits an earlier project, cozylife's audio-fingerprint. Stripped down, the classic form is about a dozen lines:

```
const ctx = new OfflineAudioContext(1, 5000, 44100)
const osc = ctx.createOscillator()
osc.type = 'triangle'
osc.frequency.value = 10000
const comp = ctx.createDynamicsCompressor()
comp.threshold.value = -50
comp.knee.value = 40
comp.ratio.value = 12
comp.attack.value = 0
comp.release.value = 0.25
osc.connect(comp); comp.connect(ctx.destination)
osc.start(0)
const data = (await ctx.startRendering()).getChannelData(0)
let sum = 0
for (let i = 4500; i < 5000; i++) sum += Math.abs(data[i])

```

5,000 frames at 44,100 Hz is about 113 milliseconds, on one channel, and none of it goes to the speaker. The sum at the end is a float with a long tail of digits. Two Chromes on two identical laptops print the same one. Move to another chip family or operating system and the last few digits move with it. Newer FingerprintJS releases changed the graph; the principle did not change.

Why offline? An `OfflineAudioContext` renders into an `AudioBuffer` as fast as the CPU can go and never opens an audio device. The [Web Audio API specification](https://www.w3.org/TR/webaudio/) defines it for producing audio for later rather than for now. Nothing asks permission and nothing plays, and the render is over in a few milliseconds.

Mosaic is a desktop app

Email yourself the link and open it on your computer.

[How it works: Machines that exist](https://mosaicbrowser.com/features/real-fingerprints)

## Why the compressor is there

Take the compressor out and the trick mostly stops working: a triangle wave is simple arithmetic, and Chrome on a Mac and Chrome on a Windows PC compute it to nearly the same bits. With the compressor in, every sample goes through a gain curve, a log, an exponent and an envelope that smooths the gain over time. Those are computed by whatever maths library and vector instructions this build was compiled with. The spec describes the curve. It does not dictate how a build for ARM and a build for x86 round the last bit of a 32-bit float, and it does not try.

The errors are tiny, but the envelope carries each one into the next sample. By sample 4,500 they have had a tenth of a second to pile up, so the script reads the tail of the buffer and skips the start.

## Where it was first measured in the wild

Steven Englehardt and Arvind Narayanan's 2016 paper, "Online Tracking: A 1-million-site Measurement and Analysis", crawled the top million sites and found scripts reading audio this way. They saw two setups. One fed an oscillator into an `AnalyserNode` and hashed the frequency data. The other is the one above in outline: an oscillator through a compressor inside an `OfflineAudioContext`, the output summed and hashed.

They also built a test page and collected the values. Across 18,500 devices they saw 713 distinct fingerprints, about 5.4 bits of entropy. That is far from unique. It means the audio sum puts you in one of several hundred buckets, and the bucket says roughly what kind of machine and browser you are.

## Why sites read it so widely

It takes a few milliseconds, no prompt appears, and the value survives a cookie wipe, a private window and a new tab. Five bits is not much on its own and a site does not need more from it. What it wants is a value that stays put between visits and agrees with everything else the browser has said.

The second part is where it bites. Say a browser's WebGL renderer string names an Apple chip, and its audio sum is the one an x86 Windows build produces. Those two cannot both be true, and a site only has to collect the reference sum for each platform once to notice. The same logic makes a canvas hash useful, which is why [what a canvas hash reveals](https://mosaicbrowser.com/blog/what-a-canvas-hash-reveals) reads so much like this page. The full list of values a page can read beside it is in our [browser fingerprint glossary entry](https://mosaicbrowser.com/glossary/browser-fingerprint).

## What a defence has to get right

Say you want two profiles on one Mac to have different audio sums. A script will render the graph twice in a row, and again after a restart, and it expects the same samples every time. Meanwhile profile two must come out different from profile one, and only in the last digits, where real hardware differs, never by an order of magnitude.

Most blockers fail the first test. They add random noise on every read, so a script that renders twice and compares sees two answers from one machine, which no real device gives. Patching `AudioBuffer.prototype.getChannelData` in JavaScript fails it too, by another route: a worker or a fresh iframe reads the buffer through a copy of the function the patch never touched, and the page can also check whether a native function has been swapped out. We wrote up that whole class of failure in [why we change fingerprints in C++, not JavaScript](https://mosaicbrowser.com/blog/cpp-not-javascript).

## What Mosaic does with it

Tesseract Chrome, Mosaic's engine, makes the change inside Blink's own `offline_audio_context.cc`, in C++. When a profile creates an offline context, the engine shifts the rate the render runs at by less than a hundredth of a hertz, by an amount computed from that profile's seed. Every page, frame and worker in the profile sees the same shift, the same graph gives the same sum on every read and every launch, and a second profile on the same computer gives a different one. Our comparison script, run against Google's unpatched Chrome for Testing of the same major on the same Mac, printed `canvas stable, audio stable` on its noise line.

The seed also survives a hardware repair. When Mosaic redraws a profile's machine to fix a mismatched GPU or screen, the canvas and audio identities are kept, because those are what a site already recognises the profile by. How the rest of a profile's hardware is drawn from real machines is on the [real fingerprints](https://mosaicbrowser.com/features/real-fingerprints) page.

## Questions

### Does an audio fingerprint play sound or need the microphone?

No. An OfflineAudioContext renders into a buffer in memory and never reaches the speakers. It asks for no permission, so the page reads the result without you hearing or seeing anything.

### Is the audio fingerprint unique to my computer?

Not on its own. Englehardt and Narayanan's 2016 test page saw 713 different values across 18,500 devices, about 5.4 bits. It sorts you into a group of similar machines, and a site combines it with other values.

### Does clearing cookies change the audio fingerprint?

No. The page stores nothing to clear. It runs the render again on each visit and gets the same sum, because the browser build and the chip under it have not changed.

### Why not just add random noise to the audio buffer?

A script can render the graph twice and compare. Your laptop gives the same samples both times; a blocker that adds fresh noise per read gives two, and that is easier to spot than the sum it was hiding.

## Related

[Browser fingerprint: what a site can read about your machine](https://mosaicbrowser.com/glossary/browser-fingerprint): A browser fingerprint is the set of values a site reads about your browser and computer, which together recognize you without cookies. What goes into one.

[What a canvas hash actually tells a site about your machine](https://mosaicbrowser.com/blog/what-a-canvas-hash-reveals): A canvas fingerprint reveals your rendering stack: GPU, driver, fonts, the OS's text rasteriser and antialiasing. It names a kind of machine, not a person.

[Why we change fingerprints in C++, not JavaScript](https://mosaicbrowser.com/blog/cpp-not-javascript): A JavaScript spoof is one realm deep. An iframe read the real source of 33 of our patched functions; the same values changed in C++ agree everywhere.

[Machines that exist: fingerprints drawn from real hardware](https://mosaicbrowser.com/features/real-fingerprints): Mosaic draws each profile's GPU, screen, CPU, memory and fonts from one real device, so the parts of a fingerprint agree with each other and with the host.

[Why every Mac's Chrome says Mac OS X 10_15_7](https://mosaicbrowser.com/blog/mac-os-x-10-15-7): Chrome froze the macOS version in its User-Agent, so every Mac claims 10.15.7. The real one is in Sec-CH-UA-Platform-Version, a hint sites must ask for.

[Why the User-Agent says Windows 10 on Windows 11](https://mosaicbrowser.com/blog/windows-11-user-agent-windows-10): Windows 11 and 10 both send Windows NT 10.0 in the User-Agent. Only the platformVersion client hint, 13 or higher, says Windows 11.

[Why Chrome's User-Agent says 0.0.0](https://mosaicbrowser.com/blog/chrome-user-agent-0-0-0): Chrome zeroes everything after the major, so the User-Agent says 0.0.0 and the build moves to Sec-CH-UA-Full-Version-List. A real build in the UA is a tell.

[Privacy Sandbox is retired, and its APIs are a version tell](https://mosaicbrowser.com/blog/privacy-sandbox-apis-removed): Chrome is removing Topics and Protected Audience release by release, so whether runAdAuction exists tells a page which Chrome you really are.

## Mosaic, on your own computer

The browser this blog is about: its own Chromium, fingerprints from real machines and a proxy exit per profile. Free holds 3 profiles. Pro is $49 a month for any number.

Mosaic is a desktop app

Email yourself the link and open it on your computer.
