Canvas, Audio and WebGL: an in-depth analysis of fingerprinting technologies
10/28/25

Websites have long learned to recognize visitors not only by cookies. Even if you clear your browsing history, enable private mode and change IPs, your browser can still leak your digital fingerprint. This hidden identifier is built from many technical system characteristics. It follows you on your every visit.
A browser fingerprint is a combination of data about your device and software environment. Just as there are no two identical snowflakes, there are practically no two completely identical browsers. Single parameters (say, browser version or screen resolution) appear across thousands of users. But the combination of dozens of such details creates a distinct profile. What’s worse, the fingerprint is not stored on your device: it’s computed “on the fly” by the website. For that reason the fingerprint doesn’t disappear when you clear cookies or use private mode: each time a script will re-collect the same traits.
Alongside simple fingerprint components like user agent, timezone or browser language, there are three advanced techniques for obtaining it: Canvas, AudioContext, and WebGL. All three leverage the browser’s built-in graphics and audio capabilities to extract information about your hardware and software. But how exactly do they work? Why do they reveal nearly imperceptible differences between devices? Let’s break it down.
Websites have long learned to recognize visitors not only by cookies. Even if you clear your browsing history, enable private mode and change IPs, your browser can still leak your digital fingerprint. This hidden identifier is built from many technical system characteristics. It follows you on your every visit.
A browser fingerprint is a combination of data about your device and software environment. Just as there are no two identical snowflakes, there are practically no two completely identical browsers. Single parameters (say, browser version or screen resolution) appear across thousands of users. But the combination of dozens of such details creates a distinct profile. What’s worse, the fingerprint is not stored on your device: it’s computed “on the fly” by the website. For that reason the fingerprint doesn’t disappear when you clear cookies or use private mode: each time a script will re-collect the same traits.
Alongside simple fingerprint components like user agent, timezone or browser language, there are three advanced techniques for obtaining it: Canvas, AudioContext, and WebGL. All three leverage the browser’s built-in graphics and audio capabilities to extract information about your hardware and software. But how exactly do they work? Why do they reveal nearly imperceptible differences between devices? Let’s break it down.
Contents
Canvas fingerprinting
The Canvas API in the browser allows drawing graphics via JavaScript. This capability is covertly used to obtain a fingerprint. A script on the page creates an invisible <canvas> element and performs a series of drawing commands (for example, renders text, geometric shapes, adds shadow), then reads the resulting image as a pixel array. Each device produces tiny variations in the final pixel output due to differences in its hardware and software. A hash is then computed from that data and used as a unique user identifier.
The process can be described step-by-step like this:
Canvas creation. The visited site dynamically inserts a
<canvas>element into the page (often offscreen or hidden).Drawing. JS code renders test graphics into that canvas. Typically it draws a string of text in an uncommon font, adding colored shapes, lines, and effects (gradients, shadows).
Reading the pixels. After drawing, the script calls
toDataURL()(or a similar method) to get a binary representation of the result.Hashing. The obtained string (or pixel array) is passed through a hashing function. The output hash code is sent to the server and is used as the fingerprint.
For example, to simplify: let’s draw the word “Fingerprint” on a Canvas and compute a primitive hash:
let canvas = document.createElement("canvas"); let ctx = canvas.getContext("2d"); canvas.width = 200; canvas.height = 50; ctx.textBaseline = "top"; ctx.font = "20px Arial"; ctx.fillStyle = "#f60"; ctx.fillText("Fingerprint", 10, 10); let data = canvas.toDataURL(); let hash = 0; for (let i = 0; i < data.length; i++) { hash = (hash << 5) - hash + data.charCodeAt(i); hash |= 0; } console.log("Canvas hash:", hash);
This code will output a 32-bit number (it may be negative due to overflow) that depends on how the browser rendered the text. With another browser (in our example, Opera) the result may differ (though sometimes it coincides, which is rare). On another PC the results are even more likely to differ, although coincidences are possible in edge cases.
Screenshots below show that even on the same PC but in different browsers the hash differs: in the first case we rendered the word “Fingerprint” in Chrome, in the second in Opera. You can repeat this on your own device.


