Canvas, Audio y WebGL: un análisis en profundidad de las tecnologías de huella digital

Canvas, Audio y WebGL: un análisis en profundidad de las tecnologías de huella digital
Markus_automation
Markus_automation

Expert in data parsing and automation

Hace tiempo que los sitios web aprendieron a reconocer a los visitantes no solo por las cookies. Incluso si borras el historial de navegación, activas el modo privado y cambias de IP, tu navegador puede seguir filtrando tu huella digital. Este identificador oculto se construye a partir de muchas características técnicas del sistema. Te sigue en cada una de tus visitas.

Una huella digital de navegador es una combinación de datos sobre tu dispositivo y entorno de software. Así como no hay dos copos de nieve idénticos, prácticamente no hay dos navegadores completamente idénticos. Los parámetros individuales (por ejemplo, la versión del navegador o la resolución de la pantalla) coinciden en miles de usuarios. Pero la combinación de docenas de estos detalles crea un perfil único. Lo que es peor, la huella digital no se almacena en tu dispositivo: el sitio web la calcula "al vuelo". Por este motivo, la huella digital no desaparece al borrar las cookies o al utilizar el modo privado: cada vez, un script volverá a recopilar los mismos rasgos.

Junto con los componentes sencillos de la huella digital, como el agente de usuario, la zona horaria o el idioma del navegador, existen tres técnicas avanzadas para obtenerla: Canvas, AudioContext y WebGL. Las tres aprovechan las capacidades de gráficos y audio integradas en el navegador para extraer información sobre tu hardware y software. Pero, ¿cómo funcionan exactamente? ¿Por qué revelan diferencias casi imperceptibles entre dispositivos? Analicémoslo.

Contenidos

Manténgase anónimo, aproveche el multicuentas y alcance sus objetivos con el navegador antidetección de mayor calidad del mercado.

¿Te gustaría probar Octo Browser con descuento?
Usa el código promocional OCTOBLOG para obtener un 30% de descuento en cualquier suscripción. Esta oferta solo es válida para nuevos usuarios.

Huella digital de Canvas (canvas fingerprinting)

La API de Canvas en el navegador permite dibujar gráficos mediante JavaScript. Esta capacidad se utiliza de forma encubierta para obtener una huella digital. Un script en la página crea un elemento <canvas> invisible y realiza una serie de comandos de dibujo (por ejemplo, representa texto, formas geométricas, añade sombras) y luego lee la imagen resultante como una matriz de píxeles. Cada dispositivo produce pequeñas variaciones en la salida de píxeles final debido a las diferencias en su hardware y software. Luego se calcula un hash a partir de esos datos y se utiliza como un identificador único de usuario.

El proceso se puede describir paso a paso de la siguiente manera:

  1. Creación del Canvas. El sitio visitado inserta dinámicamente un elemento <canvas> en la página (a menudo fuera de la pantalla o de forma oculta).

  2. Dibujo. El código JS representa gráficos de prueba en ese canvas. Normalmente dibuja una cadena de texto en una fuente poco común, añadiendo formas de colores, líneas y efectos (degradados, sombras).

  3. Lectura de los píxeles. Después de dibujar, el script llama a toDataURL() (o un método similar) para obtener una representación binaria del resultado.

  4. Hashing. La cadena obtenida (o matriz de píxeles) se pasa a través de una función de hashing. El código hash de salida se envía al servidor y se utiliza como la huella digital.

Por ejemplo, para simplificar: dibujemos la palabra “Fingerprint” en un Canvas y calculemos un hash primitivo:

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);
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);

Este código generará un número de 32 bits (puede ser negativo debido al desbordamiento) que depende de cómo el navegador haya representado el texto. Con otro navegador (en nuestro ejemplo, Opera), el resultado puede diferir (aunque a veces coincide, lo cual es poco común). En otro PC es aún más probable que los resultados difieran, aunque las coincidencias son posibles en casos límite.

Las capturas de pantalla de abajo muestran que incluso en el mismo PC pero en diferentes navegadores el hash difiere: en el primer caso representamos la palabra “Fingerprint” en Chrome, en el segundo en Opera. Puede repetir esto en su propio dispositivo.

Canvas ChromeCanvas Opera

