关于前端:没登录网页也能个性化推荐一文详解浏览器指纹

18次阅读

共计 7508 个字符,预计需要花费 19 分钟才能阅读完成。

日常生活中,生物辨认技术曾经是少数智能手机的标配,大多数手机具备人脸识别、指纹识别等性能,目前的指纹识别技术曾经十分成熟。但咱们明天要聊的并不是生物辨认技术中的指纹识别,而是浏览器指纹。很多人对这项技术是又爱又恨,这到底是为什么呢?那咱们明天就来深刻理解下浏览器指纹。

什么是浏览器指纹

浏览器指纹能够通过浏览器对网站可见的配置、设置信息,来跟踪 Web 浏览器,它就像咱们人手上的指纹一样,具备个体辨识度,只不过现阶段浏览器指纹分别的是浏览器。

浏览器指纹辨识的信息能够是 UA、时区、地理位置或者是应用的语言等等,浏览器所开发的信息决定了浏览器指纹的准确性。

对于网站而言,拿到浏览器指纹并没有理论价值,真正有价值的是浏览器指纹对应的用户信息。作为网站站长,收集用户浏览器指纹并记录用户的操作,是一个有价值的行为,特地是针对没有用户身份的场景。

例如一个视频网站,未注册该网站的用户 A 喜爱浏览二次元的视频,通过浏览器指纹记录这个,那么下次能够间接向该浏览器推送二次元的视频。因为当初的上网设施大都是私人的,这样的推送形式很容易取得大部分用户的好感,从而使之成为网站的用户。

浏览器指纹的倒退

浏览器指纹技术的倒退跟大多数技术一样,并非欲速不达的,现有的几代浏览器指纹技术是这样的:

  • 第一代是状态化的,次要集中在用户的 cookie 和 evercookie 上,须要用户登录才能够失去无效的信息。
  • 第二代才有了浏览器指纹的概念,通过一直减少浏览器的特征值从而让用户更具备区分度,例如 UA、浏览器插件信息等
  • 第三代是曾经将眼光放在人身上了,通过收集用户的行为、习惯来为用户建设特征值甚至模型,能够实现真正的追踪技术。然而目前实现比较复杂,仍然在摸索中。

目前浏览器指纹的追踪技术能够算是进入 2.5 代,这么说是因为跨浏览器辨认指纹的问题仍没有解决。

指纹采集

信息熵(entropy)是接管的每条音讯中蕴含的信息的均匀量,信息熵越高,则能传输越多的信息,信息熵越低,则意味着传输的信息越少。

浏览器指纹是由许多浏览器的特色信息综合起来的,其中特征值的信息熵也不尽相同。因而,指纹也分为根本指纹和高级指纹。

根本指纹

根本指纹就是容易被发现和批改的局部,如 http 的 header。


{  "headers": {    
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",     
    "Accept-Encoding": "gzip, deflate, br",     
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",     
    "Host": "httpbin.org",     
    "Sec-Fetch-Mode": "navigate",     
    "Sec-Fetch-Site": "none",     
    "Sec-Fetch-User": "?1",     
    "Upgrade-Insecure-Requests": "1",     
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36"
  }}

除了 http 中拿到的指纹,还能够通过其余形式来取得浏览器的特色信息,例如:

  • 每个浏览器的 UA
  • 浏览器发送的 HTTP ACCEPT 标头
  • 浏览器中装置的浏览器扩大 / 插件,例如 Quicktime,Flash,Java 或 Acrobat,以及这些插件的版本
  • 计算机上安装的字体。
  • 浏览器是否执行 JavaScript 脚本
  • 浏览器是否能种下各种 cookie 和“super cookies”
  • 是否浏览器设置为“Do Not Track”
  • 零碎平台(例如 Win32、Linux x86)
  • 零碎语言(例如 cn、en-US)
  • 浏览器是否反对触摸屏

拿到这些值后能够进行一些运算,失去浏览器指纹具体的信息熵以及浏览器的 uuid。

这些信息就相似人类的体重、身高、肤色一样,有很大的反复概率,只能作为辅助辨认,所以咱们须要更准确的指纹来判断唯一性。

高级指纹

一般指纹是不够辨别独特的集体,这时就须要高级指纹,将范畴进一步放大,甚至生成一个举世无双的跨浏览器身份。

用于生产指纹的各个信息,有权重大小之分,信息熵大的将领有较大的权重。

