Most people assume clearing cookies or using incognito mode makes them anonymous. It does not. Modern websites can still recognize you using browser fingerprinting.

This post explains how browser fingerprinting works and how I built Quark to understand it from the inside.

What is Browser Fingerprinting?

Browser fingerprinting identifies a browser by collecting information it already exposes. Nothing is stored on the device. Instead, multiple characteristics are combined into a stable identifier.

Unlike cookies, this identifier can persist across sessions and private windows.

What data is used?

Fingerprinting relies on many small signals that seem harmless on their own.

Basic signals include the user agent, operating system, language, timezone, screen resolution, and color depth.

More advanced signals come from JavaScript APIs. Websites can check which fonts are available, how the browser handles certain CSS rules, or how numbers are processed internally.

Canvas and WebGL fingerprinting are especially effective. The browser is asked to draw something off-screen. The result depends on the OS, GPU, drivers, font rendering, and browser implementation. The differences are subtle but consistent.

Audio fingerprinting works in a similar way. Small differences in how audio is processed can be measured and added to the fingerprint.

Individually, these values are weak. Together, they are often enough to uniquely identify a browser.

Basic fingerprint signals

The simplest signals come directly from browser APIs.

const fingerprintBase = {
  userAgent: navigator.userAgent,
  language: navigator.language,
  platform: navigator.platform,
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
};

On their own, these values are common. Combined with other signals, they become more useful.

Screen and display data

Screen characteristics add another layer of entropy.

const screenData = {
  width: screen.width,
  height: screen.height,
  colorDepth: screen.colorDepth,
  pixelRatio: window.devicePixelRatio
};

Different devices and display setups produce consistent but distinct results.

Canvas fingerprinting

Canvas fingerprinting relies on rendering differences between systems.

const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");

ctx.textBaseline = "top";
ctx.font = "16px Arial";
ctx.fillStyle = "#000";
ctx.fillText("quark fingerprint", 10, 10);

const canvasFingerprint = canvas.toDataURL();

The output looks the same visually, but the encoded image data differs across operating systems, GPUs, and font renderers.

Building Quark

I built Quark to explore this process end to end with minimal abstraction.

Repository: https://github.com/ni5arga/quark

The goal was clarity. Every signal collected should be readable and understandable.

Combining signals

Quark normalizes all collected values into a single string.

const rawFingerprint = [
  navigator.userAgent,
  navigator.language,
  screen.width,
  screen.height,
  canvasFingerprint
].join("|");

This string represents the browser’s fingerprint input.

Hashing the fingerprint

Instead of storing raw data, Quark hashes the combined string.

import SHA256 from "crypto-js/sha256";

const fingerprintId = SHA256(rawFingerprint).toString();

This produces a stable identifier that can be compared without exposing the original values.

Backend usage

The backend logic is intentionally simple.

if (incomingFingerprint === storedFingerprint) {
  // returning visitor
} else {
  // new browser
}

Fingerprinting is powerful. It can improve security and reduce abuse but it can also enable silent tracking.