Canvas, Audio и WebGL: углубленный анализ технологий фингерпринтинга
28.10.2025

Веб-сайты давно научились узнавать посетителей не только по файлам cookie. Даже если вы очистите историю, включите режим инкогнито и смените IP, ваш браузер все равно может выдать цифровой отпечаток. Этот скрытый идентификатор составляется из множества технических характеристик системы. Он преследует вас при каждом визите.
Отпечаток браузера — сочетание данных о вашем устройстве и программном окружении. Как не бывает двух одинаковых снежинок, так не бывает двух полностью одинаковых браузеров (ну почти). Единичные параметры (скажем, версия браузера или разрешение экрана) сами по себе встречаются у тысяч людей. Но комбинация десятков таких деталей образует особый профиль. Еще неприятнее, что он не хранится у вас на диске, а вычисляется «на лету» на стороне сайта. По этой причине отпечаток не исчезнет при очистке куки или в приватном режиме — каждый раз скрипт заново соберет те же признаки.
Наравне с простыми компонентами отпечатка, вроде юзерагента, часового пояса или языка браузера, существуют три продвинутые техники его получения: Canvas, AudioContext и WebGL. Все три задействуют возможности самого браузера (графику и звук) для извлечения информации о вашем железе и софте. Но как именно они работают? Поче
Веб-сайты давно научились узнавать посетителей не только по файлам cookie. Даже если вы очистите историю, включите режим инкогнито и смените IP, ваш браузер все равно может выдать цифровой отпечаток. Этот скрытый идентификатор составляется из множества технических характеристик системы. Он преследует вас при каждом визите.
Отпечаток браузера — сочетание данных о вашем устройстве и программном окружении. Как не бывает двух одинаковых снежинок, так не бывает двух полностью одинаковых браузеров (ну почти). Единичные параметры (скажем, версия браузера или разрешение экрана) сами по себе встречаются у тысяч людей. Но комбинация десятков таких деталей образует особый профиль. Еще неприятнее, что он не хранится у вас на диске, а вычисляется «на лету» на стороне сайта. По этой причине отпечаток не исчезнет при очистке куки или в приватном режиме — каждый раз скрипт заново соберет те же признаки.
Наравне с простыми компонентами отпечатка, вроде юзерагента, часового пояса или языка браузера, существуют три продвинутые техники его получения: Canvas, AudioContext и WebGL. Все три задействуют возможности самого браузера (графику и звук) для извлечения информации о вашем железе и софте. Но как именно они работают? Поче
Содержание
Canvas-фингерпринтинг
Canvas API в браузере позволяет рисовать графику средствами JavaScript. Именно эту возможность скрытно задействуют для получения отпечатка. Скрипт на странице создает невидимый <canvas>-элемент и выполняет серию команд рисования (например, отрисовывает текст, геометрические фигуры, добавляет тень), а затем считывает получившееся изображение как набор пикселей. У каждого устройства появляются едва заметные отличия в итоговом пиксельном рисунке — из-за особенностей его железа и ПО. Далее вычисляется хеш от этих данных, который и служит уникальным идентификатором пользователя.
Процесс можно описать по шагам:
Создание Canvas. Сайт, к которому пользователь обращается, динамически добавляет на страницу элемент
<canvas>(и не всегда в видимой области экрана, чаще за ее пределами).Рисование. С помощью JS-кода в этот Canvas выводятся тестовые графические данные. Обычно рисуют строку текста нестандартным шрифтом, а также разноцветные фигуры, линии, добавляют эффекты (градиент, тени).
Чтение пикселей. Завершив рисование, скрипт вызывает метод
toDataURL() (или аналогичный) у Canvas, чтобы получить битовое представление результата.Хеширование. Полученная строка (или массив пикселей) пропускается через функцию хеширования. Выходной хеш-код отправляется на сервер и служит отпечатком.
Например, упростим и нарисуем слово Fingerprint на Canvas и вычислим примитивный хеш:
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);
Этот код выведет 32-битное число (оно может быть отрицательным из-за переполнения), зависящее от того, как браузер отрисует надпись. На другом браузере (в нашем случае это браузер Opera) результат может быть другим (но может и совпасть, правда, такое бывает редко). На другом ПК результаты с большей степенью вероятности будут разными, хотя в некоторых случаях совпадения возможны.
На скриншотах ниже видно, что даже на одном ПК, но в разных браузерах хеш различается: в первом случае мы отрисовали слово Fingerprint на браузере Chrome, а во втором — на браузере Opera. Можете повторить это на своем ПК.