¿Por qué difieren? No debido al texto “Fingerprint” en sí: visualmente se ve idéntico al ojo humano en todos los dispositivos. Las diferencias aparecen a nivel de renderizado: el suavizado de fuentes (hinting), el anti-aliasing, los algoritmos de rasterización y los sutiles ajustes de glifos para las rejillas de píxeles se manejan de manera diferente por los distintos sistemas operativos y navegadores. Añada a esto las variaciones en las GPU y los controladores, y cada dispositivo introduce pequeñas distorsiones en la imagen final. En un píxel el borde de la letra es ligeramente más claro, en otro es más oscuro, en algún lugar el glifo se suavizó de manera diferente. Estas diferencias microscópicas conducen a hashes diferentes a pesar de que las imágenes se vean idénticas visualmente.

Aun así, los sitios intentan amplificar la variabilidad. Pueden utilizar cadenas especialmente diseñadas que incluyen el alfabeto completo y diversos símbolos para activar muchas rutas de código de renderizado; por ejemplo, una frase que incluye casi todas las letras latinas: “Cwm fjordbank glyphs mute quiz.” También pueden dibujar rectángulos de colores, degradados o sombras sobre el texto; todo lo necesario para extraer más detalles únicos a nivel de píxel. El resultado final del Canvas fingerprinting es la cadena hash mencionada anteriormente, pero en una forma más compleja (por ejemplo, e3d52382d0…). Este hash identifica consistentemente a ese dispositivo y, en visitas repetidas, el mismo navegador producirá el mismo hash a menos que cambie su entorno.

De este modo, Canvas proporciona a los sitios web una potente herramienta de seguimiento. Sin el consentimiento del usuario, el sitio recopila un "renderizado" del sistema que depende del hardware. Los dispositivos con GPU y software idénticos pueden producir huellas digitales de Canvas coincidentes, pero encontrar dos gemelos de este tipo es extremadamente raro. Por lo general, la combinación de las especificaciones de la GPU, las fuentes y el software es lo suficientemente única para el seguimiento.

Huella digital de AudioContext (AudioContext fingerprinting)

El siguiente método es la huella digital de audio, donde la Web Audio API se convierte en una fuente de un identificador de "sonido" único. Puede sonar extraño: el sitio no solicita acceso al micrófono y no reproduce ningún sonido audible. Es más sutil. El script genera y procesa una señal de audio dentro del navegador y luego extrae métricas numéricas que reflejan indirectamente las características del sistema. El resultado es un identificador estable análogo al hash de Canvas, pero basado en el audio.

Cómo funciona a grandes rasgos:

  1. AudioContext. El script crea un contexto de audio oculto (normalmente un OfflineAudioContext), una "tarjeta de sonido" virtual que puede procesar audio en la memoria sin emitirlo a los altavoces.

  2. Generación de la señal. Un oscilador (OscillatorNode) genera un tono de frecuencia fija (por ejemplo, 1,000 Hz para una onda triangular). En lugar de cargar un archivo de audio, el oscilador sintetiza el tono mediante programación.

  3. Procesamiento de efectos. Para aumentar las diferencias de hardware, la señal se enruta a través de efectos de audio, a menudo un compresor (DynamicsCompressorNode) que "comprime" la onda de sonido. Al configurar el umbral, la relación, el tiempo de liberación y otros parámetros, aparecen cambios sutiles en la forma de la onda.

  4. Renderizado y lectura. El contexto de audio virtual renderiza rápidamente el fragmento de audio especificado en la memoria. Después del renderizado, el script obtiene un búfer de valores de muestra (una matriz de números de coma flotante). Por ejemplo, a 44,100 Hz y con una duración de ~113 ms, se podrían obtener unas 5,000 muestras.

  5. Cálculo de la huella digital. La matriz de muestras se reduce a un número compacto. Un método sencillo consiste en sumar los valores absolutos de todas las muestras y tomar los dígitos más significativos. Ese número se convierte en la huella digital de audio.

A continuación se muestra un ejemplo sencillo de código que puede ejecutar en la consola del navegador para generar una huella digital de audio:

(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);
})();
(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);
})();

En las capturas de pantalla de abajo puede ver que la huella digital de audio del mismo PC en diferentes navegadores no difiere (en nuestro caso produjo el número 953.152941). Puede compararlos y ejecutar el código en su propio PC. El resultado será idéntico, pero con su propia huella digital, por supuesto.

