Canvas fingerprinting asks your browser to draw an image and then reads back the pixels. The same drawing comes out slightly differently on different systems, so a hash of the image works as an identifier. Mowery and Shacham described the technique in 2012 in Pixel Perfect: Fingerprinting Canvas in HTML5, and found that the operating system, browser version, graphics card, installed fonts, subpixel hinting and anti-aliasing all affect the result. This canvas fingerprint test shows your hash and, more usefully, checks whether the canvas behaves like an unmodified browser.
How the test works
The page draws a 280 by 60 pixel image in a hidden canvas: a filled rectangle, a line of text in 16px Arial with an emoji, and a semi-transparent arc. It reads the image with toDataURL(), which returns it as PNG data, and hashes that with SHA-256. Then it runs three more checks: it draws the same image a second time, measures a line of text, and paints a plain red square to see whether it comes back plain. Nothing leaves your browser for this test.
The text and the emoji are the parts that vary most between systems. Fonts are rendered by the operating system's text engine, Arial may be replaced by a similar font where it isn't installed, and every system has its own emoji set.
Reading your results
Canvas hash
The first 16 characters of the SHA-256 hash of the test image. On its own the hash means little: it is a label for "the way this browser on this device draws this image". Many devices with the same operating system, browser version and graphics hardware share a hash. What matters is whether it is stable and whether it fits the rest of your browser.
Same image twice
The page draws the identical image twice and compares the results. An unmodified browser returns identical images every time. If they differ, something is adding random noise on every read. That kind of noise defeats a simple hash, but it is easy to detect, as this row shows, and it means the browser looks different every time a site asks.
Text measuring
The page measures the width of a test string in 18px Arial with measureText(). A real browser returns a width of well over 50 pixels for this string. A width near zero means text measuring is broken. Some canvas masking implementations break measureText(), and pages notice in two ways: text in charts, editors and games is laid out wrong, and fingerprinting scripts that measure text get impossible values. Text measuring is also how scripts detect installed fonts, so it is a commonly read value. This row is red when the width is near zero.
Flat red square
The page paints a 16 by 16 square in pure red and counts how many of its 256 pixels come back as anything other than pure red. On an unmodified browser the answer is none. Changed pixels are a sign of canvas noise. A flat color is the easiest place to see noise, because there are no edges or anti-aliasing where small differences would be normal.
WebGL renderer
The graphics driver's renderer string, read through the WEBGL_debug_renderer_info extension. It is shown for context. The renderer should fit the operating system your browser reports: an Apple GPU name next to a Windows user agent is a contradiction. Some browsers hide this value; Firefox disables the extension when its privacy.resistFingerprinting setting is on.
Noise versus real values
There are three common ways browsers and tools deal with canvas fingerprinting:
| Approach | What sites see | Trade-off |
|---|---|---|
| Real values | The image your system actually draws | Stable and consistent, and the same on every site |
| Random noise on every read | A different image each time | Easy to detect by drawing twice |
| Stable noise from a seed | A slightly different image that stays the same for one profile | Distinct per profile; depends on how well the noise is applied |
Brave, for example, adds small randomization to canvas, WebGL and audio so that each site and session sees a different fingerprint (Brave, 2020). Antidetect browsers usually use stable noise from a per-profile seed, so a profile keeps the same canvas result across visits.
Noise can be detected even when it is stable. The FP-Scanner study (USENIX Security 2018) detected canvas tampering by drawing an image and checking for:
- background pixels that should be transparent but aren't,
- isolated stray pixels where shapes are closed,
- colors that are almost, but not exactly, the color the script drew,
- canvas functions such as
toDataURLthat had been replaced by script.
The last point is why noise added inside the browser engine is harder to spot than noise added by an extension that overrides JavaScript functions. An extension's replacement function can often be told apart from the native one.
Noise also has to be consistent with everything else. A canvas image that looks like macOS font rendering next to a Windows user agent is a contradiction, however good the noise is.
What to do about problems
- Same image twice is yellow. A privacy extension or browser setting adds per-read noise. Turn it off if you want a stable fingerprint, or accept that the browser is recognizable as randomized.
- Text measuring is red. Canvas masking has broken
measureText(). Turn off canvas masking or the extension responsible. Pages that lay out text on a canvas will look wrong until you do. - Flat red square is yellow. Canvas noise is active. Whether that matters depends on what you need: it hides the real hash but can be detected.
- WebGL renderer doesn't fit the operating system. Mask the renderer to a value that matches the reported system, or report the real system.
Canvas in Multime
Multime profiles can mask canvas, WebGL images, audio and element sizes (ClientRects) with noise derived from the profile's seed. The masked values stay the same each time the profile opens, two profiles never share them, and you can choose a new fingerprint on every start instead. Graphics card info can be masked or real.
One limit matters for this test. On the Windows engine (Chrome 150), canvas masking is switched off automatically, because that engine build breaks text measuring when it is on, which is the failure the text measuring row detects. On Windows, a profile's canvas therefore shows your real values. The Mac engine (Chrome 152) doesn't have this limit. Read more about Multime on Windows.
Open this page inside a profile twice, on different days, to confirm that the hash is stable and text measuring works.