Что дает разницу? Не сам текст Fingerprint, так как даже на разных устройствах он выглядит одинаково для человеческого глаза. Различия появляются на уровне рендеринга шрифтов и графики. Сглаживание, алгоритмы растеризации кривых, хинтинг шрифтов (микрокоррекция формы глифов под пиксельную сетку) — разные операционные системы и браузеры все это обрабатывают по-разному. Добавьте сюда различия в видеокартах и драйверах, плюс каждый компьютер привносит свои мелкие искажения в итоговое изображение. На одном пикселе контур буквы чуть светлее, на другом темнее, где-то символ сгладился иначе. Эти микроскопические разницы и приводят к разным хешам, хотя визуально картинки выглядят идентично.
Сайты, в свою очередь, стараются усилить вариативность. Используют специальные текстовые строки, содержащие весь алфавит и символы, чтобы задействовать максимум обходных путей рендеринга. Как пример, использовать фразы, которые включают почти все буквы латиницы, как тут — Cwm fjordbank glyphs mute quiz. Поверх текста также могут рисовать разноцветный прямоугольник или градиент, добавлять тень — все это позволяет получить еще больше уникальных пиксельных деталей. Итогом работы Canvas-фингерпринта является та самая хеш-строка, о которой рассказывали выше, но в более усложненном варианте — e3d52382d0…. Этот хеш стабильно идентифицирует данное устройство, и при повторном визите этот же браузер выдаст этот же самый хеш, если его окружение не изменилось.
Таким образом, Canvas дает сайту мощный инструмент слежения. Без ведома пользователя сайт собирает аппаратно-зависимый «рисунок» его системы. Устройства с идентичной видеокартой и ПО могут выдавать совпадающие отпечатки Canvas. Однако встретить двух таких близнецов можно чрезвычайно редко. Обычно комбинация графических особенностей, шрифтов и софта уникальна, а для трекинга этого достаточно.
AudioContext-фингерпринтинг
Следующий метод — аудиоотпечаток, при котором браузерный Web Audio API служит источником уникального «звукового» идентификатора. Может звучать странно: ведь сайт не запрашивает микрофон и не воспроизводит слышимый звук. Тут все несколько хитрее. Скрипт генерирует и обрабатывает аудиосигнал внутри браузера, а затем извлекает числовые показатели, косвенно отражающие особенности системы. В итоге получается стабильный идентификатор по аналогии с canvas-хешем, только основанный на аудио.
Как это работает в общих чертах:
Аудиоконтекст. Скрипт создает скрытый аудиоконтекст (обычно
OfflineAudioContext) — виртуальную «звуковую карту», способную мгновенно обработать звук в памяти, не выводя его на колонки.Генерация сигнала. В контексте запускают генератор звука, например
OscillatorNode, задав фиксированную частоту (к примеру, 1 000 Гц для треугольного сигнала). Вместо аудиофайла осциллятор синтезирует чистый тон программно.Обработка эффектами. Чтобы усилить аппаратные различия, сигнал пропускают через звуковой эффект. Чаще всего используют компрессор (
DynamicsCompressorNode), как бы «сдавливающий» волну. Путем установки порога срабатывания, коэффициента сжатия, времени релиза и других параметров добиваются тонких изменений формы волны.Рендеринг и считывание. Виртуальный аудиоконтекст быстро просчитывает заданный отрезок звука. По завершении скрипт получает буфер с отсчетами аудиосигнала (набором чисел с плавающей запятой). Например, при частоте дискретизации 44 100 Гц и длительности ~ 113 мс получится около 5 000 значений.
Вычисление 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); })();
На скриншотах ниже видно, что аудиоотпечаток одного и того же ПК, но на разных браузерах не отличается (у нас он выдал такую цифру — 953.152941). Вы можете сравнить их и повторить код на своем ПК. Результат будет идентичный, но с вашим отпечатком, соответственно.