Audio ChromeAudio Opera

Por lo tanto, cada dispositivo tiene normalmente su propio número, un hash de audio.

Los desarrolladores de navegadores se dieron cuenta hace tiempo del peligro de este método. Apple estuvo entre los primeros en añadir protección: a partir de Safari 17, en el modo Privado, la API de AudioContext inyecta intencionadamente una pequeña aleatoriedad en el sonido generado. Gracias a este cambio, el mismo Safari puede producir diferentes hashes de audio en distintas sesiones. La mayoría de los demás navegadores todavía permiten la recopilación de huellas digitales de audio sin mayores obstáculos (como demuestra el ejemplo anterior). Combinado con los datos de Canvas y WebGL, la huella digital de audio aumenta significativamente la reconocibilidad del dispositivo.

Huella digital de WebGL (WebGL fingerprinting)

HTML5 WebGL es una API de gráficos para renderizar 3D en el navegador (a través de un <canvas> con un contexto WebGL). También se ha convertido en una herramienta para capturar huellas digitales de dispositivos. Si la huella digital de Canvas revela diferencias en el procesamiento 2D, WebGL profundiza más, en la propia GPU. Las posibilidades de identificación aquí son aún más amplias. A partir de los datos de WebGL, casi se puede conocer directamente el modelo de GPU y el controlador, y los pequeños detalles del renderizado pueden incluso distinguir dos dispositivos con la misma GPU.

Un escenario típico de huella digital de WebGL se ve así:

  1. Inicialización de WebGL. El script crea un contexto WebGL (por ejemplo, canvas.getContext("webgl2")). En este paso, el script ya puede obtener algunos detalles del entorno: nombre de la GPU (proveedor/procesador), versión del controlador, extensiones compatibles, etc.

  2. Renderizado de la escena. Luego, un canvas oculto renderiza una escena 3D o primitivos especiales. Normalmente se dibuja un conjunto de formas con efectos de sombreado (shaders), iluminación y texturas, lo suficiente para poner a prueba diferentes partes de la tubería de gráficos (pipeline).

  3. Recopilación de parámetros. Después del renderizado, el script lee la imagen resultante (a través de gl.readPixels) y consulta un conjunto de parámetros WebGL: extensiones compatibles, tamaños máximos de textura, precisión del sombreador, cadenas RENDERER/VENDOR, etc. Estos datos forman una especie de "instantánea de hardware" del sistema de gráficos.

  4. Generación del hash. Los números y cadenas recopilados se combinan y se les aplica un hash (por ejemplo, con el algoritmo SHA-256). El hash resultante se convierte en la huella digital de WebGL. Luego se envía al servidor y el sitio web puede utilizarla para la identificación en visitas actuales y futuras.

Aquí hay un ejemplo de cómo se genera dicho hash:

(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);
})();
(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);
})();

Esto es lo que obtenemos al final:

WebGL Chrome

¿Qué diferencias revela WebGL? Primero, los identificadores de la GPU. Por ejemplo, los gráficos integrados de Intel y las tarjetas dedicadas de NVIDIA GeForce o AMD Radeon tienen diferentes capacidades, tamaños de VRAM y controladores, lo que se refleja en los parámetros del contexto. Segundo, variaciones dentro del mismo modelo. Incluso modelos de GPU supuestamente idénticos presentan pequeñas diferencias individuales de rendimiento y precisión de cálculo. WebGL también puede sacarlas a la luz: la medición de los tiempos de ejecución del sombreador (shader) o los sutiles artefactos de píxeles en el resultado renderizado expondrán las diferencias. Por último, el propio navegador importa. Los distintos motores de renderizado (Blink, WebKit, Gecko) ejecutan las llamadas a WebGL de manera diferente y pueden producir resultados de renderizado ligeramente distintos. La captura de pantalla siguiente muestra que cuando generamos la misma huella digital en Opera, aparecen diferencias.

WebGL Opera

La huella digital de WebGL se suele utilizar junto con la de Canvas y Audio para un seguimiento por capas, pero incluso utilizada sola aporta bastante información y puede decir mucho sobre el sistema.

Cómo protegerse contra la obtención de huellas digitales (fingerprinting)

