Creepjs学习--Canvas 2D 指纹采集:`src/canvas/index.ts` 在做什么?
本文是 CreepJS 源码学习笔记的一篇聚焦src/canvas/index.ts这个模块在指纹体系里扮演什么角色、关键函数各自干什么、主流程怎样一步步跑完。CreepJS 的目标之一是暴露反指纹扩展和隐私浏览器里的薄弱点。Canvas 2D 正是它重点检测的能力之一——既能采到高熵特征又最容易被插件注入噪声、伪造结果。读懂这个文件等于同时读懂怎么采以及怎么识破被采坏了的结果。一句话概括这个文件做两件事采集在浏览器里用 Canvas 2D API 画画、量文字、读像素拼出一组能代表当前设备/浏览器的数据指纹素材。展示把采集结果渲染成页面上的「Canvas 2d」信息卡片方便人眼查看和对比。核心入口是getCanvas2d()页面展示入口是canvasHTML()。二者由上层creep.ts与 WebGL 等其它模块一起调度。为什么 Canvas 能当“指纹”想象你让所有人按同一份说明书画画同样的文字「A」、同样的 emoji「」同样的随机渐变、圆弧、曲线说明书一样但每个人的“画笔”不同——显卡驱动、操作系统字体、浏览器渲染引擎、抗锯齿算法都不一样。画完后把图画读出来toDataURL/getImageData得到的像素往往会有细微差别。这些差别被哈希后就能当成一种设备特征。src/canvas/index.ts正是在做这件事同时还额外检查有没有人在偷偷改像素、伪装结果反指纹插件、隐私浏览器等。这和 CreepJS「检测并忽略 JS 篡改 / 指纹谎言模式」的整体思路是一致的。文件里有哪些关键函数可以按「工具 → 主流程 → 展示」来理解函数角色一句话paintCanvas()画画引擎用固定种子画一堆图形制造高区分度的画面getPixelMods()噪声探测器写像素再读回来看有没有被篡改getCanvas2d()主入口默认导出串起全部检测返回指纹数据canvasHTML()UI 渲染把结果画成网页上的信息面板下面分别说清楚。1.paintCanvas()—— “毕加索式”画画机灵感来自 Picasso-like canvas fingerprinting。它不是随便乱画而是用一个伪随机种子生成器createPicassoSeed保证同一种子 → 同一套坐标和颜色多轮循环默认 10 轮每轮大致做随机径向渐变填充随机阴影模糊随机选一种图形画出来圆弧、贝塞尔曲线、二次曲线非 WebKit 还会加椭圆需要时再画描边文字「A」为什么这样设计因为复杂图形会放大不同渲染管线的差异指纹更“稳”、区分度更高。学习 CreepJS 时可以把这里看成用可控随机性把硬件/引擎差异“画”出来。2.getPixelMods()—— 像素有没有被动手脚灵感来自 arkenfox 的 canvas noise 测试。流程很直观画布1随机填满 8×8 像素 ↓ 逐像素 getImageData 读出来 画布2用读到的颜色再画一遍再读回来 ↓ 对比两次颜色 若不一致 → 说明中间有人改了像素常见于指纹噪音注入返回值大致包括pixels有多少个像素被改过rgba改动集中在哪些通道r / g / b / a 的组合pixelImage把差异可视化成一张放大图方便页面展示如果发现像素被改后面主流程会把结果标记为lied可能被欺骗。对照 CreepJS 的测试列表这正是在对抗 CanvasBlocker、各类 canvas noise 类扩展时常用的手法。3.getCanvas2d()—— 整条采集流水线这是默认导出的异步函数也是被creep.ts调用的主入口。它返回的对象大致长这样{dataURI,// 综合绘画含文字描边的图片数据paintURI,// GPU 路径下的纯绘画paintCpuURI,// CPU 友好上下文下的纯绘画textURI,// 字母「A」emojiURI,// emoji「」mods,// getPixelMods() 的噪声检测结果textMetricsSystemSum,// 文字度量特征汇总emojiSet,// 度量结果互不相同的 emoji 集合liedTextMetrics,// 文字度量是否可疑lied,// 整体是否判定为被篡改/欺骗}注意CreepJS 页面上看到的 Canvas 一项往往对应的是「Image / Blob / Paint / Text / Emoji」等多种子信号这个返回值就是那些子信号在代码里的落点。4.canvasHTML()—— 把结果变成可读的 UI负责生成「Canvas 2d」那一块 HTML显示各项哈希、缩略图预览显示噪声比例、被改的 RGBA 通道色点显示 textMetrics 汇总和 emoji 集合若lied为真卡片会加上rejected样式提示结果不可信读源码时建议一边看getCanvas2d返回字段一边对照canvasHTML怎么展示印象会更深。做事流程getCanvas2d()一步步在干什么可以把整段逻辑想成一条装配线┌─────────────────────────────────────────────┐ │ 1. 计时 排队createTimer / queueEvent │ │ 2. 检查 API 是否已被“撒谎”钩子污染 │ │ 3. 创建 Canvas 上下文必要时用隐蔽窗口 │ │ 4. paintCanvas → 得到综合图 dataURI │ │ 5. getPixelMods → 检测像素噪声 │ │ 6. measureText(emoji) → 文字度量指纹 │ │ 7. 再画 paintURI / paintCpuURI │ │ 8. 单独画 textURI、emojiURI │ │ 9. 二次校验清空画布后读像素、低熵像素库对比│ │ 10. measureText 浮点噪声检测 │ │ 11. 打日志返回结果对象 │ └─────────────────────────────────────────────┘步骤详解① 启动计时用createTimer()计时并用queueEvent()把任务切成小段避免长时间阻塞主线程。② 谎言检测Lie Detection先看一批关键 API 是否已被项目的lieProps标记为被篡改例如HTMLCanvasElement.toDataURLHTMLCanvasElement.getContextCanvasRenderingContext2D.getImageData/fillText/strokeText/fontmeasureText以及各类TextMetrics字段String.fromCodePoint任一为真lied就会置位。这是 CreepJS「先识谎、再采信」的典型写法。③ 准备画布创建两个 2D 上下文普通context走常规偏 GPU路径contextCPU带desynchronizedwillReadFrequently偏 CPU 读取路径两者对比可以看出 GPU / CPU 渲染路径差异。非 Brave 且存在PHANTOM_DARKNESS时会改用隐蔽窗口的document减少被页面脚本干扰。④ 综合绘画 →dataURI调用paintCanvas开启文字描边尺寸 WebKit 50、其它 75再toDataURL()。这是最“花哨”的一张图。⑤ 像素噪声 →mods调用getPixelMods()看读写像素是否一致。⑥ Emoji / TextMetrics 指纹对一组EMOJIS逐个measureText记录包围盒、宽度等维度。度量完全相同的 emoji 视为“同一类”只保留维度不同的集合emojiSet再算出一个汇总值textMetricsSystemSum。这反映系统字体对 emoji 的度量差异是另一路指纹信号与 CreepJS 测试项里的 TextMetrics / Emoji 相关。⑦ 纯绘画两份同一套paintCanvas不画文字分别在普通上下文和 CPU 上下文跑一遍得到paintURIGPUpaintCpuURICPU⑧ 文字与 Emoji 单独出图fillText(A)→textURIfillText()→emojiURI方便单独对比字体渲染差异。⑨ 再验一次像素是否被改若mods.pixels有值或清空后的小区域仍读出非零数据 → 判定像素被改调用documentLie记录并设lied true再画一张极简 2×2 图把getImageData结果与 Blink / Gecko / WebKit 的已知正常像素串对比对不上则标记LowerEntropy.CANVAS并sendToTrash怀疑异常/低熵伪装⑩ TextMetrics 浮点噪声对空字符串measureText()若包围盒出现不该有的浮点数视为度量噪声注入标记textMetricsLie/lied。⑪ 收尾logTestResult记录耗时与是否通过返回上面的结果对象出错则captureError并返回空。它依赖了项目里的哪些“基础设施”读这个文件时你会看到不少从别处 import 的工具它们各自负责一块横切能力依赖用途lieProps/documentLie/PHANTOM_DARKNESS检测与记录 API 欺骗sendToTrash把可疑信号丢进“垃圾/异常”收集hashMini短哈希便于 UI 展示createTimer/queueEvent/logTestResult计时、让出主线程、记测试结果IS_WEBKIT/IS_BLINK/IS_GECKO/LIKE_BRAVE按引擎分支处理不稳定行为EMOJIS/CSS_FONT_FAMILY统一字体与 emoji 测试集Analysis/LowerEntropy全局分析状态如低熵 CanvasHTMLNote/modal被拦截时的提示文案、弹层详情captureError统一错误上报学 CreepJS 时建议把这些依赖当成「横切能力地图」Canvas 模块本身专注画画与读图谎言、垃圾桶、引擎分支则是全项目共用的骨架。一张心智图数据从哪来、到哪去用户浏览器 │ ▼ getCanvas2d() ← creep.ts 并行调用的一环 ├── paintCanvas() ──► dataURI / paintURI / paintCpuURI ├── fillText() ──► textURI / emojiURI ├── measureText() ──► emojiSet / textMetricsSystemSum └── getPixelMods() ──► mods噪声 lied 判定 │ ▼ 返回指纹对象再由上层 creep 汇总、哈希 │ ▼ canvasHTML(fp) ──► 页面上的 Canvas 2d 卡片读代码时可以抓住的三个重点多源信号而不是一张图综合图、纯绘画GPU/CPU、字母、emoji、文字度量、像素噪声——多路特征一起看更稳也更难简单伪造。采集与反欺骗绑在一起不光要“采到指纹”还要判断结果是不是被噪声脚本、隐私模式改过。lied/LowerEntropy.CANVAS就是在说“这数据可能不可信。”引擎差异被显式处理WebKit 画布尺寸更保守、椭圆绘制会跳过Blink / Gecko / WebKit 各自有已知像素样本库——说明作者在用真实浏览器行为校准而不是一套参数打天下。小结src/canvas/index.ts是 CreepJS 的Canvas 2D 指纹模块用paintCanvas制造可复现的复杂画面用getPixelMods和一系列度量/像素校验发现篡改用getCanvas2d输出结构化指纹数据用canvasHTML把结果展示给人看。如果你在系统学习 CreepJS建议从这个文件入手 Canvas 相关能力流程清晰API 都是标准 Canvas 2D对抗逻辑也写得很直白——先正常画再怀疑有没有人改画。延伸阅读源码src/canvas/index.ts调用处src/creep.ts与 WebGL 等其它模块一并采集官方演示CreepJS on GitHub PagesPicasso-like 思路antoinevastel/picasso-like-canvas-fingerprintingCanvas 噪声测试思路arkenfox TZP canvasnoise