浏览器指纹

464 阅读3分钟

浏览器指纹

浏览器指纹是指仅通过浏览器的各种信息,包括CPU核心数、显卡信息、系统字体、屏幕分辨率、浏览器插件(Flash、Silverlight、Java等)、浏览器扩展、浏览器设置、时区偏移量(浏览器GMT偏移量)和许多其他信息的特征组合成的一个字符串,就能近乎绝对定位一个用户,就算使用浏览器的隐私窗口模式,也无法避免。

  • navigator.userAgent - 获取用户代理字符串,它包含有关浏览器和操作系统的信息。
  • navigator.appVersion - 获取浏览器的版本信息。
  • navigator.platform - 获取运行浏览器的操作系统信息。
  • navigator.language - 获取浏览器的首选语言。
// 用户代理字符串
console.log(navigator.userAgent);
// 浏览器版本
console.log(navigator.appVersion);
// 操作系统信息
console.log(navigator.platform);
// 首选语言
console.log(navigator.language);

//综合一起生成浏览器指纹
console.log(hex_md5(navigator.userAgent + navigator.appVersion + navigator.platform + navigator.language));

浏览器高级指纹

基本指纹就像人的外貌、身高、体重、性别,很难从肉眼的角度去区分浏览器,那么高级指纹对于浏览器来说,就像DNA一般精准(不要害怕,也是有极限的)本文会介绍到目前广泛使用的三大高级指纹:canvas指纹/WebGL指纹/Audio音频指纹。

浏览器指纹的发展

  • 第一代是状态化的,主要集中在用户的 cookie 和 evercookie 上,需要用户登录才可以得到有效的信息。
  • 第二代才有了浏览器指纹的概念,通过不断增加浏览器的特征值从而让用户更具有区分度,例如 UA、浏览器插件信息等。
  • 第三代是已经将目光放在人身上了,通过收集用户的行为、习惯来为用户建立特征值甚至模型,可以实现真正的追踪技术。但是目前实现比较复杂,依然在探索中。

浏览器指纹技术——Canvas画布指纹

许多网站和跟踪软件使用HTML画布指纹。因为每个浏览器都会生成不同的模式。基本上,每种浏览器都会使用不同的图像处理引擎,不同的导出选项,不同的压缩级别,这样每台电脑绘制的图片都会略有不同,这些图案可以用来为用户设备分配特定数量的指纹,也可以用来识别不同的用户。

function getCanvasFingerprint() {
  var canvas = document.createElement('canvas');
  var context = canvas.getContext("2d");
  context.font = "18pt Arial";
  context.textBaseline = "top";
  context.fillText("Hello, user.", 2, 2);
  return canvas.toDataURL("image/jpeg");
}
var imgData = getCanvasFingerprint();
console.log(hex_md5(imgData));

浏览器指纹技术——webGL指纹

利用 WebGL 来识别设备指纹,一般可以用两种方式来做到指纹

    function getWebGLFingerprint() {
      var canvas = document.createElement('canvas');
      var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");

      // 设置清除颜色为黑色,不透明
      gl.clearColor(0.0, 0.0, 0.0, 1.0);
      // 清除颜色缓冲区
      gl.clear(gl.COLOR_BUFFER_BIT);

      // 创建顶点着色器
      var vsSource = `
        attribute vec4 aVertexPosition;
        void main(void) {
          gl_Position = aVertexPosition;
        }
    `;
      var vertexShader = gl.createShader(gl.VERTEX_SHADER);
      gl.shaderSource(vertexShader, vsSource);
      gl.compileShader(vertexShader);

      // 创建片段着色器
      var fsSource = `
        void main(void) {
            gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
        }
    `;
      var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
      gl.shaderSource(fragmentShader, fsSource);
      gl.compileShader(fragmentShader);

      // 创建着色器程序
      var shaderProgram = gl.createProgram();
      gl.attachShader(shaderProgram, vertexShader);
      gl.attachShader(shaderProgram, fragmentShader);
      gl.linkProgram(shaderProgram);
      gl.useProgram(shaderProgram);

      // 定义三角形的顶点
      var vertices = new Float32Array([
        0.0, 1.0, 0.0,
        -1.0, -1.0, 0.0,
        1.0, -1.0, 0.0
      ]);

      // 创建顶点缓冲区对象
      var vertexBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

      // 将缓冲区对象绑定到着色器变量
      var vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
      gl.enableVertexAttribArray(vertexPositionAttribute);
      gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);

      // 绘制三角形
      gl.drawArrays(gl.TRIANGLES, 0, 3);

      var res = canvas.toDataURL()
      return res
    }
    var webGLId = getWebGLFingerprint();
    console.log(hex_md5(webGLId));

浏览器指纹技术——audio指纹

Audio指纹(音频指纹)是音频内容的独特标识,可以将其看作是沿时间轴的数字摘要。

    let AudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContex
    let context = new AudioContext(1, 5000, 44100)
    let oscillator = context.createOscillator()
    oscillator.type = "triangle"
    oscillator.frequency.value = 1000
    let 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);

    async function sha256(message) {
      // 把字符串转换为Uint8Array
      const msgBuffer = new TextEncoder().encode(message);
      // 计算散列值
      const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
      // 转换为数组
      const hashArray = Array.from(new Uint8Array(hashBuffer));
      // 转换为16进制字符串
      const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
      return hashHex;
    }

    oscillator.start()
    context.oncomplete = event => {
      let samples = event.renderedBuffer.getChannelData(0)
      let samples_str = JSON.stringify(samples)
      sha256(samples_str).then(hash => console.log(hash));
    };
    context.startRendering()