Eliminar por completo la obtención de huella digital en el navegador es sumamente difícil, ya que existen demasiados canales de filtración. No obstante, una serie de medidas pueden reducir su singularidad:

  • Modos y navegadores especiales. Tor Browser aplica una protección estricta: todos los usuarios comparten el mismo conjunto de características, como si fueran clones. Allí Canvas y WebGL están desactivados o devuelven valores promediados. La desventaja es que muchos servicios web dejan de funcionar. Brave, en su modo de protección agresiva, también bloquea las medidas de seguimiento habituales. Safari en modo Privado añade ruido a los datos de audio para ocultar la huella digital.

  • Bloqueo y suplantación (spoofing). Existen extensiones como CanvasBlocker que impiden que los scripts lean el Canvas o suplantan la imagen con una aleatoria. También existen complementos para AudioContext. Sin embargo, relativamente pocos usuarios utilizan estos complementos (alrededor de ~100k). Un sitio que vea un Canvas completamente en blanco o un hash de ruido que cambie constantemente sospechará que algo ocurre. En lugar de ocultarse, es posible que llame aún más la atención.

  • Unificación del entorno. Otro enfoque consiste en hacer que la huella digital no sea única sino genérica; por ejemplo, ejecutar el navegador dentro de una máquina virtual o un servicio en la nube que proporcione a todos los clientes el mismo perfil. Algunos sistemas antifraude hacen esto: los usuarios sospechosos se ejecutan dentro de un "emulador de navegador" aislado donde su huella digital se anonimiza. Pero esto resulta obviamente incómodo para la navegación diaria.

  • Suplantación controlada. Una de las mejores soluciones son los navegadores antidetección, que permiten configurar el entorno de forma detallada. Le permiten decidir qué Canvas o WebGL ve el sitio web. Un ejemplo perfecto es Octo Browser. Ofrece una suplantación proactiva: añade ruido a Canvas y al audio, suplanta WebGL y otros parámetros susceptibles de crear huellas digitales, y genera configuraciones que se asemejan a dispositivos reales. Los navegadores antidetección intentan imitar a un navegador típico en lugar de cambiarlo todo al azar. Un buen navegador antidetección hace que cada perfil parezca plausiblemente único sin destacar entre millones de otros usuarios.

Si desea permanecer verdaderamente anónimo en línea, necesitará usar diferentes navegadores y dispositivos, mantener su software constantemente actualizado, desactivar complementos innecesarios, etc., pero incluso entonces solo mejorará ligeramente sus posibilidades de pasar desapercibido entre la multitud. La alternativa mejor y más práctica es utilizar herramientas profesionales como Octo Browser. Un navegador antidetección puede suplantar de manera significativa y consistente su huella digital y ayudarle a preservar una privacidad real en línea.

Manténgase anónimo, aproveche el multicuentas y alcance sus objetivos con el navegador antidetección de mayor calidad del mercado.

¿Te gustaría probar Octo Browser con descuento?
Usa el código promocional OCTOBLOG para obtener un 30% de descuento en cualquier suscripción. Esta oferta solo es válida para nuevos usuarios.

Huella digital de Canvas (canvas fingerprinting)

La API de Canvas en el navegador permite dibujar gráficos mediante JavaScript. Esta capacidad se utiliza de forma encubierta para obtener una huella digital. Un script en la página crea un elemento <canvas> invisible y realiza una serie de comandos de dibujo (por ejemplo, representa texto, formas geométricas, añade sombras) y luego lee la imagen resultante como una matriz de píxeles. Cada dispositivo produce pequeñas variaciones en la salida de píxeles final debido a las diferencias en su hardware y software. Luego se calcula un hash a partir de esos datos y se utiliza como un identificador único de usuario.

El proceso se puede describir paso a paso de la siguiente manera:

  1. Creación del Canvas. El sitio visitado inserta dinámicamente un elemento <canvas> en la página (a menudo fuera de la pantalla o de forma oculta).

  2. Dibujo. El código JS representa gráficos de prueba en ese canvas. Normalmente dibuja una cadena de texto en una fuente poco común, añadiendo formas de colores, líneas y efectos (degradados, sombras).

  3. Lectura de los píxeles. Después de dibujar, el script llama a toDataURL() (o un método similar) para obtener una representación binaria del resultado.

  4. Hashing. La cadena obtenida (o matriz de píxeles) se pasa a través de una función de hashing. El código hash de salida se envía al servidor y se utiliza como la huella digital.