Таким образом, видно, что у каждой среднестатистической машины есть свое число — аудиохеш.
Разработчики браузеров давно осознали опасность такого метода. Компания Apple одной из первых внесла защиту: начиная с Safari 17, в приватном режиме API AudioContext специально добавляет небольшую случайность в генерируемый звук. Благодаря этому изменению один и тот же Safari может выдавать разные аудиохеши в разных сессиях. Остальные браузеры пока позволяют собрать аудиоотпечаток без особых помех (что и демонстрирует пример выше). В сочетании с Canvas- и WebGL-данными он серьезно повышает узнаваемость устройства.
WebGL-фингерпринтинг
HTML5 WebGL — это графический API для вывода 3D-картинки прямо в браузере (через <canvas> с контекстом WebGL). Он тоже стал инструментом для «снятия отпечатков» устройств. Если Canvas-фингерпринт выявляет различия в 2D-рисунках, то WebGL копает глубже — в сам графический процессор. Здесь возможности идентификации еще шире. По данным WebGL можно почти напрямую узнать модель видеокарты и драйвера, а по мелким деталям рендеринга — различить даже два компьютера с одинаковым GPU.
Типичный сценарий WebGL-фингерпринта такой:
Инициализация WebGL. Скрипт создает WebGL-контекст [например,
canvas.getContext("webgl2")]. Уже на этом шаге скрипт может получить некоторые детали окружения: названия графического адаптера (vendor/renderer), версию драйвера, поддерживаемые расширения и пр.Отрисовка сцены. Затем в скрытом Canvas выполняется рендеринг 3D-сцены или специальных примитивов. Чаще всего рисуют набор фигур с шейдерными эффектами, освещением и текстурами — так, чтобы задействовать разные части графического конвейера.
Сбор параметров. После рендеринга скрипт считывает полученное изображение (через
gl.readPixels), а также запрашивает ряд параметров WebGL: список поддерживаемых расширений, максимальные размеры текстур, точность шейдерной арифметики, строкиRENDERER/VENDORи т. д. Эти данные содержат своего рода «аппаратный слепок» графической системы.Генерация хеша. Собранный набор цифр и строк объединяется и хешируется (как вариант — алгоритмом SHA-256). Итоговый хеш и считается WebGL-отпечатком. Его отправляют на сервер, и сайт может использовать его для идентификации при текущем и будущих визитах.
Вот пример, как происходит генерация такого хеша:
(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); })();
Вот что получаем в итоге:

Какие именно различия выявляет WebGL? Во-первых, идентификаторы GPU. Например, одно дело встроенная Intel Graphics, другое — дискретная NVIDIA GeForce или AMD Radeon. У них разный набор возможностей, объем видеопамяти и драйверов, что отражается в параметрах контекста. Во-вторых, вариации внутри одной модели. Казалось бы одинаковые экземпляры видеокарт все равно имеют мелкие индивидуальные отличия по производительности и точности вычислений. WebGL может выудить и их: например, замеры времени выполнения шейдерных программ или тонкие артефакты в нарисованных пикселях выдадут разницу. Наконец, влияет и сам браузер. Разные графические движки (Blink, WebKit, Gecko) по-разному выполняют WebGL-вызовы и могут выдавать слегка отличающиеся результаты рендеринга. Ниже на скрине видно, что, когда мы генерим тот же самый отпечаток в браузере Опера, уже есть отличия.

