前端中指纹信息采集方式

主要涉及的点

  1. 采集音频指纹, 创建一段音频 对结果进行hash的到结果。
  2. 获得电池信息, navigator.getBattery(). 得到当前的充电信息,电量信息,多久充满等等的信息。
  3. 获得canvas信息, 绘制一段文本的信息,转化为base64获得hash值
  4. 用英文字体绘制canvas, 获得hash值
  5. 用中文字体绘制canvas,获得hash值
  6. 彩深度: (在不同的显示器上,有不同的颜色深度) screen.colorDepth
  7. CPU核数: navigator.hardwareConcurrency //表示当前的CPU核心数
  8. 获得浏览器支持的字体 方法就是逐个字体去实验一遍
  9. 同样的支持的英文字体是一样的。
  10. 检测浏览器是否为 爬虫驱动。 exactRiskBrowser(还可以单独的启动一个iframe进行再次的检测) window.navigator.webdriver
  11. 判断浏览器语言 navigator.language;
  12. 本地函数 window.addEventListener.toString()
  13. 屏幕颜色分辨率 screen.pixelDepth //【和上面颜色深度是一样的】
  14. 完成关于浏览器的平台 navigator.platform
  15. 判断浏览器的插件 Array.from(navigator.plugins).forEach(item => console.log(item.name));
  16. 判断屏幕的 宽度 和 高度 screen.width * screen.height
  17. 判断当前的时区
  18. 判断当前UA navigator.userAgent
  19. 判断声音驱动 和 上面的方法是一样的 voiceFingerPrint
  20. webgl的查询指纹

第一:采集音频指纹

var each = function(obj, iterator) {
    if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
        obj.forEach(iterator)
    } else if (obj.length === +obj.length) {
        for (var i = 0, l = obj.length; i < l; i++) {
            iterator(obj[i], i, obj)
        }
    } else {
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                iterator(obj[key], key, obj)
            }
        }
    }
}

var AudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext
var context = new AudioContext(1, 44100, 44100)
var oscillator = context.createOscillator()
oscillator.type = 'triangle'
oscillator.frequency.setValueAtTime(10000, context.currentTime)
var compressor = context.createDynamicsCompressor()
each([
  ['threshold', -50],
  ['knee', 40],
  ['ratio', 12],
  ['reduction', -20],
  ['attack', 0],
  ['release', 0.25]
], function (item) {
  if (compressor[item[0]] !== undefined && typeof compressor[item[0]].setValueAtTime === 'function') {
    compressor[item[0]].setValueAtTime(item[1], context.currentTime)
  }
})

oscillator.connect(compressor)
compressor.connect(context.destination)
oscillator.start(0)
context.startRendering()

var audioTimeoutId = setTimeout(function () {
  console.warn('Audio fingerprint timed out. Please report bug at https://github.com/Valve/fingerprintjs2 with your user agent: "' + navigator.userAgent + '".')
  context.oncomplete = function () { }
  context = null
  return done('audioTimeout')
}, 100)

context.oncomplete = function (event) {
  var fingerprint
  try {
    clearTimeout(audioTimeoutId)
    fingerprint = event.renderedBuffer.getChannelData(0)
      .slice(4500, 5000)
      .reduce(function (acc, val) { return acc + Math.abs(val) }, 0)
      .toString()
    oscillator.disconnect()
    compressor.disconnect()
  } catch (error) {
    console.log(error)
    return
  }
  console.log("fingerprint",fingerprint)
}

第二: 获得电池信息

navigator.getBattery().then(val => console.log(val));

第三:获得canvas信息

function createCanvasAndContext(contextId) {
    var canvas = document.createElement('canvas');
    var context = canvas.getContext(contextId);
    return {
        canvas: canvas,
        context: context,
    };
}

function resize(canvas, width, height) {
    canvas.width = width;
    canvas.height = height;
}

function res() {    
    var _a = createCanvasAndContext('2d'), canvas = _a.canvas, ctx = _a.context;
    resize(canvas, 122, 110);
    if (!ctx) {
        throw new Error('no 2d context');
    }
    var context = ctx;
    // Canvas blending
    // https://web.archive.org/web/20170826194121/http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/
    // http://jsfiddle.net/NDYV8/16/
    context.globalCompositeOperation = 'multiply';
    for (var _i = 0, _b = [
        ['#f2f', 40, 40],
        ['#2ff', 80, 40],
        ['#ff2', 60, 80],
    ]; _i < _b.length; _i++) {
        var _c = _b[_i], color = _c[0], x = _c[1], y = _c[2];
        context.fillStyle = color;
        context.beginPath();
        context.arc(x, y, 40, 0, Math.PI * 2, true);
        context.closePath();
        context.fill();
    }
    // Canvas winding
    // https://web.archive.org/web/20130913061632/http://blogs.adobe.com/webplatform/2013/01/30/winding-rules-in-canvas/
    // http://jsfiddle.net/NDYV8/19/
    context.fillStyle = '#f9c';
    context.arc(60, 60, 60, 0, Math.PI * 2, true);
    context.arc(60, 60, 20, 0, Math.PI * 2, true);
    context.fill('evenodd');
    return canvas.toDataURL();
}

