Отпечаток canvas, разобранный по механике
Скрипт создаёт элемент canvas, который никогда не попадает на страницу, рисует в нём текст и пару фигур, затем считывает пиксели обратно и хеширует их. Этот хеш и есть отпечаток. На вашей машине ничего не сохраняется и разрешения не требуется, потому что рисование и чтение холста - обычные веб-API, доступные любой странице.
Как это устроено
Шаг рисования подобран так, чтобы максимизировать различия между машинами: конкретный шрифт конкретного размера, иногда эмодзи, иногда градиент или повёрнутая фигура, определённого цвета. Ничего случайного здесь нет. Так выбрано потому, что отрисовка текста - одна из наименее стандартизованных частей браузера.
Шаг чтения использует один из двух вызовов:
canvas.toDataURL()сериализует весь холст в строку PNG в base64. Страница хеширует строку напрямую либо сначала декодирует обратно в пиксели.ctx.getImageData(x, y, w, h)возвращает сыройUint8ClampedArrayиз байтов RGBA прямо из подложечной битовой карты, минуя кодирование PNG. Сайты часто вызывают оба и сравнивают, поскольку кодирование может слегка округлить значения пикселей, а расхождение между двумя способами - сигнал само по себе.
В любом случае источник один и тот же - та самая битовая карта, и именно она даёт энтропию.
Почему одна и та же команда рисования даёт разные пиксели на разных машинах
Инструкция «нарисуй этот текст этим размером» не определяет результат полностью. Между инструкцией и пикселем стоит цепочка машинно-зависимых решений: какой файл шрифта подставится под запрошенное семейство, как растеризатор текста в ОС хинтует и сглаживает края глифов (DirectWrite, CoreText и FreeType расходятся в мелочах), кто рисует контур - видеокарта или процессор, и как управляется цвет на выходе. Смените видеокарту, версию драйвера или ОС - и байты пикселей сдвинутся, иногда на один бит в горстке пикселей, чего достаточно, чтобы хеш поменялся целиком.
Ничто в этой цепочке не меняется между двумя вызовами в одной сессии на одной машине. Та же видеокарта, тот же драйвер, тот же файл шрифта, каждый раз. Поэтому хеш стабилен у вас и отличается у другого: он измеряет детерминированный выход одного конкретного конвейера отрисовки, а не что-то случайное.
Три способа, которыми подмена выдаёт себя
Полная блокировка чтения холста. Некоторые инструменты приватности заставляют getImageData бросать исключение или возвращать пустое изображение. Так почти никто не делает по умолчанию, поэтому заблокированное чтение холста само по себе редкий опознающий сигнал, а не отсутствие сигнала. Скрипт может выделить «canvas заблокирован» в отдельную маленькую группу и уже на этом основании считать её подозрительной.
Случайные значения при каждом вызове. Инструменты, добавляющие шум к каждому вызову toDataURL или getImageData, чтобы хеш каждый раз отличался, решают не ту задачу. Настоящее железо детерминировано, поэтому страница может вызвать функцию дважды за сессию и сравнить. Расхождение подтверждает, что инструмент правит вывод в реальном времени, и это находка посильнее, чем «этот хеш просто нечастый». Детекторы проводят ровно такое сравнение двух чтений.
Один хеш на множество профилей. Подставить фиксированное правдоподобное значение canvas и применить его ко всем создаваемым профилям - значит решить задачу случайности и создать новую: если скрипт видит один и тот же хеш в тысячах сессий, которые в остальном выглядят разными людьми, это кластер, а кластеры - именно то, что ищет корреляционный анализ. Цель изоляции, при которой каждый профиль выглядит своей машиной, проваливается одинаково, будь общее значение явно поддельным или тихо одинаковым.
Что выдерживает проверку
Значение canvas, которое приходит из настоящего пути отрисовки, подобающе различается между разными профилями и остаётся точно тем же при повторных чтениях внутри одного профиля, сессия за сессией. Это свойство того, как значение получено, а не фильтр, наложенный поверх: оно генерируется один раз на профиль и применяется в самом движке браузера, ниже слоя JavaScript, без скрипта-подмены над настоящим выводом, который страница могла бы поймать посреди вызова.
Когда правильный ответ - инструмент попроще
Если цель - личная приватность, а не ведение множества раздельных личностей, вам вообще не нужно ничего, что трогает вывод canvas в каждой сессии. Режим resist-fingerprinting в Firefox прячет чтение холста за запрос разрешения, бесплатно и без расширений. Tor Browser идёт дальше и нормализует вывод canvas так, что значительная доля его пользователей выдаёт одно и то же значение; это настоящая защита, потому что она работает через слияние с толпой, а не через выделение вас как ещё одной согласованной, но отличной машины. Ни то, ни другое не поможет, если вам нужно, чтобы десять аккаунтов выглядели десятью людьми, но это уже другая задача.
Проведите тест двух чтений сами
Откройте консоль браузера на любой странице и выполните это дважды подряд:
function canvasHash() {
const c = document.createElement('canvas')
const ctx = c.getContext('2d')
ctx.textBaseline = 'top'
ctx.font = '14px Arial'
ctx.fillText('canvas fingerprint test', 2, 2)
return ctx.getImageData(0, 0, c.width, c.height).data.slice(0, 40).join(',')
}
console.log(canvasHash())
console.log(canvasHash())
В настоящем немодифицированном браузере обе строки печатаются одинаково, каждый раз. Если вы проверяете расширение или инструмент против детекта и строки различаются, это различие и есть находка: вы поймали подмену в реальном времени, а она опознаёт сильнее любого значения, которое пытались спрятать.
CreepJS выполняет вариант такого сравнения автоматически и выносит несогласованность canvas в отдельную категорию. Более широкий набор свойств, которые обязаны сходиться друг с другом, разобран в чек-листе согласованности отпечатка и в общем обзоре отпечатков браузера.
Попробуйте на бесплатном тарифе.
Неограниченное число локальных профилей, без карты. Проверьте результат детекторами сами.