WebGL-отпечаток часто используется совместно с Canvas и Audio для комплексного трекинга, но и без них довольно информативен и способен много рассказать о системе.
Как защититься от фингерпринтинга
Полностью исключить браузерный фингерпринтинг крайне трудно — слишком много каналов утечки. Тем не менее несколько мер могут снизить вашу уникальность:
Специальные режимы и браузеры. В Tor Browser по умолчанию включена жесткая защита: все пользователи имеют один и тот же набор характеристик, словно клоны. Canvas и WebGL там либо отключены, либо выдают усредненные значения. Минус — многие веб-сервисы ломаются. Браузер Brave в режиме агрессивной защиты также блокирует известные приемы слежения. Safari в приватном режиме добавляет шум в аудиоданные, пытаясь скрыть отпечаток.
Блокировка и спуфинг. Существуют расширения вроде CanvasBlocker, которые запрещают скриптам читать Canvas или подменяют рисунок на случайный. Есть и плагины для AudioContext. Но имейте в виду: пользователей подобных аддонов не так много (около 100 тысяч человек). Сайт, увидев абсолютно чистый Canvas или хаотично меняющийся noise-хеш, заподозрит неладное. Вместо того чтобы скрыться, вы наоборот будете выделяться из толпы еще сильнее.
Унификация окружения. Еще один подход — сделать отпечаток не уникальным, а типовым. Например, запускать браузер внутри виртуальной машины или облачного сервиса, который выдает всем клиентам один и тот же профиль. Так поступают некоторые антифрод-системы: подозрительных юзеров запускают в изолированном «эмуляторе браузера», где их отпечаток обезличен. Однако для повседневного серфинга это вряд ли удобно.
Контролируемая маскировка. Одно из лучших решений — антидетект-браузеры, которые позволяют тонко настраивать параметры среды. В них вы сами решаете, какой Canvas или WebGL будет видеть сайт. Пример — Octo Browser. Он предлагает активную маскировку: добавляет шумы в Canvas и аудио, подделывает WebGL и другие отпечатки, выдавая конфигурации, близкие к «реальным» устройствам. Антидетект-браузеры стараются маскироваться под обычный браузер, а не просто рандомно менять все. Хороший антидетект-браузер добивается того, что каждый профиль выглядит правдоподобно уникальным, но не выделяется среди миллионов других.
Если вы хотите остаться в сети действительно анонимным, придется заходить в интернет через разные браузеры и устройства, регулярно обновлять ПО, отключать лишние плагины — и все это лишь немного повысит ваши шансы «затеряться в толпе». Альтернатива — профессиональные инструменты, такие как Octo Browser. Антидетект-браузер качественно подменит ваш фингерпринт и позволит сохранить настоящую приватность.
Canvas-фингерпринтинг
Canvas API в браузере позволяет рисовать графику средствами JavaScript. Именно эту возможность скрытно задействуют для получения отпечатка. Скрипт на странице создает невидимый <canvas>-элемент и выполняет серию команд рисования (например, отрисовывает текст, геометрические фигуры, добавляет тень), а затем считывает получившееся изображение как набор пикселей. У каждого устройства появляются едва заметные отличия в итоговом пиксельном рисунке — из-за особенностей его железа и ПО. Далее вычисляется хеш от этих данных, который и служит уникальным идентификатором пользователя.
Процесс можно описать по шагам:
Создание Canvas. Сайт, к которому пользователь обращается, динамически добавляет на страницу элемент
<canvas>(и не всегда в видимой области экрана, чаще за ее пределами).Рисование. С помощью JS-кода в этот Canvas выводятся тестовые графические данные. Обычно рисуют строку текста нестандартным шрифтом, а также разноцветные фигуры, линии, добавляют эффекты (градиент, тени).
Чтение пикселей. Завершив рисование, скрипт вызывает метод
toDataURL() (или аналогичный) у Canvas, чтобы получить битовое представление результата.Хеширование. Полученная строка (или массив пикселей) пропускается через функцию хеширования. Выходной хеш-код отправляется на сервер и служит отпечатком.
Например, упростим и нарисуем слово Fingerprint на Canvas и вычислим примитивный хеш:
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);
Этот код выведет 32-битное число (оно может быть отрицательным из-за переполнения), зависящее от того, как браузер отрисует надпись. На другом браузере (в нашем случае это браузер Opera) результат может быть другим (но может и совпасть, правда, такое бывает редко). На другом ПК результаты с большей степенью вероятности будут разными, хотя в некоторых случаях совпадения возможны.
На скриншотах ниже видно, что даже на одном ПК, но в разных браузерах хеш различается: в первом случае мы отрисовали слово Fingerprint на браузере Chrome, а во втором — на браузере Opera. Можете повторить это на своем ПК.