console.log(res());

第四: 英文字体的绘制指纹

unction createCanvasAndContext(contextId) {
    var canvas = document.createElement('canvas');
    var context = canvas.getContext(contextId);
    return {
        canvas: canvas,
        context: context,
    };
}

function resize(canvas, width, height) {
    canvas.width = width;
    canvas.height = height;
}

function res() {    
    var _a = createCanvasAndContext('2d'), canvas = _a.canvas, ctx = _a.context;
    resize(canvas, 240, 100);
    if (!ctx) {
        throw new Error('no 2d context');
    }
    var context = ctx;
    context.textBaseline = 'alphabetic';
    context.fillStyle = '#fe3666';
    context.fillRect(100, 1, 62, 20);
    context.fillStyle = '#069';
    // It's important to use explicit built-in fonts in order to exclude the affect of font preferences
    // (there is a separate entropy source for them).
    context.font = '11pt "Times New Roman"';
    // The choice of emojis has a gigantic impact on rendering performance (especially in FF).
    // Some newer emojis cause it to slow down 50-200 times.
    // There must be no text to the right of the emoji, see https://github.com/fingerprintjs/fingerprintjs/issues/574
    // A bare emoji shouldn't be used because the canvas will change depending on the script encoding:
    // https://github.com/fingerprintjs/fingerprintjs/issues/66
    // Escape sequence shouldn't be used too because Terser will turn it into a bare unicode.
    var printedText = "kingsoft Cwm fjordbank gly " + String.fromCharCode(55357, 56835) ;
    context.fillText(printedText, 2, 15);
    context.fillStyle = 'rgba(102, 204, 0, 0.2)';
    context.font = '18pt Arial';
    context.fillText(printedText, 4, 45);
    return canvas.toDataURL();
}

console.log(res());

第五: 中文字体绘制方法

function createCanvasAndContext(contextId) {
    var canvas = document.createElement('canvas');
    var context = canvas.getContext(contextId);
    return {
        canvas: canvas,
        context: context,
    };
}

function resize(canvas, width, height) {
    canvas.width = width;
    canvas.height = height;
}

function res() {    
    /**
     * 中文字体渲染
     *
     * 这里使用 宋体 楷体 仿宋 黑体 分别输出
     *
     * 常用中文字体
     * @see https://zenozeng.github.io/fonts.css/
     * CSS中Font-Family的中文字体
     * @see https://shixiongfei.com/css-chinese-font-family.html
     * @see https://segmentfault.com/a/1190000006110417 如何优雅的选择字体
     */
    var _a = createCanvasAndContext('2d'), canvas = _a.canvas, ctx = _a.context;
    resize(canvas, 440, 100);
    if (!ctx) {
        throw new Error('no 2d context');
    }
    var context = ctx;
    context.textBaseline = 'alphabetic';
    context.fillStyle = '#fe3666';
    context.fillRect(30, 5, 62, 50);
    var printedText = String.fromCharCode(55357, 56835) + 
"\u5FEB\u624B\u300B\uFF0C\u62E5\u62B1\u6BCF\u4E00\u79CD
\u751F\u6D3B\uFF01\u3001\u3010\u6C38\u300D\u3002\u2026\u2026";
    context.fillStyle = '#069';
    context.font = '11pt "Songti SC", "Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif CN", STSong, 
"AR PL New Sung", "AR PL SungtiL GB", NSimSun, SimSun, "TW\\-Sung",
 "WenQuanYi Bitmap Song", "AR PL UMing CN", "AR PL UMing HK", "AR PL UMing TW", "AR PL UMing TW MBE", PMingLiU, MingLiU';
    context.fillText(printedText, 2, 25);
    context.fillStyle = 'rgba(102, 204, 0, 0.2)';
    context.font = '12pt "Liberation Serif", "Kaiti SC", STKaiti, "AR PL UKai CN", 
"AR PL UKai HK", "AR PL UKai TW", "AR PL UKai TW MBE", "AR PL KaitiM GB", KaiTi, KaiTi_GB2312, DFKai-SB, "TW\\-Kai"';
    context.fillText(printedText, 4, 50);
    context.fillStyle = 'rgba(220, 230, 80, 0.5)';
    context.font = '13pt STFangsong, FangSong, FangSong_GB2312, "CWTEX\\-F"';
    context.fillText(printedText, 6, 75);
    context.fillStyle = 'rgba(240, 80, 80, 0.5)';
    context.font = '14pt "Noto Sans", "Helvetica Neue", Helvetica, "Nimbus Sans L", Arial, "Liberation Sans",
 "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Source Han Sans SC",
 "Source Han Sans CN", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", "ST Heiti", SimHei, "WenQuanYi Zen Hei Sharp"';
    context.fillText(printedText, 7, 90);
    return canvas.toDataURL();
}

