This article focuses on WebGL fingerprinting. It explains how to modify the Chromium source code to randomize the list of extensions obtained via the WebGL API, thereby generating a unique WebGL fingerprint each time (with the goal of increasing the difficulty of user device tracking). It also introduces relevant online verification tools.
What is a WebGL Fingerprint?
WebGL: Stands for Web Graphics Library. It is a JavaScript API that enables high-performance 2D and 3D graphics rendering within web browsers without requiring additional plugins.
WebGL Fingerprint: Refers to a unique identifier generated based on the WebGL API in a web browser. It leverages subtle differences in the Graphics Processing Unit (GPU) across devices (such as variations in rendering capabilities and performance) to create a device fingerprint that can be used to track users, making it one of the methods for distinguishing between different user devices.
How is a Browser's WebGL Fingerprint Obtained?
Let's first see how a website typically obtains your WebGL fingerprint using JavaScript. You can simply copy the code below into your F12 console to retrieve and display your own WebGL fingerprint.
js
async function sha256(message) {
const msgBuffer = new TextEncoder().encode(message);
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return hashHex;
}
function getWebGLFingerprint() {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) {
return null;
}
const getGlParam = function(parameter) {
const value = gl.getParameter(parameter);
return value ? value.toString() : 'null';
};
const webglParams = [
gl.VENDOR,
gl.RENDERER,
gl.VERSION,
gl.getSupportedExtensions(),
gl.MAX_TEXTURE_SIZE,
gl.MAX_RENDERBUFFER_SIZE,
];
const glValues = webglParams.map(param => {
if (Array.isArray(param)) {
return param.join('-');
}
return getGlParam(param);
});
const webglFingerprint = glValues.join('_');
return webglFingerprint;
}
sha256(getWebGLFingerprint()).then(hash => console.log(hash));
Output Result:
language
dfe89f41416faecf0ab4be2ddeccdc79999aafd3577a0e14e9b3e5265e72d6e7
How to Compile for Random WebGL Fingerprint
1.Locate the source code file:
third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
2.Add these includes at the top of the file (if not already present):
C++
#include <algorithm> // std::shuffle
#include <random> // std::default_random_engine
#include <chrono> // std::chrono::system_clock
3.Find and replace the existing getSupportedExtensions() function code (or modify it accordingly) with the following:
C++
std::optional<Vector<String>>
WebGLRenderingContextBase::getSupportedExtensions() {
if (isContextLost())
return std::nullopt;
Vector<String> result;
for (ExtensionTracker* tracker : extensions_) {
if (ExtensionSupportedAndAllowed(tracker)) {
result.push_back(tracker->ExtensionName());
}
}
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
std::default_random_engine engine(seed);
std::shuffle(result.begin(), result.end(), engine);
return result;
}
As you can see, the core function for obtaining the WebGL fingerprint is getSupportedExtensions, which returns a list of all extension names supported by the current WebGL context object. By shuffling and randomly ordering this returned list, the hash value of the fingerprint information collected by JavaScript will naturally be different each time.