Что дает разницу? Не сам текст Fingerprint, так как даже на разных устройствах он выглядит одинаково для человеческого глаза. Различия появляются на уровне рендеринга шрифтов и графики. Сглаживание, алгоритмы растеризации кривых, хинтинг шрифтов (микрокоррекция формы глифов под пиксельную сетку) — разные операционные системы и браузеры все это обрабатывают по-разному. Добавьте сюда различия в видеокартах и драйверах, плюс каждый компьютер привносит свои мелкие искажения в итоговое изображение. На одном пикселе контур буквы чуть светлее, на другом темнее, где-то символ сгладился иначе. Эти микроскопические разницы и приводят к разным хешам, хотя визуально картинки выглядят идентично.
Сайты, в свою очередь, стараются усилить вариативность. Используют специальные текстовые строки, содержащие весь алфавит и символы, чтобы задействовать максимум обходных путей рендеринга. Как пример, использовать фразы, которые включают почти все буквы латиницы, как тут — Cwm fjordbank glyphs mute quiz. Поверх текста также могут рисовать разноцветный прямоугольник или градиент, добавлять тень — все это позволяет получить еще больше уникальных пиксельных деталей. Итогом работы Canvas-фингерпринта является та самая хеш-строка, о которой рассказывали выше, но в более усложненном варианте — e3d52382d0…. Этот хеш стабильно идентифицирует данное устройство, и при повторном визите этот же браузер выдаст этот же самый хеш, если его окружение не изменилось.
Таким образом, Canvas дает сайту мощный инструмент слежения. Без ведома пользователя сайт собирает аппаратно-зависимый «рисунок» его системы. Устройства с идентичной видеокартой и ПО могут выдавать совпадающие отпечатки Canvas. Однако встретить двух таких близнецов можно чрезвычайно редко. Обычно комбинация графических особенностей, шрифтов и софта уникальна, а для трекинга этого достаточно.
AudioContext-фингерпринтинг
Следующий метод — аудиоотпечаток, при котором браузерный Web Audio API служит источником уникального «звукового» идентификатора. Может звучать странно: ведь сайт не запрашивает микрофон и не воспроизводит слышимый звук. Тут все несколько хитрее. Скрипт генерирует и обрабатывает аудиосигнал внутри браузера, а затем извлекает числовые показатели, косвенно отражающие особенности системы. В итоге получается стабильный идентификатор по аналогии с canvas-хешем, только основанный на аудио.
Как это работает в общих чертах:
Аудиоконтекст. Скрипт создает скрытый аудиоконтекст (обычно
OfflineAudioContext) — виртуальную «звуковую карту», способную мгновенно обработать звук в памяти, не выводя его на колонки.Генерация сигнала. В контексте запускают генератор звука, например
OscillatorNode, задав фиксированную частоту (к примеру, 1 000 Гц для треугольного сигнала). Вместо аудиофайла осциллятор синтезирует чистый тон программно.Обработка эффектами. Чтобы усилить аппаратные различия, сигнал пропускают через звуковой эффект. Чаще всего используют компрессор (
DynamicsCompressorNode), как бы «сдавливающий» волну. Путем установки порога срабатывания, коэффициента сжатия, времени релиза и других параметров добиваются тонких изменений формы волны.Рендеринг и считывание. Виртуальный аудиоконтекст быстро просчитывает заданный отрезок звука. По завершении скрипт получает буфер с отсчетами аудиосигнала (набором чисел с плавающей запятой). Например, при частоте дискретизации 44 100 Гц и длительности ~ 113 мс получится около 5 000 значений.
Вычисление 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); })();
На скриншотах ниже видно, что аудиоотпечаток одного и того же ПК, но на разных браузерах не отличается (у нас он выдал такую цифру — 953.152941). Вы можете сравнить их и повторить код на своем ПК. Результат будет идентичный, но с вашим отпечатком, соответственно.