console.log(res());

第六: 色彩深度: (在不同的显示器上,有不同的颜色深度)

ColorDepth(色彩深度)
ColorDepth(色彩深度)是计算机图形学领域表示在位图或者视频缓冲区中存储1像素的颜色所用的位数,也称为 位/像素(bpp).
色彩深度越高,可用的颜色就越多.
screen.colorDepth

第七: CPU核数:

navigator.hardwareConcurrency
//表示当前的CPU核心数

第八: 获得浏览器支持的字体

var TEST_STRING = 'KsmmMwWLliI1O0&@';
var FONT_SIZE = '48px';
var BASE_FONT_EN = [
    'monospace',
    'sans-serif',
    'serif',
];
var FONT_LIST_EN = [
    'monospace',
    // This is android-specific font from "Roboto" family
    'sans-serif-thin',
    'ARNO PRO',
    'Agency FB',
    'Arabic Typesetting',
    'Arial Unicode MS',
    'AvantGarde Bk BT',
    'BankGothic Md BT',
    'Batang',
    'Bitstream Vera Sans Mono',
    'Calibri',
    'Century',
    'Century Gothic',
    'Clarendon',
    'EUROSTILE',
    'Franklin Gothic',
    'Futura Bk BT',
    'Futura Md BT',
    'GOTHAM',
    'Gill Sans',
    'HELV',
    'Haettenschweiler',
    'Helvetica Neue',
    'Humanst521 BT',
    'Leelawadee',
    'Letter Gothic',
    'Levenim MT',
    'Lucida Bright',
    'Lucida Sans',
    'Menlo',
    'MS Mincho',
    'MS Outlook',
    'MS Reference Specialty',
    'MS UI Gothic',
    'MT Extra',
    'MYRIAD PRO',
    'Marlett',
    'Meiryo UI',
    'Microsoft Uighur',
    'Minion Pro',
    'Monotype Corsiva',
    'PMingLiU',
    'Pristina',
    'SCRIPTINA',
    'Segoe UI Light',
    'Serifa',
    'Small Fonts',
    'Staccato222 BT',
    'TRAJAN PRO',
    'Univers CE 55 Medium',
    'Vrinda',
    'ZWAdobeF',
    'JetBrains Mono',
];
var BASE_FONT_ZH = [
    'STSong',
    'SimHei',
];
var FONT_LIST_ZH = [
    // 宋体
    "STSong",
    "Songti SC",
    "Noto Serif CJK SC",
    "Source Han Serif SC",
    "Source Han Serif CN",
    "STSong",
    "AR PL New Sung",
    "AR PL SungtiL GB",
    "NSimSun",
    "SimSun,",
    "TW\-Sung",
    "WenQuanYi Bitmap Song",
    "AR PL UMing CN",
    "AR PL UMing HK",
    "AR PL UMing TW",
    "AR PL UMing TW MBE",
    "PMingLiU",
    "MingLiU",
    // 楷体
    "Liberation Serif",
    "Kaiti SC",
    "STKaiti",
    "AR PL UKai CN",
    "AR PL UKai HK",
    "AR PL UKai TW",
    "AR PL UKai TW MBE",
    "AR PL KaitiM GB",
    "KaiTi",
    "KaiTi_GB2312",
    "DFKai-SB",
    "TW\\-Kai",
    // 仿宋
    "STFangsong",
    "FangSong",
    "FangSong_GB2312",
    "CWTEX\\-F",
    // 黑体
    "-apple-system",
    "Noto Sans",
    "Helvetica Neue",
    "Helvetica",
    "Nimbus Sans L",
    "Arial",
    "Liberation Sans",
    "PingFang SC",
    "Hiragino Sans GB",
    "Noto Sans CJK SC",
    "Source Han Sans SC",
    "Source Han Sans CN",
    "Microsoft YaHei",
    "Wenquanyi Micro Hei",
    "WenQuanYi Zen Hei",
    "ST Heiti",
    "SimHei",
    "WenQuanYi Zen Hei Sharp"
];