Por ejemplo, para simplificar: dibujemos la palabra “Fingerprint” en un Canvas y calculemos un hash primitivo:

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);

Este código generará un número de 32 bits (puede ser negativo debido al desbordamiento) que depende de cómo el navegador haya representado el texto. Con otro navegador (en nuestro ejemplo, Opera), el resultado puede diferir (aunque a veces coincide, lo cual es poco común). En otro PC es aún más probable que los resultados difieran, aunque las coincidencias son posibles en casos límite.

Las capturas de pantalla de abajo muestran que incluso en el mismo PC pero en diferentes navegadores el hash difiere: en el primer caso representamos la palabra “Fingerprint” en Chrome, en el segundo en Opera. Puede repetir esto en su propio dispositivo.

Canvas ChromeCanvas Opera

¿Por qué difieren? No debido al texto “Fingerprint” en sí: visualmente se ve idéntico al ojo humano en todos los dispositivos. Las diferencias aparecen a nivel de renderizado: el suavizado de fuentes (hinting), el anti-aliasing, los algoritmos de rasterización y los sutiles ajustes de glifos para las rejillas de píxeles se manejan de manera diferente por los distintos sistemas operativos y navegadores. Añada a esto las variaciones en las GPU y los controladores, y cada dispositivo introduce pequeñas distorsiones en la imagen final. En un píxel el borde de la letra es ligeramente más claro, en otro es más oscuro, en algún lugar el glifo se suavizó de manera diferente. Estas diferencias microscópicas conducen a hashes diferentes a pesar de que las imágenes se vean idénticas visualmente.

Aun así, los sitios intentan amplificar la variabilidad. Pueden utilizar cadenas especialmente diseñadas que incluyen el alfabeto completo y diversos símbolos para activar muchas rutas de código de renderizado; por ejemplo, una frase que incluye casi todas las letras latinas: “Cwm fjordbank glyphs mute quiz.” También pueden dibujar rectángulos de colores, degradados o sombras sobre el texto; todo lo necesario para extraer más detalles únicos a nivel de píxel. El resultado final del Canvas fingerprinting es la cadena hash mencionada anteriormente, pero en una forma más compleja (por ejemplo, e3d52382d0…). Este hash identifica consistentemente a ese dispositivo y, en visitas repetidas, el mismo navegador producirá el mismo hash a menos que cambie su entorno.

De este modo, Canvas proporciona a los sitios web una potente herramienta de seguimiento. Sin el consentimiento del usuario, el sitio recopila un "renderizado" del sistema que depende del hardware. Los dispositivos con GPU y software idénticos pueden producir huellas digitales de Canvas coincidentes, pero encontrar dos gemelos de este tipo es extremadamente raro. Por lo general, la combinación de las especificaciones de la GPU, las fuentes y el software es lo suficientemente única para el seguimiento.

Huella digital de AudioContext (AudioContext fingerprinting)

El siguiente método es la huella digital de audio, donde la Web Audio API se convierte en una fuente de un identificador de "sonido" único. Puede sonar extraño: el sitio no solicita acceso al micrófono y no reproduce ningún sonido audible. Es más sutil. El script genera y procesa una señal de audio dentro del navegador y luego extrae métricas numéricas que reflejan indirectamente las características del sistema. El resultado es un identificador estable análogo al hash de Canvas, pero basado en el audio.

Cómo funciona a grandes rasgos:

  1. AudioContext. El script crea un contexto de audio oculto (normalmente un OfflineAudioContext), una "tarjeta de sonido" virtual que puede procesar audio en la memoria sin emitirlo a los altavoces.

  2. Generación de la señal. Un oscilador (OscillatorNode) genera un tono de frecuencia fija (por ejemplo, 1,000 Hz para una onda triangular). En lugar de cargar un archivo de audio, el oscilador sintetiza el tono mediante programación.

  3. Procesamiento de efectos. Para aumentar las diferencias de hardware, la señal se enruta a través de efectos de audio, a menudo un compresor (DynamicsCompressorNode) que "comprime" la onda de sonido. Al configurar el umbral, la relación, el tiempo de liberación y otros parámetros, aparecen cambios sutiles en la forma de la onda.

  4. Renderizado y lectura. El contexto de audio virtual renderiza rápidamente el fragmento de audio especificado en la memoria. Después del renderizado, el script obtiene un búfer de valores de muestra (una matriz de números de coma flotante). Por ejemplo, a 44,100 Hz y con una duración de ~113 ms, se podrían obtener unas 5,000 muestras.

  5. Cálculo de la huella digital. La matriz de muestras se reduce a un número compacto. Un método sencillo consiste en sumar los valores absolutos de todas las muestras y tomar los dígitos más significativos. Ese número se convierte en la huella digital de audio.

