FingerprintJS

The backend guy asked if the front-end could generate a browser unique identifier UUID and send it to the backend for processing. I didn’t even think about it. I said no. I thought this would be a good topic for research, so I googled it and found FingerprintJS. It can generate browser-unique identifiers to distinguish different users, with the Pro version having a 99.5% recognition rate (whereas the open source version, described here, is only 60%).

FingerprintJS principle

The implementation of FingerprintJS is pretty straightforward. First, find out what distinguishes the different parts of the browser. The most common way to get detailed information about the browser is through navigator. In addition to using navigator, FingerprintJS uses the Canvas fingerprint, and other aspects of the font width:

// FingerprintJS refines the various parts
export const sources = {
  // Expected errors and default values must be handled inside the functions. Unexpected errors must be thrown.
  osCpu: getOsCpu,
  languages: getLanguages,
  colorDepth: getColorDepth,
  deviceMemory: getDeviceMemory,
  screenResolution: getScreenResolution,
  availableScreenResolution: getAvailableScreenResolution,
  hardwareConcurrency: getHardwareConcurrency,
  timezoneOffset: getTimezoneOffset,
  timezone: getTimezone,
  sessionStorage: getSessionStorage,
  localStorage: getLocalStorage,
  indexedDB: getIndexedDB,
  openDatabase: getOpenDatabase,
  cpuClass: getCpuClass,
  platform: getPlatform,
  plugins: getPlugins,
  canvas: getCanvasFingerprint,
  // adBlock: isAdblockUsed, // https://github.com/fingerprintjs/fingerprintjs/issues/405
  touchSupport: getTouchSupport,
  fonts: getFonts,
  audio: getAudioFingerprint,
  pluginsSupport: getPluginsSupport,
  productSub: getProductSub,
  emptyEvalLength: getEmptyEvalLength,
  errorFF: getErrorFF,
  vendor: getVendor,
  chrome: getChrome,
  cookiesEnabled: areCookiesEnabled,
}
Copy the code

FingerprintJS use parts as components respectively, and then use the loadBuiltinSources method to get all of the components, and then use componentsToCanonicalString traversal components return values, seriation joining together into a string, Finally, the x64Hash128 method is used to convert the string into a unique hash value.

The canvas fingerprint, font difference and the hash algorithm used are interesting.

Canvas prints

Canvas fingerprint refers to drawing a hidden image on the page through the Canvas interface. In different systems, the final image in the browser has pixel level difference, mainly because the operating system uses different Settings and algorithms to carry out anti-aliasing and sub-pixel rendering operations. Even with the same drawing operation, the CRC test for the resulting picture data is not the same. CRC refers to the last 32-bit captcha in base64 data returned using canvas.toDataURL.

Canvas fingerprints are not uncommon, in addition to audio fingerprints, and even WebGL and WebGL fingerprints. The principle of audio fingerprint is similar to that of canvas fingerprint, which is based on hardware or software differences. The former generates audio, while the latter generates images, and then calculates different hashes to identify them. Audio fingerprints can be generated in two ways:

  1. Generate audio information flow (triangle wave), perform FFT transformation on it, and calculate SHA value as fingerprint
  2. Generate audio information stream (sine wave), perform dynamic compression processing, calculate MD5 value

FingerprintJS also uses audio fingerprinting, which seems to be method 1, but if you’re interested, take a look at the code

The system font

FingerprintJS uses the getFonts() method to get supported fonts. The basic principles are as follows:

  1. Defining font copywritingconst testString = 'mmMwWLliI0O&1'; The font sizeconst textSize = '48px'; Basic fontFamilyconst baseFonts = ['monospace', 'sans-serif', 'serif']; And the list of fonts to be detectedfontList(Defines many font types)
  2. BaseFonts and fontList are traversed, span is generated, and fontFamily is set. Note that fontList traverses baseFonts and fontFamily is set to the default baseFonts font
 // creates a span and load the font to detect and a base font for fallback
const createSpanWithFonts = (fontToDetect: string, baseFont: string) = > {
  return createSpan(` '${fontToDetect}',${baseFont}`)}Copy the code
  1. Compare the width and height of fontList and baseFonts fonts under different Fontfamilies. If they are not equal, the font is supported. If they are equal, the system does not support fontList fonts and uses the default baseFonts font
// checks if a font is available
const isFontAvailable = (fontSpans: HTMLElement[]) = > {
  return baseFonts.some(
    (baseFont, baseFontIndex) = >fontSpans[baseFontIndex].offsetWidth ! == defaultWidth[baseFont] || fontSpans[baseFontIndex].offsetHeight ! == defaultHeight[baseFont], ) }Copy the code

murmurHash3

The hash algorithm used by x64Hash128 is murmurHash3. The code is more complex, interested can go pondering, here is only a brief introduction.

MurmurHash is an extensively tested and fast unencrypted hash function. It was created by Austin Appleby in 2008 and has several variations, taking its name from two basic operations, multiply and rotate(although the algorithm actually uses Shift and Xor instead of rotate).

MurmurHash3 can produce a 32-bit or 128-bit hash. Older versions of MurmurHash2 produce 32-bit or 64-bit values. The MurmurHash2A variant adds the Merkel-damgard construct so that it can be called step by step. MurmurHash64A is optimized for 64-bit processors and MurmurHash64B is optimized for 32-bit processors

To be continued