// 获得当前的可用信息
function getAvailableFont(fontList, baseFonts, text) {
    if (fontList === void 0) { fontList = FONT_LIST_EN; }
    if (baseFonts === void 0) { baseFonts = BASE_FONT_EN; }
    if (text === void 0) { text = TEST_STRING; }
    return runInIframe(function (iframe, iWin) {
        var doc = iWin.document;
        var workspace = doc.body;
        workspace.style.fontSize = FONT_SIZE;
        var $container = doc.createElement('div');
        function createSpanWithFont(fontFamily) {
            var span = document.createElement('span');
            var style = span.style;
            style.position = 'absolute';
            style.top = '0';
            style.left = '0';
            style.fontFamily = fontFamily;
            span.textContent = text;
            $container.appendChild(span);
            return span;
        }
        function createSpanWithFallbackFont(expectFont, baseFont) {
            return createSpanWithFont("'" + expectFont + "', " + baseFont);
        }
        var baseFontSpans = baseFonts.map(function (baseFont) { return createSpanWithFont(baseFont); });
        var fontSpanMap = {};
        fontList.forEach(function (font) {
            var spans = baseFonts.map(function (baseFont) 
{ return createSpanWithFallbackFont(font, baseFont); });
            fontSpanMap[font] = spans;
        });
        workspace.appendChild($container);
        function getSpanSize(span) {
            return span.offsetWidth + 'x' + span.offsetHeight;
        }
        var baseFontSizes = baseFontSpans.map(getSpanSize);
        function isFontAvailable(font) {
            try {
                var spans = fontSpanMap[font];
                var sizes = spans.map(getSpanSize);
                return sizes.some(function (fontSize, index) {
                    // console.log('font', font, baseFonts[index]);
                    // console.log('sizes font', fontSize, 'base', baseFontSizes[index]);
                    return baseFontSizes[index] !== fontSize;
                });
            }
            catch (e) {
                debugger;
                return false;
            }
        }
        return fontList.filter(function (font) { return isFontAvailable(font); });
    });
}

getAvailableFont();

第九: 同样的支持的英文字体是一样的。

cssFontFingerPrintZh
关于字体支持的检测方法如下:https://blog.csdn.net/u013291076/article/details/81232179

第十: 检测浏览器是否为爬虫驱动。

exactRiskBrowser(还可以单独的启动一个iframe进行再次的检测)

window.navigator.webdriver

第十一: 判断浏览器语音

navigator.language;

第十二: 本地函数

window.addEventListener.toString()

第十三: 屏幕颜色分辨率

 screen.pixelDepth //【和上面颜色深度是一样的】

第十四: 完成关于浏览器的平台

navigator.platform

第十五: 判断浏览器的插件

Array.from(navigator.plugins).forEach(item => console.log(item.name));

第十六: 判断屏幕的 宽度 和 高度

screen.width * screen.height

第十七: 判断当前的时区

function tz() {
    var now = new Date();
    var offset = -now.getTimezoneOffset();
    var hour = offset / 60;
    return "UTC" + (offset >= 0 ? '+' : '') + hour;
}

第十八: 判断当前UA

navigator.userAgent

第十九: 判断声音驱动和上面的方法是一样的

voiceFingerPrint

第二十: webgl的查询指纹

function getWebgl() {
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
    if (!ctx) {
        throw new Error('webgl not support');
    }
    var gl = ctx;
    var dbgRenderInfo = gl.getExtension('WEBGL_debug_renderer_info');
    var unmaskRenderer = '';
    var unmaskVendor = '';
    if (dbgRenderInfo !== null) {
        unmaskRenderer = gl.getParameter(dbgRenderInfo.UNMASKED_RENDERER_WEBGL) || '';
        unmaskVendor = gl.getParameter(dbgRenderInfo.UNMASKED_VENDOR_WEBGL) || '';
    }
    var data = {
        glRenderer: gl.getParameter(gl.RENDERER),
        glVendor: gl.getParameter(gl.VENDOR),
        unmaskRenderer: unmaskRenderer,
        unmaskVendor: unmaskVendor,
    };
    return JSON.stringify(data);
}
console.log(getWebgl())

第二十一: webgl字体的书写