在论文《Cross-Browser Fingerprinting via OS and Hardware Level Features [http://yinzhicao.org/TrackingFree/crossbrowsertracking_NDSS17.pdf]》中更是具体钻研了各个指标的信息熵和稳定性。

从该论文中能够看出,时区、屏幕分辨率和色深、Canvas、webGL 的信息熵在跨浏览器指纹上的权重是比拟大的。上面咱们就来看看这些高级指纹都蕴含了些什么信息。

Canvas 指纹

Canvas 是 HTML5 中的动静绘图标签,也能够用它生成图片或者解决图片。即使应用 Canvas 绘制雷同的元素,然而因为零碎的差异,字体渲染引擎不同,对抗锯齿、次像素渲染等算法也不同,Canvas 将同样的文字转成图片,失去的后果也是不同的。

实现代码大抵为:在画布上渲染一些文字,再用 toDataURL 转换进去,即使开启了隐衷模式一样能够拿到雷同的值。

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");
}
getCanvasFingerprint()

流程很简略,渲染文字,toDataURL 是将整个 Canvas 的内容导出,失去值。

WebGL 指纹

WebGL(Web 图形库)是一个 JavaScript API,可在任何兼容的 Web 浏览器中渲染高性能的交互式 3D 和 2D 图形,而无需应用插件。WebGL 通过引入一个与 OpenGL ES 2.0 十分统一的 API 来做到这一点,该 API 能够在 HTML5 元素中应用。这种一致性使 API 能够利用用户设施提供的硬件图形减速。网站能够利用 WebGL 来辨认设施指纹,个别能够用两种形式来做到指纹生产:

WebGL 报告——残缺的 WebGL 浏览器报告表是可获取、可被检测的。在一些状况下,它会被转换成为哈希值以便更快地进行剖析。

WebGL 图像 ——渲染和转换为哈希值的暗藏 3D 图像。因为最终后果取决于进行计算的硬件设施,因而此办法会为设施及其驱动程序的不同组合生成惟一值。这种形式为不同的设施组合和驱动程序生成了惟一值。

能够通过 Browserleaks test 检测网站来查看网站能够通过该 API 获取哪些信息。

产生 WebGL 指纹原理是首先须要用着色器(shaders)绘制一个梯度对象,并将这个图片转换为 Base64 字符串。而后枚举 WebGL 所有的拓展和性能,并将他们增加到 Base64 字符串上,从而产生一个微小的字符串,这个字符串在每台设施上可能是十分独特的。

例如 fingerprint2js 库的 WebGL 指纹生产方式:

// 局部代码 
gl = getWebglCanvas()    
if (!gl) {return null}    
var result = []    
var vShaderTemplate = 'attribute vec2 attrVertex;varying vec2 varyinTexCoordinate;uniform vec2 uniformOffset;void main(){varyinTexCoordinate=attrVertex+uniformOffset;gl_Position=vec4(attrVertex,0,1);}'
var fShaderTemplate = 'precision mediump float;varying vec2 varyinTexCoordinate;void main() {gl_FragColor=vec4(varyinTexCoordinate,0,1);}'
var vertexPosBuffer = gl.createBuffer()    
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer)    
var vertices = new Float32Array([-0.2, -0.9, 0, 0.4, -0.26, 0, 0, 0.732134444, 0])
// 创立并初始化了 Buffer 对象的数据存储区。gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW) 
vertexPosBuffer.itemSize = 3
vertexPosBuffer.numItems = 3
// 创立和初始化一个 WebGLProgram 对象。var program = gl.createProgram()
// 创立着色器对象
var vshader = gl.createShader(gl.VERTEX_SHADER)
// 下两行配置着色器 
gl.shaderSource(vshader, vShaderTemplate)  // 设置着色器代码  
gl.compileShader(vshader) // 编译一个着色器,以便被 WebGLProgram 对象所应用
    
var fshader = gl.createShader(gl.FRAGMENT_SHADER)   
gl.shaderSource(fshader, fShaderTemplate)    
gl.compileShader(fshader)    
// 增加事后定义好的顶点着色器和片段着色器  
gl.attachShader(program, vshader)
gl.attachShader(program, fshader) 
// 链接 WebGLProgram 对象   
gl.linkProgram(program)
// 定义好的 WebGLProgram 对象增加到以后的渲染状态  
gl.useProgram(program)    
program.vertexPosAttrib = gl.getAttribLocation(program, 'attrVertex')    
program.offsetUniform = gl.getUniformLocation(program, 'uniformOffset')                           gl.enableVertexAttribArray(program.vertexPosArray)    
gl.vertexAttribPointer(program.vertexPosAttrib, vertexPosBuffer.itemSize, gl.FLOAT, !1, 0, 0)    
gl.uniform2f(program.offsetUniform, 1, 1)
// 从向量数组中绘制图元  
gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexPosBuffer.numItems)    
try {result.push(gl.canvas.toDataURL())    
} catch (e) {/* .toDataURL may be absent or broken (blocked by extension) */
}

如何避免被生成“用户指纹”

文章结尾也提到了,很多人对浏览器这项技术是又爱又恨。因为一大堆网站应用各种技术来“生成”用户指纹,以便给网站用户带来更精准的举荐和合乎用户的浏览习惯。而用户在享受技术带来便当的同时,也未免会有“隐衷泄露”的烦躁和不安感。那么咱们如何避免被生成“用户指纹”呢?

