How to Generate Reliable Browser Fingerprints with Navigator, Canvas, and WebGL
This article explains what browser fingerprinting is, outlines common techniques such as Navigator, Canvas, and WebGL fingerprints, and provides complete JavaScript examples that collect device information and generate stable hashes, helping developers understand and implement device‑unique identifiers for identity verification.
What is Browser Fingerprinting?
Browser fingerprinting is a set of feature values that uniquely identify a browser. It collects information such as browser, operating system, screen resolution, fonts, plugins, etc., and combines them into a unique ID. Unlike cookies, it does not store anything on the device.
Use Case
In a recent project I needed a unique credential from the user's device to verify identity. Directly accessing hardware identifiers like IMEI or serial numbers is impossible from JavaScript, so I turned to browser fingerprinting.
Feasible Solutions
Navigator fingerprint : browser type, version, platform, and other information.
Canvas fingerprint : render a hidden image and read pixel differences.
WebGL fingerprint : use graphics driver differences.
Font, plugin, timezone, screen resolution, etc.
Combining multiple signals improves uniqueness.
Navigator Fingerprint
Navigator is the primary interface for obtaining browser and device environment information.
Common properties and methods (with good cross‑browser support):
navigator.userAgent– Returns the user‑agent string, useful for detecting browser and OS.
navigator.platform– Returns the operating system platform (e.g., Win32, Linux x86_64, MacIntel).
navigator.appVersion– Returns browser version and some platform info.
navigator.appName– Returns the browser name (most modern browsers return “Netscape”).
navigator.language– Returns the preferred language (e.g., “en-US”).
navigator.languages– Returns an array of preferred languages.
navigator.hardwareConcurrency– Returns the number of logical processors.
navigator.plugins– Returns the list of installed plugins (desktop browsers only).
navigator.onLine– Indicates whether the browser is online.
navigator.cookieEnabled– Indicates whether cookies are enabled.
navigator.geolocation– Provides geolocation services (requires user permission).
navigator.maxTouchPoints– Maximum number of touch points supported.
navigator.mediaDevices– Access to audio/video device APIs.
navigator.clipboard– Read/write system clipboard (requires HTTPS and permission).
navigator.connection– Network connection information (bandwidth, type, etc.).
navigator.userAgentData– Newer user‑agent information object with better privacy.
Below is a concise example that gathers these Navigator properties, hashes them with SHA‑256, and displays the result.
<code><!DOCTYPE html>
<html>
<head>
<title>Navigator Fingerprint Example</title>
</head>
<body>
<h2>Navigator Fingerprint Example</h2>
<pre id="output"></pre>
<script>
async function getNavigatorFingerprint() {
const data = {
userAgent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
languages: navigator.languages,
cookieEnabled: navigator.cookieEnabled,
hardwareConcurrency: navigator.hardwareConcurrency || 'N/A',
deviceMemory: navigator.deviceMemory || 'N/A',
webdriver: navigator.webdriver || false,
};
const dataString = JSON.stringify(data);
const hashBuffer = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(dataString)
);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return { data, fingerprint: hashHex };
}
getNavigatorFingerprint().then(result => {
const output = document.getElementById('output');
output.textContent =
"Collected Navigator data:\n" + JSON.stringify(result.data, null, 2) +
"\n\nGenerated fingerprint (SHA-256):\n" + result.fingerprint;
});
</script>
</body>
</html>
</code>Canvas Fingerprint
Different devices render the same Canvas content with subtle variations due to OS, GPU, drivers, and font rendering, producing distinct image data that can be hashed into a fingerprint. This method is generally more stable than Navigator alone.
<code><!DOCTYPE html>
<html>
<head>
<title>Simple Canvas Fingerprint</title>
</head>
<body>
<h2>Simple Canvas Fingerprint</h2>
<p>Open the console (F12) to see the result</p>
<script>
function generateCanvasFingerprint() {
const canvas = document.createElement('canvas');
canvas.width = 200;
canvas.height = 100;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'red';
ctx.fillRect(20, 20, 50, 50);
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(120, 45, 25, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'black';
ctx.font = '16px Arial';
ctx.fillText('Canvas fingerprint', 60, 80);
const dataURL = canvas.toDataURL();
function simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return hash.toString(16);
}
const fingerprint = simpleHash(dataURL);
return { fingerprint, dataURL };
}
const result = generateCanvasFingerprint();
console.log('Canvas fingerprint:', result.fingerprint);
console.log('Canvas dataURL first 100 chars:', result.dataURL.substring(0, 100) + '...');
if (window.crypto && window.crypto.subtle) {
const encoder = new TextEncoder();
const data = encoder.encode(result.dataURL);
window.crypto.subtle.digest('SHA-256', data)
.then(hashBuffer => {
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
console.log('Canvas fingerprint (SHA-256):', hashHex);
});
}
</script>
</body>
</html>
</code>Other fingerprinting methods follow similar principles and are omitted for brevity.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.