随着互联网的普及和技术的迅猛发展,浏览器指纹技术作为一种有效的身份识别技术逐渐引起人们的关注。本文将深入探讨浏览器指纹技术的原理,以及其在安全验证、个性化定制等应用场景中的具体应用。

浏览器指纹技术的原理

浏览器指纹技术利用浏览器和操作系统等软硬件环境的特征信息(统称为指纹因子)来生成一个唯一的身份标识,常用的特征信息包括用户代理字符串、HTTP头部信息、插件和扩展、浏览器窗口尺寸、字体、语言设置等。

常见的指纹因子如下:

指纹因子 稳定性 独立性
userAgent 稳定
language 稳定 是(大多数时候)
colorDepth 稳定
deviceMemory 稳定
pixelRatio 稳定
hardwareConcurrency 稳定(但是 IE 浏览器不支持) 是(但是 IE 浏览器不支持)
screenResolution 稳定
availableScreenResolution 稳定
timezoneOffset 稳定
timezone 稳定
sessionStorage 稳定
localStorage 稳定
indexedDb 稳定
addBehavior 稳定
openDatabase 稳定
cpuClass 稳定
platform 稳定 是(大多数时候)
doNotTrack 稳定
plugins 待定
canvas 稳定(大多数时候) 否(已经过实际验证)
webgl 稳定(大多数时候) 否(已经过实际验证)
adBlock 稳定(取决于使用时间)
hasLiedLanguages 稳定
hasLiedResolution 稳定
hasLiedOs 稳定
hasLiedBrowser 稳定
touchSupport 稳定
fonts 稳定(大多数时候) 否(大多数时候)
audio 未知

表格来源:fingerprintjs

独立性通常指的是硬件信息,在不同的浏览器上会有相同的表现,虽然可以在不同的浏览器体现相同的特征,但是不同用户设备之间的重复率较高。

⚠️浏览器指纹并不能作为用户唯一身份的识别,不可以用在用户验证等方向。

下面我们挑几个比较典型的因子来说一下:

Canvas

Canvas是一个用于创建交互式网页应用程序的HTML5元素。它提供了一个可绘制图形的区域,并通过JavaScript来操作和绘制图形。Canvas可用于绘制图表、图像、动画和其他视觉效果。开发人员可以使用Canvas API来创建和操作图形,包括绘制形状、线条、文本和图像,以及应用颜色和渐变等效果。

即便使用相同的代码,在不同操作系统、不同浏览器上,产生的图片内容也不会完全相同。这是因为在图片格式上,不同浏览器使用了不同的图形处理引擎、不同的图片导出选项、不同的默认压缩级别等不同的。

我们可以用下面的代码进行测试

<body>
  <canvas id="canvas"></canvas>
  <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const txt = "Hello World";

    ctx.textBaseline = "top";
    ctx.font = "14px 'Arial'";
    ctx.textBaseline = "alphabetic";
    ctx.fillStyle = "#f60";
    ctx.fillRect(125, 1, 62, 20);
    ctx.fillStyle = "#069";
    ctx.fillText(txt, 2, 15);
    ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
    ctx.fillText(txt, 4, 17);

    const dataUrl = canvas.toDataURL();
    console.log(dataUrl);
  </script>
</body>

可以看到在我的Chrome和Safari上的输出是不同的

image-20230902215232636

可以使用这个工具来查看自己浏览器的canvas指纹。

Audio

AudioContext指纹也是基于硬件设备或者软件的差别,来产生不同的音频输出,然后计算得到不同的hash来作为标志。

使用AudioAPI不需要担心声音外放,只要在输出到播放设备前清除就不会播放声音,不了解可以看下这个

可以利用下面的代码进行音频指纹的计算

const AudioContext =
  window.OfflineAudioContext ||
  window.webkitOfflineAudioContext;

const context = new AudioContext(1, 5000, 44100)
const oscillator = context.createOscillator()
oscillator.type = "triangle"
oscillator.frequency.value = 1000

const compressor = context.createDynamicsCompressor()
compressor.threshold.value = -50
compressor.knee.value = 40
compressor.ratio.value = 12
compressor.reduction.value = 20
compressor.attack.value = 0
compressor.release.value = 0.2

oscillator.connect(compressor)
compressor.connect(context.destination);

oscillator.start()
context.oncomplete = event => {
  const samples = event.renderedBuffer.getChannelData(0)
  console.log(calculateHash(samples))
}
context.startRendering()