混同 Canvas 指纹

咱们曾经理解了是如何获取 canvas 指纹的,那么应该如何防备被歹意获取呢?想混同 Canvas 指纹,只须要在 toDataURL 失去的后果上做手脚就能够。

toDataURL() 将整个 canvas 的内容导出,咱们须要将 Canvas 中的局部内容批改,这个时候能够通过 getImageData() 复制画布上指定矩形的像素数据,而后通过 putImageData()将图像数据放回,而后再应用 toDataURL() 导出的图片就有了差别。

CanvasRenderingContext2D.getImageData() 返回一个 ImageData 对象,用来形容 Canvas 区域隐含的像素数据。这个区域通过矩形示意,起始点为(sx, sy)、宽为 sw、高为 sh。

ImageData 接口形容了 <Canvas> 元素的一个隐含像素数据的区域,能够由 ImageData() 办法结构,或者由 canvas 在一起的 CanvasRenderingContext2D 对象的创立办法:createImageData() 和 getImageData()。

ImageData 对象存储着 canvas 对象实在的像素数据,它蕴含几个只读属性:

  • width 图片宽度,单位像素
  • height 图片高度,单位像素
  • data

Uint8ClampedArray 类型的一位数组,蕴含着 RGBA 的整型数据,范畴在 0~255。它能够视作初始像素数据,每个像素用 4 个 1 bytes 值(依照 red、green、blue、alpha 的程序),每个色彩值用 0~255 中的数字代表。每个局部被调配到一个数组内的间断索引,左上角第一个像素的红色局部,位于数组索引的第 0 位。像素从左到右从上到下被解决,遍历整个数组。

Unit8ClampedArray 蕴含 高度 宽度4 bytes 数据,索引值从 0 ~ (wh4)-1。

例如,读取图片中位于第 50 行,200 列的像素的蓝色局部,则:


const blueComponent = imageData[50*(imageData.width * 4) + 200*4 + 2]

上面是实现混同 Canvas 指纹的办法:



const toBlob = HTMLCanvasElement.prototype.toBlob;
const toDataURL = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.manipulate = function() {const {width, height} = this;
  // 拿到在进行 toDataURL 或者 toBlob 前的 canvas 所生成的 CanvasRenderingContext2D
  const context = this.getContext('2d'); 
  const shift = {'r': Math.floor(Math.random() * 10) - 5,
    'g': Math.floor(Math.random() * 10) - 5,
    'b': Math.floor(Math.random() * 10) - 5
  };
  const matt = context.getImageData(0, 0, width, height);
  // 对 getImageData 生成的 imageData(像素源数据)中的每一个像素的 r、g、b 局部的值进行进行随机扭转从而生成惟一的图像。for (let i = 0; i < height; i += Math.max(1, parseInt(height / 10))) {for (let j = 0; j < width; j += Math.max(1, parseInt(width / 10))) {const n = ((i * (width * 4)) + (j * 4));
      matt.data[n + 0] = matt.data[n + 0] + shift.r; // 加上随机扰动
      matt.data[n + 1] = matt.data[n + 1] + shift.g;
      matt.data[n + 2] = matt.data[n + 2] + shift.b;
    }
  }
  context.putImageData(matt, 0, 0); // 从新放回去
// 批改 prototype.toBlob
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {value: function() {if (script.dataset.active === 'true') {
      try {this.manipulate(); // 在每次 toBlob 前,先混同下 ImageData
      }
      catch(e) {console.warn('manipulation failed', e);
      }
    }
    return toBlob.apply(this, arguments);
  }
});
// 批改 prototype. toDataURL
Object.defineProperty(HTMLCanvasElement.prototype, 'toDataURL', {value: function() {if (script.dataset.active === 'true') {
      try {this.manipulate(); // 在每次 toDataURL 前,先混同下 ImageData
      }
      catch(e) {console.warn('manipulation failed', e);
      }
    }
    return toDataURL.apply(this, arguments);
  }
});

混同其余指纹

与后面混同 canvas 指纹混同的思路是统一的,都是更改被获取对象的原型的办法。

比方混同时区,就是更改 Date.prototype.getTimezoneOffset 的返回值。

混同分辨率则是更改 documentElement.clientHeight documentElement.clientWidth

混同 WebGL 则要更改 WebGLbufferData getParameter 办法等等。

当然,咱们也有一些简略的办法来避免被生成用户指纹。例如咱们能够通过浏览器的扩大插件(Canvas Blocker、WebGL Fingerprint Defender、Fingerprint Spoofing 等),在网页加载前执行一段 JS 代码,更改、重写 JS 的各个函数来阻止网站获取各种信息,或返回一个假的数据,以此来爱护咱们的隐衷信息。

举荐浏览

go-zero:开箱即用的微服务框架

辞别 DNS 劫持,一文读懂 DoH

正文完
 0