Таким образом, видно, что у каждой среднестатистической машины есть свое число — аудиохеш.
Разработчики браузеров давно осознали опасность такого метода. Компания Apple одной из первых внесла защиту: начиная с Safari 17, в приватном режиме API AudioContext специально добавляет небольшую случайность в генерируемый звук. Благодаря этому изменению один и тот же Safari может выдавать разные аудиохеши в разных сессиях. Остальные браузеры пока позволяют собрать аудиоотпечаток без особых помех (что и демонстрирует пример выше). В сочетании с Canvas- и WebGL-данными он серьезно повышает узнаваемость устройства.
WebGL-фингерпринтинг
HTML5 WebGL — это графический API для вывода 3D-картинки прямо в браузере (через <canvas> с контекстом WebGL). Он тоже стал инструментом для «снятия отпечатков» устройств. Если Canvas-фингерпринт выявляет различия в 2D-рисунках, то WebGL копает глубже — в сам графический процессор. Здесь возможности идентификации еще шире. По данным WebGL можно почти напрямую узнать модель видеокарты и драйвера, а по мелким деталям рендеринга — различить даже два компьютера с одинаковым GPU.
Типичный сценарий WebGL-фингерпринта такой:
Инициализация WebGL. Скрипт создает WebGL-контекст [например,
canvas.getContext("webgl2")]. Уже на этом шаге скрипт может получить некоторые детали окружения: названия графического адаптера (vendor/renderer), версию драйвера, поддерживаемые расширения и пр.Отрисовка сцены. Затем в скрытом Canvas выполняется рендеринг 3D-сцены или специальных примитивов. Чаще всего рисуют набор фигур с шейдерными эффектами, освещением и текстурами — так, чтобы задействовать разные части графического конвейера.
Сбор параметров. После рендеринга скрипт считывает полученное изображение (через
gl.readPixels), а также запрашивает ряд параметров WebGL: список поддерживаемых расширений, максимальные размеры текстур, точность шейдерной арифметики, строкиRENDERER/VENDORи т. д. Эти данные содержат своего рода «аппаратный слепок» графической системы.Генерация хеша. Собранный набор цифр и строк объединяется и хешируется (как вариант — алгоритмом SHA-256). Итоговый хеш и считается WebGL-отпечатком. Его отправляют на сервер, и сайт может использовать его для идентификации при текущем и будущих визитах.
Вот пример, как происходит генерация такого хеша:
(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); })();
Вот что получаем в итоге:

Какие именно различия выявляет WebGL? Во-первых, идентификаторы GPU. Например, одно дело встроенная Intel Graphics, другое — дискретная NVIDIA GeForce или AMD Radeon. У них разный набор возможностей, объем видеопамяти и драйверов, что отражается в параметрах контекста. Во-вторых, вариации внутри одной модели. Казалось бы одинаковые экземпляры видеокарт все равно имеют мелкие индивидуальные отличия по производительности и точности вычислений. WebGL может выудить и их: например, замеры времени выполнения шейдерных программ или тонкие артефакты в нарисованных пикселях выдадут разницу. Наконец, влияет и сам браузер. Разные графические движки (Blink, WebKit, Gecko) по-разному выполняют WebGL-вызовы и могут выдавать слегка отличающиеся результаты рендеринга. Ниже на скрине видно, что, когда мы генерим тот же самый отпечаток в браузере Опера, уже есть отличия.