Why do they differ? Not because of the text “Fingerprint” itself: visually it looks the same to the human eye across devices. Differences appear at the rendering level: font hinting, anti-aliasing, rasterization algorithms, and subtle glyph adjustments for pixel grids are handled differently by different OSes and browsers. Add to this variations in GPUs and drivers, and each device introduces tiny distortions in the final image. On one pixel the letter edge is slightly lighter, on another it’s darker, somewhere the glyph was smoothed differently. These microscopic differences lead to different hashes even though the images look identical visually.
Sites try to amplify variability still. They may use specially crafted strings that include the full alphabet and diverse symbols to trigger many rendering code paths; for example, a phrase that includes almost all Latin letters: “Cwm fjordbank glyphs mute quiz.” They may also draw colored rectangles, gradients, or shadows on top of the text — everything to extract more unique pixel-level details. The end result of Canvas fingerprinting is the hash string mentioned earlier, but in a more complex form (e.g., e3d52382d0…). This hash consistently identifies that device and, on repeat visits, the same browser will yield the same hash unless its environment changes.
Thus, Canvas gives websites a powerful tracking tool. Without the user’s consent, the site collects a hardware-dependent “render” of the system. Devices with identical GPUs and software can yield matching Canvas fingerprints, but finding two such twins is extremely rare. Usually the combination of GPU specifics, fonts and software is unique enough for tracking.
AudioContext fingerprinting
The next method is audio fingerprinting, where the Web Audio API becomes a source of a unique “sound” identifier. It might sound odd: the site doesn’t request microphone access and doesn’t play audible audio. It’s more subtle. The script generates and processes an audio signal inside the browser and then extracts numeric metrics that indirectly reflect system characteristics. The result is a stable identifier analogous to the Canvas hash, but based on audio.
How it works in broad strokes:
AudioContext. The script creates a hidden audio context (usually an
OfflineAudioContext) — a virtual “sound card” that can process audio in memory without outputting it to speakers.Signal generation. An oscillator (
OscillatorNode) generates a fixed-frequency tone (e.g., 1,000 Hz for a triangle wave). Instead of loading an audio file the oscillator synthesizes the tone programmatically.Effects processing. To magnify hardware differences, the signal is routed through audio effects — often a compressor (
DynamicsCompressorNode) that “squeezes” the waveform. By configuring threshold, ratio, release time and other parameters, subtle waveform changes appear.Rendering and reading. The virtual audio context renders the specified audio chunk quickly in memory. After rendering the script obtains a buffer of sample values (an array of floating-point numbers). For example, at 44,100 Hz and ~113 ms duration you might get ~5,000 samples.
Fingerprint computation. The sample array is reduced to a compact number. One simple method is summing the absolute values of all samples and taking the most significant digits. That number becomes the audio fingerprint.
Below is a simple example of code you can run in the browser console to generate an audio fingerprint:
(async () => { const AC = window.OfflineAudioContext || window.webkitOfflineAudioContext; const ctx = new AC(1, 5000, 44100); const osc = ctx.createOscillator(); osc.type = 'triangle'; osc.frequency.value = 1000; const comp = ctx.createDynamicsCompressor(); comp.threshold.value = -50; comp.knee.value = 40; comp.ratio.value = 12; comp.attack.value = 0; comp.release.value = 0.25; osc.connect(comp); comp.connect(ctx.destination); osc.start(0); const rendered = await ctx.startRendering(); const samples = rendered.getChannelData(0); let acc = 0; for (let i = 0; i < samples.length; i++) acc += Math.abs(samples[i]); const demo = Math.round(acc * 1e6) / 1e6; const buf = samples.buffer.slice( samples.byteOffset, samples.byteOffset + samples.byteLength ); const hashBuf = await crypto.subtle.digest('SHA-256', buf); const hashHex = Array.from(new Uint8Array(hashBuf)) .map(b => b.toString(16).padStart(2, '0')) .join(''); console.log('Audio demo sum:', demo); console.log('Audio SHA-256 :', hashHex); })();
On the screenshots below you can see that the audio fingerprint of the same PC in different browsers does not differ (in our case it produced the number 953.152941). You can compare them and run the code on your own PC. The result will be identical, but with your own fingerprint, of course.