function calculateHash(samples) {
  let hash = 0
  for (let i = 0; i < samples.length; ++i) {
    hash += Math.abs(samples[i])
  }
  return hash
}

OfflineAudioContext 和 AudioContext 的主要区别在于OfflineAudioContext不会将音频呈现给设备硬件,会尽可能快地生成音频并将其保存到AudioBuffer中。

image-20230903110801167

另外,可以在 audiofingerprint 查看自己浏览器的音频指纹。

WebGL

同canvas一样,创建一个梯度对象,然后生成Base64字符串。

const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');

// 创建着色器并编译  
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, '');
gl.compileShader(vertexShader);

const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, '');
gl.compileShader(fragmentShader);

const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);

// 绘制梯度对象  
const triangleVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);

const triangleVertices = new Float32Array([0.0, 0.5, 0.5, -0.5, -0.5, -0.5]);
gl.bufferData(gl.ARRAY_BUFFER, triangleVertices, gl.STATIC_DRAW);

gl.drawArrays(gl.TRIANGLES, 0, 3);

let base64String = canvas.toDataURL();
console.log(base64String);

image-20230903115005631

一般来说可以附加一些其他信息,比如硬件信息(可以通过调试信息获取)、webgl扩展信息等

let base64String = canvas.toDataURL();

const extensions = gl.getSupportedExtensions();
for (let i = 0; i < extensions.length; i++) {
  base64String += extensions[i];
}

// 生成WebGL指纹字符串  
let fingerprintString = '';
const attributes = gl.getExtension('WEBGL_debug_renderer_info');
if (attributes) {
  const renderer = gl.getParameter(attributes.UNMASKED_RENDERER_WEBGL);
  const vendor = gl.getParameter(attributes.UNMASKED_VENDOR_WEBGL);
  fingerprintString += ' WebGL Renderer: ' + renderer;
  fingerprintString += ' WebGL Vendor: ' + vendor;
}

单独使用某种方法来计算指纹的重复率依然是不低的,所以需要综合多个属性计算出一个哈希值。好在已经有比较稳定的工具来帮我们完成这些计算了——fingerprintjs。

fingerprintjs

可以在fingerprintjs官方工具查看自己的浏览器指纹信息

使用之前安装一下依赖:npm install @fingerprintjs/fingerprintjs,或者使用CDN引入<script src="https://openfpcdn.io/fingerprintjs/v4/iife.min.js"></script>

import FingerprintJS from '@fingerprintjs/fingerprintjs'

const fp = FingerprintJS.load({
  monitoring: false // 关闭指纹上报
})

fp
  .then(f => f.get())
  .then(console.log)

image-20230903200602317

  • version:使用的fingerptintjs的版本号;
  • visitorId:生成的浏览器指纹;
  • components:参与生成指纹的指纹因子;
  • confidence:score置信度分数,0-1,数字越大访客标识符真实的可能性就越高,comment置信度分数的附加信息。

浏览器指纹技术的应用场景

  1. 安全验证:浏览器指纹技术可用于网站的安全验证,如防止恶意刷票、注册机器人、多账号注册等行为。通过对用户的浏览器指纹信息进行比对,网站可以判断是否为同一用户,从而进行合理的限制和验证。
  2. 反欺诈机制:在在线交易、金融支付等场景中,浏览器指纹技术可以用于反欺诈机制。通过监测用户的浏览器环境特征,包括设备、操作系统、网络环境等信息,可以及时发现异常行为并加强安全防护措施。
  3. 个性化定制:基于浏览器指纹技术,网站可以根据用户的浏览器环境特征进行个性化定制。比如,根据用户的偏好设置提供相关推荐内容,或者根据用户的设备和网络状况优化网页显示效果,提升用户体验。
  4. 个性化广告投放:浏览器指纹技术还广泛应用于广告投放,根据用户在浏览器的搜索行为提取用户喜好等特征,提高广告和内容的精准度和效果。

用户隐私

首先,浏览器指纹可能会直接损害用户的隐私权。虽然单独的指纹信息可能不足以识别个人身份,但当不同网站和服务共享这些指纹数据时,用户的隐私就面临泄露的风险。

其次,由于浏览器指纹信息的收集和传播通常是在用户不知情的情况下进行的,用户对这种信息的控制权非常有限。这使得用户的隐私权面临潜在的威胁,例如,用户的行踪和习惯可能被追踪和记录,进而影响到用户的隐私和个人数据的保护。

所以现在有很多浏览器提供了伪造指纹的能力,就是为了防止用户隐私被滥用。


前端小白