Device Fingerprinting для виявлення зловживань
Device fingerprinting — ідентифікація пристрою за сукупністю характеристик браузера та ОС без використання cookies. Дозволяє розпізнати шахрая після зміни IP, скидання cookies, навіть у режимі інкогніто. На відміну від cookies, не вимагає явної згоди для цілей безпеки за більшістю законодавств (не є рекламним трекингом).
Компоненти Fingerprint
Якість fingerprint визначається ентропією — кількістю інформації кожного атрибуту:
| Компонент | Ентропія (біти) | Опис |
|---|---|---|
| User-Agent | 10–14 | ОС + браузер + версія |
| Canvas fingerprint | 8–12 | GPU + шрифти + AA рендеринг |
| WebGL vendor/renderer | 8–10 | Модель GPU |
| Встановлені шрифти | 6–10 | Список через measureText |
| AudioContext fingerprint | 6–8 | DSP характеристики залізо |
| Розрізнення + devicePixelRatio | 4–6 | Монітор + масштаб |
| Timezone | 4–5 | IANA timezone |
| Мови | 3–4 | Список мов браузера |
| Platform | 2–3 | Windows/Mac/Linux |
| CPU cores, пам'ять | 3–4 | navigator.hardwareConcurrency |
Комбінація забезпечує 40–80 біт ентропії — теоретично достатньо для глобальної унікальності.
Збір на стороні клієнта (JavaScript)
class DeviceFingerprinter {
async collect() {
const [canvas, webgl, audio, fonts] = await Promise.all([
this.getCanvasFingerprint(),
this.getWebGLFingerprint(),
this.getAudioFingerprint(),
this.getInstalledFonts(),
])
return {
// Базові
userAgent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
languages: navigator.languages?.join(',') ?? '',
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
// Екран
screenResolution: `${screen.width}x${screen.height}`,
screenDepth: screen.colorDepth,
devicePixelRatio: window.devicePixelRatio,
windowSize: `${window.innerWidth}x${window.innerHeight}`,
// Залізо
cpuCores: navigator.hardwareConcurrency,
deviceMemory: navigator.deviceMemory,
// Fingerprints рендеринга
canvas,
webgl,
audio,
fonts: fonts.slice(0, 20).join(','), // топ-20 шрифтів
// API браузера
cookieEnabled: navigator.cookieEnabled,
doNotTrack: navigator.doNotTrack,
touchPoints: navigator.maxTouchPoints,
pdfViewer: navigator.pdfViewerEnabled,
}
}
async getCanvasFingerprint() {
const canvas = document.createElement('canvas')
canvas.width = 200
canvas.height = 50
const ctx = canvas.getContext('2d')
// Малюємо текст з ефектами — кожна GPU рендерить по-своєму
ctx.textBaseline = 'top'
ctx.font = '14px Arial'
ctx.fillStyle = '#f60'
ctx.fillRect(125, 1, 62, 20)
ctx.fillStyle = '#069'
ctx.fillText('FP Test 🦄 ', 2, 15)
ctx.fillStyle = 'rgba(102, 204, 0, 0.7)'
ctx.fillText('FP Test 🦄 ', 4, 17)
return canvas.toDataURL()
.split(',')[1]
.substring(0, 50) // беремо перші 50 символів — достатньо для ентропії
}
async getWebGLFingerprint() {
const canvas = document.createElement('canvas')
const gl = canvas.getContext('webgl') ||
canvas.getContext('experimental-webgl')
if (!gl) return 'no_webgl'
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info')
const vendor = debugInfo
? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL)
: gl.getParameter(gl.VENDOR)
const renderer = debugInfo
? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)
: gl.getParameter(gl.RENDERER)
return `${vendor}~${renderer}`
}
}