Thus, each device normally has its own number, an audio hash.
Browser developers long ago realized the danger of this method. Apple was among the first to add protection: starting with Safari 17, in Private mode the AudioContext API intentionally injects a small randomness into the generated sound. Thanks to this change, the same Safari can produce different audio hashes in different sessions. Most other browsers still allow audio fingerprinting without major obstacles (as the example above demonstrates). Combined with Canvas and WebGL data, audio fingerprinting significantly increases device recognizability.
WebGL fingerprinting
HTML5 WebGL is a graphics API for rendering 3D in the browser (via a <canvas> with a WebGL context). It has also become a tool for capturing device fingerprints. If Canvas fingerprinting reveals differences in 2D rendering, WebGL digs deeper, into the GPU itself. Identification possibilities here are even broader. From WebGL data you can almost directly learn the GPU model and driver, and small rendering details can even distinguish two devices with the same GPU.
A typical WebGL fingerprint scenario looks like this:
WebGL initialization. The script creates a WebGL context (for example,
canvas.getContext("webgl2")). At this step the script can already obtain some environment details: GPU name (vendor/renderer), driver version, supported extensions, etc.Scene rendering. Then a hidden canvas renders a 3D scene or special primitives. Typically a set of shapes with shader effects, lighting and textures is drawn — enough to exercise different parts of the graphics pipeline.
Parameter collection. After rendering the script reads the resulting image (via
gl.readPixels) and queries a set of WebGL parameters: supported extensions, maximum texture sizes, shader precision,RENDERER/VENDORstrings, etc. These data form a kind of “hardware snapshot” of the graphics system.Hash generation. The collected numbers and strings are combined and hashed (for example with the SHA-256 algorithm). The resulting hash becomes the WebGL fingerprint. It is then sent to the server and the website can use it for identification on current and future visits.
Here is an example of how such a hash is generated:
(async () => { const cv = document.createElement('canvas'); cv.width = 400; cv.height = 200; const gl = cv.getContext('webgl2', {antialias:true}) || cv.getContext('webgl', {antialias:true}); if (!gl) { console.log('WebGL недоступен'); return; } const info = {}; const dbg = gl.getExtension('WEBGL_debug_renderer_info'); if (dbg) { info.vendor = gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL); info.renderer = gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL); } else { info.vendor = '(masked)'; info.renderer = '(masked)'; } info.version = gl.getParameter(gl.VERSION); info.glsl = gl.getParameter(gl.SHADING_LANGUAGE_VERSION); info.maxTex = gl.getParameter(gl.MAX_TEXTURE_SIZE); const vs = ` attribute vec2 p; void main(){ gl_Position = vec4(p,0.0,1.0); } `; const fs = ` precision highp float; uniform vec2 u_res; float h(vec2 v){ float s = sin(dot(v, vec2(12.9898,78.233))) * 43758.5453; return fract(s); } void main(){ vec2 uv = gl_FragCoord.xy / u_res; float r = h(uv + vec2(0.11,0.21)); float g = h(uv*1.3 + vec2(0.31,0.41)); float b = h(uv*1.7 + vec2(0.51,0.61)); gl_FragColor = vec4(pow(vec3(r,g,b)*(0.6+0.4*uv.x), vec3(1.1)), 1.0); } `; function sh(type, src){ const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s); if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s)||'shader error'); return s; } const pr = gl.createProgram(); gl.attachShader(pr, sh(gl.VERTEX_SHADER, vs)); gl.attachShader(pr, sh(gl.FRAGMENT_SHADER, fs)); gl.linkProgram(pr); if (!gl.getProgramParameter(pr, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(pr)||'link error'); gl.useProgram(pr); const buf = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buf); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 3,-1, -1,3]), gl.STATIC_DRAW); const loc = gl.getAttribLocation(pr, 'p'); gl.enableVertexAttribArray(loc); gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0); const ures = gl.getUniformLocation(pr, 'u_res'); gl.uniform2f(ures, gl.drawingBufferWidth, gl.drawingBufferHeight); gl.viewport(0,0,gl.drawingBufferWidth, gl.drawingBufferHeight); gl.clearColor(0,0,0,1); gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLES, 0, 3); const w = gl.drawingBufferWidth, h = gl.drawingBufferHeight; const px = new Uint8Array(w*h*4); gl.readPixels(0,0,w,h, gl.RGBA, gl.UNSIGNED_BYTE, px); const enc = new TextEncoder(); const meta = enc.encode(JSON.stringify(info)); const full = new Uint8Array(meta.length + px.length); full.set(meta, 0); full.set(px, meta.length); const hashBuf = await crypto.subtle.digest('SHA-256', full.buffer); const hex = Array.from(new Uint8Array(hashBuf)) .map(b=>b.toString(16).padStart(2,'0')).join(''); let hi = 0>>>0, lo = 0>>>0; for (let i=0;i<px.length;i+=16){ const a = px[i] | (px[i+1]<<8) | (px[i+2]<<16) | (px[i+3]<<24); hi = ((hi ^ a) + 0x9e3779b9) >>> 0; lo = ((lo ^ ((a<<7)|(a>>>25))) + 0x85ebca6b) >>> 0; hi ^= (hi<<13)>>>0; lo ^= (lo<<15)>>>0; } const sample64 = ('00000000'+hi.toString(16)).slice(-8)+('00000000'+lo.toString(16)).slice(-8); console.log('WebGL vendor :', info.vendor); console.log('WebGL renderer:', info.renderer); console.log('WebGL version :', info.version, '| GLSL:', info.glsl); console.log('MAX_TEXTURE_SIZE:', info.maxTex); console.log('SHA-256(meta+pixels):', hex); console.log('Sample64:', sample64); })();
Here’s what we get in the end:

What differences does WebGL reveal? First, GPU identifiers. For example, integrated Intel Graphics and discrete NVIDIA GeForce or AMD Radeon have different capabilities, VRAM sizes and drivers, which is reflected in context parameters. Second, variations within the same model. Even supposedly identical GPU models have small individual differences in performance and calculation precision. WebGL can surface those too: measuring shader execution times or subtle pixel artifacts in rendered output will expose differences. Finally, the browser itself matters. Different rendering engines (Blink, WebKit, Gecko) execute WebGL calls differently and may produce slightly different render results. The screenshot below shows that when we generate the same fingerprint in Opera, differences appear.

WebGL fingerprint is often used together with Canvas and Audio for layered tracking, but even used alone it is informative enough and can tell a lot about the system.
How to protect yourself against fingerprinting
Completely eliminating browser fingerprinting is extremely difficult, as there are too many leakage channels. Nevertheless, a number of measures can reduce your uniqueness:
Special modes and browsers. Tor Browser enforces strict protection: all users share the same characteristic set, like clones. Canvas and WebGL are either disabled there or return averaged values. The downside is that many web services break. Brave in aggressive protection mode also blocks common tracking measures. Safari in Private mode adds noise to audio data to obscure the fingerprint.
Blocking and spoofing. There are extensions like CanvasBlocker that prevent scripts from reading Canvas or spoof the image with a random one. Plugins for AudioContext exist as well. However, relatively few users use these add-ons (around ~100k). A site that sees a completely blank Canvas or a wildly changing noise-hash will suspect something. Instead of hiding, you may stand out even more.
Environment unification. Another approach is to make the fingerprint non-unique but generic — e.g., run the browser inside a virtual machine or cloud service that gives all clients the same profile. Some anti-fraud systems do this: suspicious users are run inside an isolated “browser emulator” where their fingerprint is anonymized. But this is obviously inconvenient for everyday browsing.
Controlled spoofing. One of the best solutions is anti-detect browsers that allow fine-grained environment configuration. They let you decide what Canvas or WebGL the website sees. A perfect example is Octo Browser. It offers proactive spoofing: it adds noise to Canvas and audio, spoofs WebGL and other fingerprintable parameters, and produces configurations resembling real devices. Anti-detect browsers try to mimic a typical browser instead of randomly changing everything. A good anti-detect browser makes each profile look plausibly unique without standing out among millions of other users.
If you want to remain truly anonymous online, you’ll need different browsers and devices, keep your software constantly updated, disable unnecessary plugins, etc., but even then you only slightly improve your chances of blending in with the crowd. The better and more practical alternative is using professional tools like Octo Browser. An anti-detect browser can meaningfully and consistently spoof your fingerprint and help you preserve real privacy online.
Canvas fingerprinting
The Canvas API in the browser allows drawing graphics via JavaScript. This capability is covertly used to obtain a fingerprint. A script on the page creates an invisible <canvas> element and performs a series of drawing commands (for example, renders text, geometric shapes, adds shadow), then reads the resulting image as a pixel array. Each device produces tiny variations in the final pixel output due to differences in its hardware and software. A hash is then computed from that data and used as a unique user identifier.
The process can be described step-by-step like this:
Canvas creation. The visited site dynamically inserts a
<canvas>element into the page (often offscreen or hidden).Drawing. JS code renders test graphics into that canvas. Typically it draws a string of text in an uncommon font, adding colored shapes, lines, and effects (gradients, shadows).
Reading the pixels. After drawing, the script calls
toDataURL()(or a similar method) to get a binary representation of the result.Hashing. The obtained string (or pixel array) is passed through a hashing function. The output hash code is sent to the server and is used as the fingerprint.
For example, to simplify: let’s draw the word “Fingerprint” on a Canvas and compute a primitive hash:
let canvas = document.createElement("canvas"); let ctx = canvas.getContext("2d"); canvas.width = 200; canvas.height = 50; ctx.textBaseline = "top"; ctx.font = "20px Arial"; ctx.fillStyle = "#f60"; ctx.fillText("Fingerprint", 10, 10); let data = canvas.toDataURL(); let hash = 0; for (let i = 0; i < data.length; i++) { hash = (hash << 5) - hash + data.charCodeAt(i); hash |= 0; } console.log("Canvas hash:", hash);
This code will output a 32-bit number (it may be negative due to overflow) that depends on how the browser rendered the text. With another browser (in our example, Opera) the result may differ (though sometimes it coincides, which is rare). On another PC the results are even more likely to differ, although coincidences are possible in edge cases.
Screenshots below show that even on the same PC but in different browsers the hash differs: in the first case we rendered the word “Fingerprint” in Chrome, in the second in Opera. You can repeat this on your own device.


