Schlagwortarchiv: Personenbezogene Daten

Geräte identifizieren über Web Audio API / Audio-Fingerprint / Nutzer identifizieren über Audiosignal / AliExpress-Methode zur Geräteidentifikation

Intention

Dieser Artikel zeigt einen Beispielcode um über die Web Audio API den Fingerprint eines Gerätes zu identifzieren. Bitte beachten Sie, dass die Identifikation nicht mit den Richtlinien der DSGVO-konform ist, daher sollte sie nur zu lokalen Testzwecken genutzt werden.

Die Aufmerksamkeit auf diesen Sachverhalt lenkte der folgende Heise-Artikel auf mich, daher entschied ich mich hier näher damit zu befassen:

https://www.heise.de/news/AliExpress-trackt-Nutzer-via-unhoerbarem-Audio-Fingerprinting-11424127.html

Grundidee: Was wir messen

Wir nutzen die Web Audio API, um:

  • ein synthetisches Signal zu erzeugen (Oszillator),
  • es durch einen DynamicsCompressorNode zu schicken,
  • das Ergebnis in einem OfflineAudioContext zu rendern,
  • aus dem fertigen Audiopuffer einen Zahlwert zu berechnen, der vom Gerät abhängt.

Dieser Zahlwert ist der Audio-Fingerprint für das Gerät und kann genutzt werden um das Geräte eindeutig zu identifizieren.

Warum das pro Gerät anders ist

  • Floating‑Point‑Arithmetik unterscheidet sich je nach CPU/Instruction‑Set.
  • Browser‑Engine (Chromium, Gecko, WebKit) implementiert DSP leicht unterschiedlich.
  • Audio‑Stack des OS kann Einfluss haben, je nach Implementierung.

Das Ergebnis: derselbe Code → auf Rechner A ein anderer Wert als auf Rechner B, aber bei Rechner A stabil über viele Aufrufe.

Code

<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="utf-8" />
  <title>Audio-Fingerprint Test (OfflineAudioContext)</title>
  <style>
    body { font-family: system-ui, sans-serif; margin: 2rem; }
    button { padding: 0.5rem 1rem; font-size: 1rem; }
    #result { margin-top: 1rem; font-family: monospace; }
  </style>
</head>
<body>
  <h1>Audio-Fingerprint Test</h1>
  <p>
    Diese Seite erzeugt einen <strong>OfflineAudioContext</strong>, rendert ein festes Signal
    und berechnet daraus einen Fingerprint-Wert. Es wird <strong>kein Ton abgespielt</strong>
    und <strong>keine Daten übertragen</strong.
  </p>

  <button id="run">Fingerprint berechnen</button>
  <div id="result"></div>

  <script>
    async function getAudioFingerprint() {
      // 1 Kanal, 5000 Frames, 44100 Hz
      const sampleRate = 44100;
      const length = 5000;
      const offlineCtx = new OfflineAudioContext(1, length, sampleRate);

      // Oszillator als Testsignal
      const osc = offlineCtx.createOscillator();
      osc.type = 'triangle';
      osc.frequency.value = 10000; // 10 kHz

      // DynamicsCompressor verstärkt Floating-Point-Unterschiede
      const comp = offlineCtx.createDynamicsCompressor();
      comp.threshold.value = -24;
      comp.knee.value = 30;
      comp.ratio.value = 12;
      comp.attack.value = 0.003;
      comp.release.value = 0.25;

      osc.connect(comp);
      comp.connect(offlineCtx.destination);
      osc.start(0);

      // Offline rendern (kein Audio-Ausgang)
      const buffer = await offlineCtx.startRendering();
      const data = buffer.getChannelData(0);

      // Aus einem festen Bereich eine kompakte Zahl ableiten
      const start = 4500;
      const end = 5000;
      let sum = 0;
      let hash = 0;

      for (let i = start; i < end; i++) {
        const v = Math.abs(data[i]);
        sum += v;
        // einfache Hash-Mischung
        hash = (hash * 31 + Math.floor(v * 1e6)) >>> 0;
      }

      return {
        rawSum: sum,
        hash: hash.toString(16).padStart(8, '0')
      };
    }

    document.getElementById('run').addEventListener('click', async () => {
      const resultEl = document.getElementById('result');
      resultEl.textContent = 'Berechne...';

      try {
        const fp = await getAudioFingerprint();
        resultEl.innerHTML =
          '<p><strong>Roh-Summe:</strong> ' + fp.rawSum.toFixed(6) + '</p>' +
          '<p><strong>Hash (hex):</strong> ' + fp.hash + '</p>' +
          '<p>Auf demselben Gerät sollte dieser Wert relativ stabil sein, ' +
          'auf anderen Geräten/Browsers unterschiedlich.</p>';
      } catch (e) {
        resultEl.textContent = 'Fehler: ' + e;
      }
    });
  </script>
</body>
</html>