DEV Community

FireKey Team
FireKey Team

Posted on

How Browser Fingerprinting Actually Works: Canvas, WebGL, and AudioContext Explained

Browser fingerprinting is one of those technologies that's everywhere but rarely explained clearly. Let me break down the actual mechanics — because if you're building anything browser-related (extensions, automation, multi-account tools), you need to understand this.

The Core Problem With Fingerprinting

Most developers think fingerprinting = cookies. Wrong. Cookies are trivially cleared. Browser fingerprinting creates a persistent device identifier from hardware and software characteristics that you can't easily change.

Let me walk through the three big ones:

Canvas Fingerprinting

The HTML5 Canvas API lets you programmatically draw graphics. Here's what fingerprinting sites do:

function getCanvasFingerprint() {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');

  // Draw text with specific font settings
  ctx.textBaseline = 'top';
  ctx.font = '14px Arial';
  ctx.fillStyle = '#f60';
  ctx.fillRect(125, 1, 62, 20);
  ctx.fillStyle = '#069';
  ctx.fillText('BrowserFingerprint!', 2, 15);
  ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
  ctx.fillText('BrowserFingerprint!', 4, 17);

  return canvas.toDataURL();
}
Enter fullscreen mode Exit fullscreen mode

The output looks identical on screen, but the underlying pixel data varies microscopically between devices. Why? Because the rendering depends on:

  • GPU vendor and model
  • GPU driver version
  • OS-level font rendering (cleartype settings, antialiasing)
  • Subpixel rendering configuration
  • Hardware acceleration settings

The resulting base64 string gets hashed. Same GPU + same driver = same hash. This is essentially your GPU's fingerprint.

WebGL Fingerprinting

WebGL exposes even more about your hardware:

function getWebGLFingerprint() {
  const canvas = document.createElement('canvas');
  const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');

  if (!gl) return null;

  const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');

  return {
    vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL),     // e.g., "Intel Inc."
    renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL), // e.g., "Intel Iris Plus Graphics"
    version: gl.getParameter(gl.VERSION),
    shadingLanguageVersion: gl.getParameter(gl.SHADING_LANGUAGE_VERSION),
    maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
    // ... 30+ more parameters
  };
}
Enter fullscreen mode Exit fullscreen mode

UNMASKED_RENDERER_WEBGL often returns something like "ANGLE (Intel, Intel(R) UHD Graphics 620 Direct3D11 vs_5_0 ps_5_0)" — which is extremely specific to your hardware/driver combination.

Chrome has been restricting access to these values, but the combination of supported extensions, precision values, and rendering behavior still creates a unique signature.

AudioContext Fingerprinting

This one surprises most developers:

function getAudioFingerprint() {
  const audioCtx = new (window.AudioContext || window.webkitAudioContext)({sampleRate: 44100});
  const oscillator = audioCtx.createOscillator();
  const analyser = audioCtx.createAnalyser();
  const gainNode = audioCtx.createGain();
  const scriptProcessor = audioCtx.createScriptProcessor(4096, 1, 1);

  gainNode.gain.value = 0; // Silent!
  oscillator.type = 'triangle';
  oscillator.connect(analyser);
  analyser.connect(scriptProcessor);
  scriptProcessor.connect(gainNode);
  gainNode.connect(audioCtx.destination);

  oscillator.start(0);

  // Capture processing output
  scriptProcessor.onaudioprocess = function(bins) {
    const output = bins.outputBuffer.getChannelData(0);
    // These floating-point values are hardware-dependent
    // ...
  };
}
Enter fullscreen mode Exit fullscreen mode

The floating-point arithmetic for audio DSP operations varies between hardware implementations. You don't hear anything, but the values are hardware-unique.

Navigator + Screen Properties

Finally, there's the combination attack:

const fingerprint = {
  userAgent: navigator.userAgent,
  language: navigator.language,
  languages: navigator.languages,
  platform: navigator.platform,
  hardwareConcurrency: navigator.hardwareConcurrency,  // CPU cores
  deviceMemory: navigator.deviceMemory,                // RAM (approximate)
  maxTouchPoints: navigator.maxTouchPoints,
  screen: {
    width: screen.width,
    height: screen.height,
    colorDepth: screen.colorDepth,
    pixelDepth: screen.pixelDepth,
    availWidth: screen.availWidth,
  },
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
  fonts: /* enumerated via Canvas measurement */
};
Enter fullscreen mode Exit fullscreen mode

Each value alone is common. The combination is unique.

Why This Matters for E-Commerce Account Management

If you're building account management tools or working with multi-account setups for e-commerce platforms (Amazon, Shopee, TikTok Shop), fingerprinting is why accounts get linked even with separate IPs.

Every account you manage from the same browser profile has:

  • Identical Canvas hash
  • Identical WebGL renderer string
  • Identical AudioContext signature
  • Identical hardware concurrency + screen properties

The platform builds a graph. All five accounts connect to the same hardware fingerprint. IP addresses are almost irrelevant at this point.

How Spoofing Works (And Why It's Hard)

The naive approach is overriding navigator.userAgent. Platforms stopped caring about User-Agent years ago.

Proper fingerprint isolation requires:

  1. Canvas noise injection — intercept toDataURL() and getImageData() and add deterministic per-profile noise to the pixel output
  2. WebGL parameter spoofing — override getParameter() to return plausible (but unique) GPU vendor/renderer strings
  3. AudioContext noise — inject subtle noise into audio processing output
  4. Consistent spoofing — the same profile must return the same values every time (inconsistency is itself a signal)

This is exactly what anti-detect browsers do. FireKey (open beta, free) implements this at the Chromium level — each browser profile gets independently configured Canvas, WebGL, Audio, and Navigator parameters.

The key technical challenge: the spoofed values need to be coherent. A MacBook fingerprint with Windows fonts and a Linux timezone is an obvious fake. The configuration needs to simulate a realistic device with consistent locale signals.

Browser APIs Worth Knowing

If you're auditing fingerprinting exposure:

  • canvas.getContext('2d').getImageData() — Canvas fingerprinting
  • gl.getParameter(WEBGL_debug_renderer_info.UNMASKED_RENDERER_WEBGL) — WebGL
  • AudioContext.createOscillator() combined with script processing — Audio
  • navigator.getBattery() — Battery API (deprecated but still used)
  • navigator.connection — Network information
  • window.devicePixelRatio — Device pixel ratio
  • CSS.supports() — CSS feature detection

Understanding these is essential for building privacy-respecting browsers, automation tools, or any system that needs consistent browser identity management.


If you want to play with fingerprinting detection, check out BrowserLeaks and CreepJS — great tools for seeing exactly what your browser exposes.

Top comments (0)