Why do they differ? Not because of the text “Fingerprint” itself: visually it looks the same to the human eye across devices. Differences appear at the rendering level: font hinting, anti-aliasing, rasterization algorithms, and subtle glyph adjustments for pixel grids are handled differently by different OSes and browsers. Add to this variations in GPUs and drivers, and each device introduces tiny distortions in the final image. On one pixel the letter edge is slightly lighter, on another it’s darker, somewhere the glyph was smoothed differently. These microscopic differences lead to different hashes even though the images look identical visually.
Sites try to amplify variability still. They may use specially crafted strings that include the full alphabet and diverse symbols to trigger many rendering code paths; for example, a phrase that includes almost all Latin letters: “Cwm fjordbank glyphs mute quiz.” They may also draw colored rectangles, gradients, or shadows on top of the text — everything to extract more unique pixel-level details. The end result of Canvas fingerprinting is the hash string mentioned earlier, but in a more complex form (e.g., e3d52382d0…). This hash consistently identifies that device and, on repeat visits, the same browser will yield the same hash unless its environment changes.
Thus, Canvas gives websites a powerful tracking tool. Without the user’s consent, the site collects a hardware-dependent “render” of the system. Devices with identical GPUs and software can yield matching Canvas fingerprints, but finding two such twins is extremely rare. Usually the combination of GPU specifics, fonts and software is unique enough for tracking.
AudioContext fingerprinting
The next method is audio fingerprinting, where the Web Audio API becomes a source of a unique “sound” identifier. It might sound odd: the site doesn’t request microphone access and doesn’t play audible audio. It’s more subtle. The script generates and processes an audio signal inside the browser and then extracts numeric metrics that indirectly reflect system characteristics. The result is a stable identifier analogous to the Canvas hash, but based on audio.
How it works in broad strokes:
AudioContext. The script creates a hidden audio context (usually an
OfflineAudioContext) — a virtual “sound card” that can process audio in memory without outputting it to speakers.Signal generation. An oscillator (
OscillatorNode) generates a fixed-frequency tone (e.g., 1,000 Hz for a triangle wave). Instead of loading an audio file the oscillator synthesizes the tone programmatically.Effects processing. To magnify hardware differences, the signal is routed through audio effects — often a compressor (
DynamicsCompressorNode) that “squeezes” the waveform. By configuring threshold, ratio, release time and other parameters, subtle waveform changes appear.Rendering and reading. The virtual audio context renders the specified audio chunk quickly in memory. After rendering the script obtains a buffer of sample values (an array of floating-point numbers). For example, at 44,100 Hz and ~113 ms duration you might get ~5,000 samples.
Fingerprint computation. The sample array is reduced to a compact number. One simple method is summing the absolute values of all samples and taking the most significant digits. That number becomes the audio fingerprint.
Below is a simple example of code you can run in the browser console to generate an audio fingerprint:
(async () => { const AC = window.OfflineAudioContext || window.webkitOfflineAudioContext; const ctx = new AC(1, 5000, 44100); const osc = ctx.createOscillator(); osc.type = 'triangle'; osc.frequency.value = 1000; const comp = ctx.createDynamicsCompressor(); comp.threshold.value = -50; comp.knee.value = 40; comp.ratio.value = 12; comp.attack.value = 0; comp.release.value = 0.25; osc.connect(comp); comp.connect(ctx.destination); osc.start(0); const rendered = await ctx.startRendering(); const samples = rendered.getChannelData(0); let acc = 0; for (let i = 0; i < samples.length; i++) acc += Math.abs(samples[i]); const demo = Math.round(acc * 1e6) / 1e6; const buf = samples.buffer.slice( samples.byteOffset, samples.byteOffset + samples.byteLength ); const hashBuf = await crypto.subtle.digest('SHA-256', buf); const hashHex = Array.from(new Uint8Array(hashBuf)) .map(b => b.toString(16).padStart(2, '0')) .join(''); console.log('Audio demo sum:', demo); console.log('Audio SHA-256 :', hashHex); })();
On the screenshots below you can see that the audio fingerprint of the same PC in different browsers does not differ (in our case it produced the number 953.152941). You can compare them and run the code on your own PC. The result will be identical, but with your own fingerprint, of course.