WebGL-отпечаток часто используется совместно с Canvas и Audio для комплексного трекинга, но и без них довольно информативен и способен много рассказать о системе.
Как защититься от фингерпринтинга
Полностью исключить браузерный фингерпринтинг крайне трудно — слишком много каналов утечки. Тем не менее несколько мер могут снизить вашу уникальность:
Специальные режимы и браузеры. В Tor Browser по умолчанию включена жесткая защита: все пользователи имеют один и тот же набор характеристик, словно клоны. Canvas и WebGL там либо отключены, либо выдают усредненные значения. Минус — многие веб-сервисы ломаются. Браузер Brave в режиме агрессивной защиты также блокирует известные приемы слежения. Safari в приватном режиме добавляет шум в аудиоданные, пытаясь скрыть отпечаток.
Блокировка и спуфинг. Существуют расширения вроде CanvasBlocker, которые запрещают скриптам читать Canvas или подменяют рисунок на случайный. Есть и плагины для AudioContext. Но имейте в виду: пользователей подобных аддонов не так много (около 100 тысяч человек). Сайт, увидев абсолютно чистый Canvas или хаотично меняющийся noise-хеш, заподозрит неладное. Вместо того чтобы скрыться, вы наоборот будете выделяться из толпы еще сильнее.
Унификация окружения. Еще один подход — сделать отпечаток не уникальным, а типовым. Например, запускать браузер внутри виртуальной машины или облачного сервиса, который выдает всем клиентам один и тот же профиль. Так поступают некоторые антифрод-системы: подозрительных юзеров запускают в изолированном «эмуляторе браузера», где их отпечаток обезличен. Однако для повседневного серфинга это вряд ли удобно.
Контролируемая маскировка. Одно из лучших решений — антидетект-браузеры, которые позволяют тонко настраивать параметры среды. В них вы сами решаете, какой Canvas или WebGL будет видеть сайт. Пример — Octo Browser. Он предлагает активную маскировку: добавляет шумы в Canvas и аудио, подделывает WebGL и другие отпечатки, выдавая конфигурации, близкие к «реальным» устройствам. Антидетект-браузеры стараются маскироваться под обычный браузер, а не просто рандомно менять все. Хороший антидетект-браузер добивается того, что каждый профиль выглядит правдоподобно уникальным, но не выделяется среди миллионов других.
Если вы хотите остаться в сети действительно анонимным, придется заходить в интернет через разные браузеры и устройства, регулярно обновлять ПО, отключать лишние плагины — и все это лишь немного повысит ваши шансы «затеряться в толпе». Альтернатива — профессиональные инструменты, такие как Octo Browser. Антидетект-браузер качественно подменит ваш фингерпринт и позволит сохранить настоящую приватность.
Следите за последними новостями Octo Browser
Нажимая кнопку, вы соглашаетесь с нашей политикой конфиденциальности.
Следите за последними новостями Octo Browser
Нажимая кнопку, вы соглашаетесь с нашей политикой конфиденциальности.
Следите за последними новостями Octo Browser
Нажимая кнопку, вы соглашаетесь с нашей политикой конфиденциальности.
Похожие статьи
Похожие статьи
Похожие статьи

Присоединяйтесь к Octo Browser сейчас
Вы можете обращаться за помощью к нашим специалистам службы поддержки в чате в любое время.

Присоединяйтесь к Octo Browser сейчас
Вы можете обращаться за помощью к нашим специалистам службы поддержки в чате в любое время.
Присоединяйтесь к Octo Browser сейчас
Вы можете обращаться за помощью к нашим специалистам службы поддержки в чате в любое время.