function createCanvasAndContext(contextId) {
    var canvas = document.createElement('canvas');
    var context = canvas.getContext(contextId);
    return {
        canvas: canvas,
        context: context,
    };
}
function getWebgl() {
    var _a = createCanvasAndContext('webgl'), canvas = _a.canvas, ctx = _a.context;
    canvas.width = 300;
    canvas.height = 150;

    if (!ctx) {
        throw new Error('no webgl context');
    }
    var gl = ctx;
    gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
    // Set the clear color to darkish green.
    gl.clearColor(1, 1, 1, 1.0);
    // Clear the context with the newly set color. This is
    // the function call that actually does the drawing.
    gl.clear(gl.COLOR_BUFFER_BIT);
    // WebGL fingerprinting is a combination of techniques,
// found in MaxMind antifraud script & Augur fingerprinting.
    // First it draws a gradient object with shaders and convers the image to the Base64 string.
    // Then it enumerates all WebGL extensions & capabilities and appends them to the Base64 string, 
//resulting in a huge WebGL string, potentially very unique on each device
    // Since iOS supports webgl starting from version 8.1 and 8.1 runs on several graphics chips, 
//the results may be different across ios devices, but we need to verify it.
    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
    ]);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
    // @ts-ignore
    vertexPosBuffer.itemSize = 3;
    // @ts-ignore
    vertexPosBuffer.numItems = 3;
    var program = gl.createProgram();
    var vshader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vshader, vShaderTemplate);
    gl.compileShader(vshader);
    var fshader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fshader, fShaderTemplate);
    gl.compileShader(fshader);
    gl.attachShader(program, vshader);
    gl.attachShader(program, fshader);
    gl.linkProgram(program);
    gl.useProgram(program);
    // @ts-ignore
    program.vertexPosAttrib = gl.getAttribLocation(program, 'attrVertex');
    // @ts-ignore
    program.offsetUniform = gl.getUniformLocation(program, 'uniformOffset');
    // @ts-ignore
    gl.enableVertexAttribArray(program.vertexPosArray);
    // @ts-ignore
    gl.vertexAttribPointer(program.vertexPosAttrib, vertexPosBuffer.itemSize, gl.FLOAT, !1, 0, 0);
    // @ts-ignore
    gl.uniform2f(program.offsetUniform, 1, 1);
    // @ts-ignore
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexPosBuffer.numItems);
    return canvas.toDataURL();
}
console.log(getWebgl())

得到最终的列表形如下:

audioTriangle: "129d646e8a0c62f5c09bead9ff4a0f29d"
battery: "1"
canvasGraph: "1763cee6c8ffbf395642646c49b638c24"
canvasGraphFingerPrint: "1763cee6c8ffbf395642646c49b638c24"
canvasTextEn: "19479195286a1f5bb67dd9f911d4701ac"
canvasTextFingerPrintEn: "19479195286a1f5bb67dd9f911d4701ac"
canvasTextFingerPrintZh: "246868f9dec2d559647def3aff4dc3284"
canvasTextZh: "246868f9dec2d559647def3aff4dc3284"
colorDepth: "30"
cpuCoreCnt: "12"
cssFontFingerPrintEn: "1329bb845104882a7754a3bdf007ff6fc"
cssFontFingerPrintZh: "11997d7fc5c7f90fad6abcbadabebb249"
exactRiskBrowser: "false"
exactRiskBrowser2: "false"
fontListEn: "1329bb845104882a7754a3bdf007ff6fc"
fontListZh: "11997d7fc5c7f90fad6abcbadabebb249"
language: "zh-CN"
nativeFunc: "1973dcbb27a04c3a2ee240d9d2549e105"
pixelDepth: "30"
platform: "MacIntel"
plugins: "1a68ba429dd293b14e41a28b6535aa590"
resolution: "1792x1120"
riskBrowser: "false"
timeZone: "UTC+8"
ua: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"
voiceFingerPrint: "129d646e8a0c62f5c09bead9ff4a0f29d"
webDriver: "false"
webDriverDeep: "false"
webDriverDeep2: "false"
webglGPUFingerPrint: "1a0eb66aaeb2abbbad09c6a1be89db4d0"
webglGpu: "1a0eb66aaeb2abbbad09c6a1be89db4d0"
webglGraph: "1fc7eb8347261ca30e79a5bfe15bc64d7"
webglGraphFingerPrint: "1fc7eb8347261ca30e79a5bfe15bc64d7"

上面涉及的方法通过对设备的信息进行采集,从硬件角度判断是否为同一个人,尽可能避免使用cookie来判断带来的误判。

关于我
loading