Thus, each device normally has its own number, an audio hash.
Browser developers long ago realized the danger of this method. Apple was among the first to add protection: starting with Safari 17, in Private mode the AudioContext API intentionally injects a small randomness into the generated sound. Thanks to this change, the same Safari can produce different audio hashes in different sessions. Most other browsers still allow audio fingerprinting without major obstacles (as the example above demonstrates). Combined with Canvas and WebGL data, audio fingerprinting significantly increases device recognizability.
WebGL fingerprinting
HTML5 WebGL is a graphics API for rendering 3D in the browser (via a <canvas> with a WebGL context). It has also become a tool for capturing device fingerprints. If Canvas fingerprinting reveals differences in 2D rendering, WebGL digs deeper, into the GPU itself. Identification possibilities here are even broader. From WebGL data you can almost directly learn the GPU model and driver, and small rendering details can even distinguish two devices with the same GPU.
A typical WebGL fingerprint scenario looks like this:
WebGL initialization. The script creates a WebGL context (for example,
canvas.getContext("webgl2")). At this step the script can already obtain some environment details: GPU name (vendor/renderer), driver version, supported extensions, etc.Scene rendering. Then a hidden canvas renders a 3D scene or special primitives. Typically a set of shapes with shader effects, lighting and textures is drawn — enough to exercise different parts of the graphics pipeline.
Parameter collection. After rendering the script reads the resulting image (via
gl.readPixels) and queries a set of WebGL parameters: supported extensions, maximum texture sizes, shader precision,RENDERER/VENDORstrings, etc. These data form a kind of “hardware snapshot” of the graphics system.Hash generation. The collected numbers and strings are combined and hashed (for example with the SHA-256 algorithm). The resulting hash becomes the WebGL fingerprint. It is then sent to the server and the website can use it for identification on current and future visits.
Here is an example of how such a hash is generated:
(async () => { const cv = document.createElement('canvas'); cv.width = 400; cv.height = 200; const gl = cv.getContext('webgl2', {antialias:true}) || cv.getContext('webgl', {antialias:true}); if (!gl) { console.log('WebGL недоступен'); return; } const info = {}; const dbg = gl.getExtension('WEBGL_debug_renderer_info'); if (dbg) { info.vendor = gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL); info.renderer = gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL); } else { info.vendor = '(masked)'; info.renderer = '(masked)'; } info.version = gl.getParameter(gl.VERSION); info.glsl = gl.getParameter(gl.SHADING_LANGUAGE_VERSION); info.maxTex = gl.getParameter(gl.MAX_TEXTURE_SIZE); const vs = ` attribute vec2 p; void main(){ gl_Position = vec4(p,0.0,1.0); } `; const fs = ` precision highp float; uniform vec2 u_res; float h(vec2 v){ float s = sin(dot(v, vec2(12.9898,78.233))) * 43758.5453; return fract(s); } void main(){ vec2 uv = gl_FragCoord.xy / u_res; float r = h(uv + vec2(0.11,0.21)); float g = h(uv*1.3 + vec2(0.31,0.41)); float b = h(uv*1.7 + vec2(0.51,0.61)); gl_FragColor = vec4(pow(vec3(r,g,b)*(0.6+0.4*uv.x), vec3(1.1)), 1.0); } `; function sh(type, src){ const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s); if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s)||'shader error'); return s; } const pr = gl.createProgram(); gl.attachShader(pr, sh(gl.VERTEX_SHADER, vs)); gl.attachShader(pr, sh(gl.FRAGMENT_SHADER, fs)); gl.linkProgram(pr); if (!gl.getProgramParameter(pr, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(pr)||'link error'); gl.useProgram(pr); const buf = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buf); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 3,-1, -1,3]), gl.STATIC_DRAW); const loc = gl.getAttribLocation(pr, 'p'); gl.enableVertexAttribArray(loc); gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0); const ures = gl.getUniformLocation(pr, 'u_res'); gl.uniform2f(ures, gl.drawingBufferWidth, gl.drawingBufferHeight); gl.viewport(0,0,gl.drawingBufferWidth, gl.drawingBufferHeight); gl.clearColor(0,0,0,1); gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLES, 0, 3); const w = gl.drawingBufferWidth, h = gl.drawingBufferHeight; const px = new Uint8Array(w*h*4); gl.readPixels(0,0,w,h, gl.RGBA, gl.UNSIGNED_BYTE, px); const enc = new TextEncoder(); const meta = enc.encode(JSON.stringify(info)); const full = new Uint8Array(meta.length + px.length); full.set(meta, 0); full.set(px, meta.length); const hashBuf = await crypto.subtle.digest('SHA-256', full.buffer); const hex = Array.from(new Uint8Array(hashBuf)) .map(b=>b.toString(16).padStart(2,'0')).join(''); let hi = 0>>>0, lo = 0>>>0; for (let i=0;i<px.length;i+=16){ const a = px[i] | (px[i+1]<<8) | (px[i+2]<<16) | (px[i+3]<<24); hi = ((hi ^ a) + 0x9e3779b9) >>> 0; lo = ((lo ^ ((a<<7)|(a>>>25))) + 0x85ebca6b) >>> 0; hi ^= (hi<<13)>>>0; lo ^= (lo<<15)>>>0; } const sample64 = ('00000000'+hi.toString(16)).slice(-8)+('00000000'+lo.toString(16)).slice(-8); console.log('WebGL vendor :', info.vendor); console.log('WebGL renderer:', info.renderer); console.log('WebGL version :', info.version, '| GLSL:', info.glsl); console.log('MAX_TEXTURE_SIZE:', info.maxTex); console.log('SHA-256(meta+pixels):', hex); console.log('Sample64:', sample64); })();
Here’s what we get in the end:

What differences does WebGL reveal? First, GPU identifiers. For example, integrated Intel Graphics and discrete NVIDIA GeForce or AMD Radeon have different capabilities, VRAM sizes and drivers, which is reflected in context parameters. Second, variations within the same model. Even supposedly identical GPU models have small individual differences in performance and calculation precision. WebGL can surface those too: measuring shader execution times or subtle pixel artifacts in rendered output will expose differences. Finally, the browser itself matters. Different rendering engines (Blink, WebKit, Gecko) execute WebGL calls differently and may produce slightly different render results. The screenshot below shows that when we generate the same fingerprint in Opera, differences appear.

WebGL fingerprint is often used together with Canvas and Audio for layered tracking, but even used alone it is informative enough and can tell a lot about the system.
How to protect yourself against fingerprinting
Completely eliminating browser fingerprinting is extremely difficult, as there are too many leakage channels. Nevertheless, a number of measures can reduce your uniqueness:
Special modes and browsers. Tor Browser enforces strict protection: all users share the same characteristic set, like clones. Canvas and WebGL are either disabled there or return averaged values. The downside is that many web services break. Brave in aggressive protection mode also blocks common tracking measures. Safari in Private mode adds noise to audio data to obscure the fingerprint.
Blocking and spoofing. There are extensions like CanvasBlocker that prevent scripts from reading Canvas or spoof the image with a random one. Plugins for AudioContext exist as well. However, relatively few users use these add-ons (around ~100k). A site that sees a completely blank Canvas or a wildly changing noise-hash will suspect something. Instead of hiding, you may stand out even more.
Environment unification. Another approach is to make the fingerprint non-unique but generic — e.g., run the browser inside a virtual machine or cloud service that gives all clients the same profile. Some anti-fraud systems do this: suspicious users are run inside an isolated “browser emulator” where their fingerprint is anonymized. But this is obviously inconvenient for everyday browsing.
Controlled spoofing. One of the best solutions is anti-detect browsers that allow fine-grained environment configuration. They let you decide what Canvas or WebGL the website sees. A perfect example is Octo Browser. It offers proactive spoofing: it adds noise to Canvas and audio, spoofs WebGL and other fingerprintable parameters, and produces configurations resembling real devices. Anti-detect browsers try to mimic a typical browser instead of randomly changing everything. A good anti-detect browser makes each profile look plausibly unique without standing out among millions of other users.
If you want to remain truly anonymous online, you’ll need different browsers and devices, keep your software constantly updated, disable unnecessary plugins, etc., but even then you only slightly improve your chances of blending in with the crowd. The better and more practical alternative is using professional tools like Octo Browser. An anti-detect browser can meaningfully and consistently spoof your fingerprint and help you preserve real privacy online.
Stay up to date with the latest Octo Browser news
By clicking the button you agree to our Privacy Policy.
Stay up to date with the latest Octo Browser news
By clicking the button you agree to our Privacy Policy.
Stay up to date with the latest Octo Browser news
By clicking the button you agree to our Privacy Policy.
Related articles
Related articles
Related articles

Join Octo Browser now
Or contact Customer Service at any time with any questions you might have.

Join Octo Browser now
Or contact Customer Service at any time with any questions you might have.
Join Octo Browser now
Or contact Customer Service at any time with any questions you might have.


