昨天检测页上的 Canvas 哈希是一个值,今天变成了另一个,而你想不起自己改过什么。或者反过来:你装了个「保护」Canvas 的东西,现在每次刷新哈希都不一样,听起来像是成功了。其中一种情况正常,另一种是浏览器能举起的最显眼的旗子。这篇讲清哈希是怎么算出来的、哪些变化在预期之内、哪些不在,以及对单个浏览器和多个环境各自意味着什么。
Canvas 指纹是怎么算出来的
脚本创建一个看不见的 <canvas>,用两三种字体画一行字,画一个 emoji,画几个带透明度的重叠图形,可能再加一段渐变。然后用 toDataURL() 或 getImageData() 把像素读回来,做一个哈希。
这个哈希之所以有意思,是因为同样的绘制指令在不同机器上画出来的像素并不相同。文字由操作系统栅格化,Windows、macOS 和 Linux 的抗锯齿与微调各不相同;图形和混合经过浏览器的图形库,开了硬件加速的话还要经过显卡和驱动,各有各的舍入方式;emoji 来自系统的 emoji 字体。Mowery 和 Shacham 在 2012 年描述了这项技术,它一直有效,因为渲染差异是机器本身的属性,网站不需要征求任何许可。
正常的变化
| 变了什么 | 哈希为什么动 | 网站看到什么 |
|---|---|---|
| 浏览器升级 | 图形库改变了舍入或混合方式 | 从升级起的一个新的稳定值 |
| 显卡驱动更新 | 同样的绘制,着色器输出不同 | 一个新的稳定值 |
| 操作系统更新 | 文字栅格化或 emoji 字体变了 | 一个新的稳定值 |
| 笔记本切换了显卡 | 集成显卡和独立显卡渲染结果不同 | 两个稳定值,取决于当前在用哪块 |
| 关闭了硬件加速 | 软件渲染取代了显卡 | 一个新的稳定值 |
| 测试用到的字体被安装或删除 | 画面里的字形变了 | 一个新的稳定值 |
每一行的结尾都一样:一个新的稳定值。机器变了,指纹就变了,并且一直保持到下一次更新。网站预期到这一点。一年变几次、和 User-Agent 里可见的浏览器与系统更新同步的 Canvas,正是真实浏览器的样子。
不正常的变化
有一种变化是任何真实机器都不会产生的:同一个会话里,同一幅画连画两次,结果不同。这发生在有东西在脚本读取之前往像素里加噪声的时候,大多数 Canvas「保护」扩展和一些隐私浏览器就是这么做的。网站直接测试它:画两次再比较,或者在屏幕上和屏幕外各画一次再比较。不一致本身,比任何具体哈希值都是更强的信号。
第二个破绽是函数本身。toDataURL 和 getImageData 是浏览器的原生代码,脚本可以检查它们是否还是原生的。打了补丁往里加噪声的扩展,留下的是一个不再原生的函数,页面一行代码就能看出来。
因为驱动更新而变化的哈希,是一台真实机器在变化。每次刷新都变化的哈希,是浏览器在宣布自己的输出被篡改过,而这才是会被处置的那种信号。
网站实际上怎么用这个值
Canvas 很少单独使用。它是指纹标识的一个输入,更重要的是,它是好几项交叉检查的一边。Canvas 输出、WebGL 渲染器字符串和字体列表都取决于操作系统和显卡,所以它们必须彼此一致,也必须和 User-Agent 一致。用 macOS 的文字平滑渲染出来的 Canvas,却出现在一个声称是 Windows 的浏览器里,或者一个在所声称的机器上不可能存在的显卡字符串,都是自相矛盾。User-Agent 那篇会告诉你这类交叉检查有多少。
对单个账号来说,实际影响通常是零,直到数值出现不一致为止。对同一台机器上的多个账号来说,稳定的 Canvas 是反方向的问题:同一套硬件上的每个环境画出的是同一幅画,于是不管 Cookie 和 IP 怎样,Canvas 都把它们连在一起。
怎么检查
- 打开实验室,看 Canvas 一行。它会把测试画面画两次并检查两次结果是否一致,还会检查绘制函数是否原生。
- 刷新再比较。哈希应该完全相同。如果不同,页面和像素之间有东西在加噪声。
- 和 WebGL 行、字体行对照。三者应该描述同一个操作系统和同一块显卡。
- 浏览器或驱动更新之后,预期会有一个新哈希,再确认一次它是稳定的。
检测你自己的 Canvas 结果
实验室会把测试画两次、检查绘制函数是否原生,并把哈希和你的 WebGL 渲染器、字体列表并排显示。
该怎么做
如果你要的是单个浏览器的隐私: 让 Canvas 保持真实。一个稳定、真实的 Canvas 是数百万个相似值中的一个,只有和其他所有信号组合起来才有辨识度。每次绘制都加噪声,只会让你更罕见而不是更隐私,因为它把浏览器标记成了被改过的。
如果你需要在一台机器上有多个身份: 每个环境需要一个与其他环境不同、但每次绘制都与自己相同的 Canvas。噪声必须按环境设定种子,在屏幕上和屏幕外一致应用,并且由看起来原生的函数交付,否则交叉检查会抓到。随机噪声过不了第一关,不加噪声过不了第二关。
Mango Browser 怎么处理 Canvas
Mango 按环境应用带种子的噪声:每个环境有自己稳定的种子,所以它的 Canvas 在环境之间唯一,在每次绘制时相同,屏幕上和屏幕外都一样。两次绘制的检查能通过,因为两次结果一致;跨环境的检查能通过,因为环境之间不同。默认模式是噪声,也可以为需要显示机器真实 Canvas 的环境选择真实模式。
同样的思路贯穿整个渲染层:WebGL 的图像噪声按环境设定种子,渲染器字符串从该环境操作系统的真实显卡池中选取;音频采样加入确定性噪声,静音仍然是静音;字体列表来自该系统自己的字体池。每个环境画出的,是一台一致的机器的一幅一致的画。