A continuación se muestra un ejemplo sencillo de código que puede ejecutar en la consola del navegador para generar una huella digital de audio:

(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);
})();

En las capturas de pantalla de abajo puede ver que la huella digital de audio del mismo PC en diferentes navegadores no difiere (en nuestro caso produjo el número 953.152941). Puede compararlos y ejecutar el código en su propio PC. El resultado será idéntico, pero con su propia huella digital, por supuesto.

Audio ChromeAudio Opera

Por lo tanto, cada dispositivo tiene normalmente su propio número, un hash de audio.

Los desarrolladores de navegadores se dieron cuenta hace tiempo del peligro de este método. Apple estuvo entre los primeros en añadir protección: a partir de Safari 17, en el modo Privado, la API de AudioContext inyecta intencionadamente una pequeña aleatoriedad en el sonido generado. Gracias a este cambio, el mismo Safari puede producir diferentes hashes de audio en distintas sesiones. La mayoría de los demás navegadores todavía permiten la recopilación de huellas digitales de audio sin mayores obstáculos (como demuestra el ejemplo anterior). Combinado con los datos de Canvas y WebGL, la huella digital de audio aumenta significativamente la reconocibilidad del dispositivo.

Huella digital de WebGL (WebGL fingerprinting)

HTML5 WebGL es una API de gráficos para renderizar 3D en el navegador (a través de un <canvas> con un contexto WebGL). También se ha convertido en una herramienta para capturar huellas digitales de dispositivos. Si la huella digital de Canvas revela diferencias en el procesamiento 2D, WebGL profundiza más, en la propia GPU. Las posibilidades de identificación aquí son aún más amplias. A partir de los datos de WebGL, casi se puede conocer directamente el modelo de GPU y el controlador, y los pequeños detalles del renderizado pueden incluso distinguir dos dispositivos con la misma GPU.

Un escenario típico de huella digital de WebGL se ve así:

  1. Inicialización de WebGL. El script crea un contexto WebGL (por ejemplo, canvas.getContext("webgl2")). En este paso, el script ya puede obtener algunos detalles del entorno: nombre de la GPU (proveedor/procesador), versión del controlador, extensiones compatibles, etc.

  2. Renderizado de la escena. Luego, un canvas oculto renderiza una escena 3D o primitivos especiales. Normalmente se dibuja un conjunto de formas con efectos de sombreado (shaders), iluminación y texturas, lo suficiente para poner a prueba diferentes partes de la tubería de gráficos (pipeline).

  3. Recopilación de parámetros. Después del renderizado, el script lee la imagen resultante (a través de gl.readPixels) y consulta un conjunto de parámetros WebGL: extensiones compatibles, tamaños máximos de textura, precisión del sombreador, cadenas RENDERER/VENDOR, etc. Estos datos forman una especie de "instantánea de hardware" del sistema de gráficos.

  4. Generación del hash. Los números y cadenas recopilados se combinan y se les aplica un hash (por ejemplo, con el algoritmo SHA-256). El hash resultante se convierte en la huella digital de WebGL. Luego se envía al servidor y el sitio web puede utilizarla para la identificación en visitas actuales y futuras.

Aquí hay un ejemplo de cómo se genera dicho hash:

(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);
})();

Esto es lo que obtenemos al final:

WebGL Chrome

¿Qué diferencias revela WebGL? Primero, los identificadores de la GPU. Por ejemplo, los gráficos integrados de Intel y las tarjetas dedicadas de NVIDIA GeForce o AMD Radeon tienen diferentes capacidades, tamaños de VRAM y controladores, lo que se refleja en los parámetros del contexto. Segundo, variaciones dentro del mismo modelo. Incluso modelos de GPU supuestamente idénticos presentan pequeñas diferencias individuales de rendimiento y precisión de cálculo. WebGL también puede sacarlas a la luz: la medición de los tiempos de ejecución del sombreador (shader) o los sutiles artefactos de píxeles en el resultado renderizado expondrán las diferencias. Por último, el propio navegador importa. Los distintos motores de renderizado (Blink, WebKit, Gecko) ejecutan las llamadas a WebGL de manera diferente y pueden producir resultados de renderizado ligeramente distintos. La captura de pantalla siguiente muestra que cuando generamos la misma huella digital en Opera, aparecen diferencias.

