Why hashing browser fingerprints fails
Exact-hash browser fingerprinting mints a new visitor ID on every update. Stability-weighted fuzzy matching is how you survive Chrome's four-week release cycle.
Hashing browser fingerprints fails because a cryptographic hash changes entirely when any one input changes, and device signals change all the time. Every Chrome release reshapes the canvas and WebGL output, so a concatenate-and-hash fingerprint mints a brand-new ID for a large share of your users every few weeks. The naive design is one line:
const fingerprint = sha256(canvas + webgl + fonts + timezone + screen + ua);
It works in the demo and fails in production, on a schedule you can predict from the Chrome release calendar.
The problem: one bit flips, the ID changes
A cryptographic hash is intentionally brittle: change one input bit and every output bit is fair game. That is the opposite of what device identity needs. Consider what actually happens to a device over eight weeks:
- Chrome ships a new major version roughly every 4 weeks. Canvas and WebGL rendering are not stable across versions. Font rasterization tweaks, ANGLE updates, and GPU driver changes all shift the pixel output, so the canvas hash changes for a large share of your users at once.
- The user agent string changes with every release, by definition.
- A window resize changes reported screen metrics on some platforms.
Hash all of that together and every browser update mints a brand-new ID. Your "returning visitor" metric quietly becomes a browser-adoption chart, and a fraud ring gets a free identity reset every four weeks just by staying on the update train.
Signals are not equally trustworthy
The fix starts with an observation: signals drift at wildly different rates.
signal typical stability weight
timezone years x5
cpu cores / ram hardware lifetime x5
fonts months x3
canvas hash ~1 browser release x1
webgl hash ~1 browser release x1
Timezone and hardware describe the physical machine; canvas and WebGL describe this week's rendering pipeline. Treating them as interchangeable bytes in a hash throws that structure away.
The fix: weighted similarity, not equality
Veriprint keeps signals as a vector and compares vectors, weighted by stability:
// score in 0..1: this is the confidence we return
const score = sum(w[i] * match(a[i], b[i])) / sum(w[i]);
if (score >= 0.82) relink(existingVisitorId);
When Chrome 138 lands and the canvas hash flips, the low-weight mismatch costs a few points of similarity while timezone, hardware, fonts, and screen still agree. The score stays above the 0.82 re-link threshold, the visitor keeps their ID, and the stored profile is updated with an EMA so the new canvas value becomes the expected one. The device drifts; the ID follows.
Two consequences worth stating plainly. First, this is probabilistic: you get a confidence score per request, not a boolean, and you should branch on it. Second, it degrades gracefully: identification is strong over weeks and good over months, rather than perfect until an update and worthless after.
Exact hashing answers "is this byte-for-byte the same browser build?" Fuzzy matching answers the question you actually asked: "is this the same device?"