什么是阅读器指纹

阅读器指纹能够经过阅读器对网站可见的装备、设置信息,来跟踪 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 [yinzhicao.org/Tracking…]》中更是详细研讨了各个指标的信息熵和稳定性。

详解阅读器指纹
从该论文中能够看出,时区、屏幕分辨率和色深、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 接口描绘了元素的一个隐含像素数据的区域,能够由 ImageData() 办法结构,或许由canvas 在一起的 CanvasRenderingContext2D 目标的创立办法:createImageData() 和 getImageData()。

ImageData 目标存储着canvas目标真实的像素数据,它包括几个只读特点:

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

Uint8ClampedArray 类型的一位数组,包括着 RGBA 的整型数据,范围在 0255。它能够视作初始像素数据,每个像素用 4 个 1 bytes 值(按照 red、green、blue、alpha 的次序),每个色彩值用0255 中的数字代表。每个部分被分配到一个数组内的连续索引,左上角第一个像素的赤色部分,坐落数组索引的第 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 的各个函数来阻止网站获取各种信息,或回来一个假的数据,以此来维护咱们的隐私信息。