WebGL Opera

La huella digital de WebGL se suele utilizar junto con la de Canvas y Audio para un seguimiento por capas, pero incluso utilizada sola aporta bastante información y puede decir mucho sobre el sistema.

Cómo protegerse contra la obtención de huellas digitales (fingerprinting)

Eliminar por completo la obtención de huella digital en el navegador es sumamente difícil, ya que existen demasiados canales de filtración. No obstante, una serie de medidas pueden reducir su singularidad:

  • Modos y navegadores especiales. Tor Browser aplica una protección estricta: todos los usuarios comparten el mismo conjunto de características, como si fueran clones. Allí Canvas y WebGL están desactivados o devuelven valores promediados. La desventaja es que muchos servicios web dejan de funcionar. Brave, en su modo de protección agresiva, también bloquea las medidas de seguimiento habituales. Safari en modo Privado añade ruido a los datos de audio para ocultar la huella digital.

  • Bloqueo y suplantación (spoofing). Existen extensiones como CanvasBlocker que impiden que los scripts lean el Canvas o suplantan la imagen con una aleatoria. También existen complementos para AudioContext. Sin embargo, relativamente pocos usuarios utilizan estos complementos (alrededor de ~100k). Un sitio que vea un Canvas completamente en blanco o un hash de ruido que cambie constantemente sospechará que algo ocurre. En lugar de ocultarse, es posible que llame aún más la atención.

  • Unificación del entorno. Otro enfoque consiste en hacer que la huella digital no sea única sino genérica; por ejemplo, ejecutar el navegador dentro de una máquina virtual o un servicio en la nube que proporcione a todos los clientes el mismo perfil. Algunos sistemas antifraude hacen esto: los usuarios sospechosos se ejecutan dentro de un "emulador de navegador" aislado donde su huella digital se anonimiza. Pero esto resulta obviamente incómodo para la navegación diaria.

  • Suplantación controlada. Una de las mejores soluciones son los navegadores antidetección, que permiten configurar el entorno de forma detallada. Le permiten decidir qué Canvas o WebGL ve el sitio web. Un ejemplo perfecto es Octo Browser. Ofrece una suplantación proactiva: añade ruido a Canvas y al audio, suplanta WebGL y otros parámetros susceptibles de crear huellas digitales, y genera configuraciones que se asemejan a dispositivos reales. Los navegadores antidetección intentan imitar a un navegador típico en lugar de cambiarlo todo al azar. Un buen navegador antidetección hace que cada perfil parezca plausiblemente único sin destacar entre millones de otros usuarios.

Si desea permanecer verdaderamente anónimo en línea, necesitará usar diferentes navegadores y dispositivos, mantener su software constantemente actualizado, desactivar complementos innecesarios, etc., pero incluso entonces solo mejorará ligeramente sus posibilidades de pasar desapercibido entre la multitud. La alternativa mejor y más práctica es utilizar herramientas profesionales como Octo Browser. Un navegador antidetección puede suplantar de manera significativa y consistente su huella digital y ayudarle a preservar una privacidad real en línea.

Mantente al día de las últimas noticias sobre Octo Browser

Al hacer clic en el botón, aceptas nuestra Política de privacidad.

Mantente al día de las últimas noticias sobre Octo Browser

Al hacer clic en el botón, aceptas nuestra Política de privacidad.

Mantente al día de las últimas noticias sobre Octo Browser

Al hacer clic en el botón, aceptas nuestra Política de privacidad.

Únete a Octo Browser ahora

O contacta al servicio al cliente en cualquier momento si tienes alguna pregunta.

Únete a Octo Browser ahora

O contacta al servicio al cliente en cualquier momento si tienes alguna pregunta.

Únete a Octo Browser ahora

O contacta al servicio al cliente en cualquier momento si tienes alguna pregunta.

©

2026

Octo Browser

©

2026

Octo Browser

©

2026

Octo Browser