Fingerprinting timing de performance : signaux hardware
Comment la précision de Performance.now(), la concurrence matérielle et la mémoire de l'appareil deviennent des signaux de pistage, et comment les contrôler au niveau du moteur.
Vous préférez la doc produit maintenue ?
Cet article a une page équivalente dans le centre de documentation. Utilisez les docs pour le flux canonique, les flags à jour et la référence durable.
Introduction
L'API Performance est l'une des interfaces navigateur les plus fondamentales, conçue à l'origine pour aider les développeurs à mesurer les temps de chargement de page, diagnostiquer les goulots d'étranglement et optimiser l'expérience utilisateur. Des fonctions comme performance.now(), performance.mark() et performance.measure() donnent aux développeurs des horodatages haute résolution pour le benchmarking de l'exécution de code. Parallèlement à ces fonctions de timing, des propriétés comme navigator.hardwareConcurrency et navigator.deviceMemory rapportent le nombre de coeurs CPU et la RAM disponible de l'appareil.
Bien que ces API servent des objectifs légitimes, elles exposent aussi des caractéristiques matérielles spécifiques qui varient entre les appareils. Un ordinateur portable avec 8 coeurs CPU et 16 Go de RAM produit des résultats de timing et des rapports matériels mesurablemenent différents d'un ordinateur de bureau avec 16 coeurs et 32 Go. Ces différences restent stables entre les sessions de navigation, survivent à l'effacement des cookies et persistent en mode incognito, ce qui en fait des signaux fiables pour le suivi d'appareils individuels.
Impact sur la confidentialité
Les signaux de timing de performance représentent une forme particulièrement insidieuse de fingerprinting car ils sont profondément liés aux caractéristiques matérielles physiques que les utilisateurs ne peuvent pas facilement changer. Contrairement aux cookies ou au stockage local, que les utilisateurs peuvent effacer, les signaux de timing émergent de la vitesse fondamentale à laquelle un appareil exécute JavaScript, rend les graphiques et traite les données.
Des recherches d'institutions incluant Princeton et KU Leuven ont démontré que le fingerprinting basé sur le timing peut distinguer les appareils avec une haute précision. Une étude de 2019 a montré que la combinaison des mesures performance.now() avec les valeurs de concurrence matérielle pouvait correctement identifier les visiteurs récurrents plus de 80 % du temps, même lorsque les autres identifiants étaient effacés.
Le problème s'étend au-delà des signaux individuels. Lorsqu'un traqueur collecte navigator.hardwareConcurrency (coeurs CPU), navigator.deviceMemory (RAM) et les temps d'exécution de micro-benchmarks ensemble, l'empreinte combinée devient très distinctive. Un appareil avec 6 coeurs, 8 Go de mémoire et un profil de vitesse d'exécution JavaScript spécifique réduit significativement la fenêtre d'identification. Ces signaux ne nécessitent aucune permission utilisateur, ne génèrent aucune invite et sont invisibles pour la personne pistée.
Contexte technique
Pour comprendre pourquoi le timing de performance varie entre les appareils, il est utile d'examiner ce qui influence la vitesse d'exécution JavaScript et les API qui exposent les informations matérielles.
Performance.now() et le temps haute résolution
performance.now() retourne un horodatage haute résolution mesuré en millisecondes avec une précision à la microseconde. La résolution réelle dépend des mesures de sécurité du navigateur (de nombreux navigateurs réduisent la précision pour prévenir les attaques de type Spectre), mais même une précision réduite révèle des caractéristiques de timing liées à la vitesse d'horloge du CPU, la hiérarchie de cache et le pipeline d'instructions.
Quand un site web exécute un micro-benchmark, comme itérer une boucle 100 000 fois ou calculer des hashes SHA-256, le temps d'exécution est directement influencé par le matériel de l'appareil. Un CPU de bureau rapide complète le même travail plus vite qu'un processeur mobile, et cette différence est mesurable et reproductible.
Concurrence matérielle
navigator.hardwareConcurrency retourne le nombre de coeurs de processeur logiques disponibles pour le navigateur. Cette valeur reflète la configuration physique du CPU et ne change pas entre les sessions. Les valeurs courantes vont de 2 (mobile bas de gamme) à 32 ou plus (stations de travail haut de gamme). La distribution n'est pas uniforme : certains nombres de coeurs (4, 8, 12, 16) sont bien plus courants que d'autres, ce qui signifie que les valeurs inhabituelles deviennent de forts identifiants.
Mémoire de l'appareil
navigator.deviceMemory retourne une quantité approximative de RAM de l'appareil en gigaoctets, arrondie à la puissance de deux la plus proche (0.25, 0.5, 1, 2, 4, 8). Bien que l'arrondi réduise la précision, cette valeur contribue toujours à l'empreinte globale. Un appareil rapportant 8 Go de mémoire combiné avec 8 coeurs est une empreinte différente d'un rapportant 4 Go avec 4 coeurs.
Pourquoi ces valeurs comptent ensemble
Individuellement, chaque signal a une entropie limitée. Mais combinés, ils forment un profil matériel : une vitesse d'exécution spécifique, un nombre de coeurs spécifique et une classe de mémoire spécifique. Lorsqu'ils sont superposés avec d'autres signaux d'empreinte comme Canvas, WebGL et les polices, les caractéristiques de timing contribuent significativement à l'identification de l'appareil.
Approches de protection courantes et leurs limites
VPN et serveurs proxy
Les VPN changent votre adresse IP mais n'ont aucun effet sur le timing de performance. Toutes les mesures de timing et requêtes de propriétés matérielles se font localement dans le navigateur. Deux appareils derrière le même VPN rapportent toujours des profils matériels et des vitesses d'exécution entièrement différents.
Incognito et navigation privée
Les modes de navigation privée effacent les cookies et l'historique mais ne modifient pas navigator.hardwareConcurrency, navigator.deviceMemory ou la vitesse d'exécution du matériel sous-jacent. Votre empreinte de performance en incognito est identique à votre empreinte dans une fenêtre normale.
Extensions de navigateur
Les extensions qui tentent de modifier les propriétés matérielles font face à des limitations fondamentales. Elles peuvent remplacer les valeurs de propriétés JavaScript en injectant des scripts, mais cela crée des incohérences faciles à détecter :
- Si
navigator.hardwareConcurrencyrapporte 4 coeurs mais qu'un micro-benchmark Web Worker montre 8 threads parallèles s'exécutant simultanément, les valeurs sont en conflit. - Si
navigator.deviceMemoryrapporte 2 Go mais que le timingperformance.now()pour les opérations intensives en mémoire est cohérent avec 16 Go (pas de pauses de garbage collection), l'inadéquation est apparente. - Les remplacements injectés par extension peuvent être détectés en vérifiant les descripteurs de propriétés, les chaînes de prototype, ou en exécutant du code dans un contexte frais avant que les extensions ne s'exécutent.
Randomisation
Certains outils de confidentialité randomisent les valeurs de timing en ajoutant du bruit à performance.now(). Bien que cela empêche l'identification stable, décaler aléatoirement les horodatages casse la surveillance légitime de performance et crée son propre motif détectable. Une séquence d'horodatages avec un jitter artificiel a un aspect différent de la variation matérielle naturelle.
Approche de BotBrowser au niveau du moteur
BotBrowser adopte une approche fondamentalement différente de la protection du timing de performance. Au lieu d'intercepter les appels API ou d'ajouter du bruit après coup, BotBrowser contrôle les signaux de timing au niveau du moteur du navigateur pour produire des résultats cohérents en interne qui correspondent à un profil d'appareil complet.
Identité matérielle basée sur le profil
Lorsque vous chargez un profil d'empreinte, BotBrowser configure toutes les valeurs liées au matériel pour correspondre à l'appareil cible de ce profil :
chrome --bot-profile="/path/to/profile.enc" \
--user-data-dir="$(mktemp -d)"
Cela définit navigator.hardwareConcurrency et navigator.deviceMemory aux valeurs configurées du profil. Ce ne sont pas des remplacements JavaScript ; ils sont appliqués au niveau du moteur avant l'exécution de tout code de page. Les vérifications de descripteurs de propriétés, l'inspection de prototype et l'évaluation dans un contexte frais retournent toutes les mêmes valeurs car le moteur lui-même les rapporte.
Echelle de timing de performance
BotBrowser fournit le flag --bot-time-scale pour contrôler la vitesse d'exécution apparente des opérations JavaScript :
chrome --bot-profile="/path/to/profile.enc" \
--bot-time-scale=0.92
Le flag accepte une valeur a virgule flottante inferieure a 1.0 (plage typique : 0.80-0.99). Une valeur d'echelle de 0.92 compresse les intervalles de performance.now() de 8%, reduisant les signaux de biais temporel qui pourraient identifier l'appareil. La mise a l'echelle est appliquee uniformement a toutes les sources de timing, donc les micro-benchmarks, performance.mark() et performance.measure() produisent tous des resultats coherents en interne qui correspondent a la meme classe materielle.
Graines de timing de performance
Introduit en mars 2026, --bot-time-seed (ENT Tier2) fournit une protection de timing de performance plus granulaire. Alors que --bot-time-scale compresse les intervalles de timing pour reduire les signaux de biais, --bot-time-seed controle la distribution du timing entre les operations individuelles du navigateur.
chrome --bot-profile="/path/to/profile.enc" \
--bot-time-scale=1.0 \
--bot-time-seed=12345
--bot-time-seed=<integer> accepte une graine entiere de 1 a UINT32_MAX (0 desactive la fonctionnalite). Chaque graine produit un profil de performance unique et stable qui diversifie le timing sur de multiples operations du navigateur, garantissant que chaque instance possede une signature de timing distincte et coherente.
La graine couvre aussi la redistribution par session des valeurs de performance.getEntries(), performance.getEntriesByType("navigation") et performance.timing, rendant les motifs de timing de navigation uniques pour chaque graine.
C'est entièrement déterministe : la meme graine produit toujours la meme empreinte de timing. Combiné avec --bot-time-scale, vous obtenez a la fois un controle de vitesse au niveau macro et une diversité de timing au niveau micro.
Exemple Playwright :
const { chromium } = require('playwright-core');
(async () => {
const browser = await chromium.launch({
executablePath: '/path/to/botbrowser/chrome',
args: [
'--bot-profile=/path/to/profile.enc',
'--bot-time-scale=1.0',
'--bot-time-seed=12345',
],
headless: true,
});
const context = await browser.newContext({ viewport: null });
const page = await context.newPage();
await page.goto('https://example.com');
// La distribution de timing est unique pour la graine 12345
await browser.close();
})();
Exemple Puppeteer :
const puppeteer = require('puppeteer-core');
(async () => {
const browser = await puppeteer.launch({
executablePath: '/path/to/botbrowser/chrome',
args: [
'--bot-profile=/path/to/profile.enc',
'--bot-time-scale=1.0',
'--bot-time-seed=12345',
],
headless: true,
defaultViewport: null,
});
const page = await browser.newPage();
await page.goto('https://example.com');
await browser.close();
})();
Cohérence entre signaux
L'avantage clé du contrôle au niveau du moteur est la cohérence. Lorsque BotBrowser rapporte 4 coeurs CPU et 4 Go de mémoire via un profil, les caractéristiques de timing de l'exécution JavaScript s'alignent aussi avec cette classe matérielle. Les Web Workers s'exécutent avec un parallélisme cohérent avec le nombre de coeurs rapporté. Les motifs d'allocation mémoire correspondent à la classe de mémoire rapportée. Il n'y a pas de contradictions entre ce que le navigateur rapporte et comment il performe réellement.
Mode déterministe
Pour les tests, la recherche et les pipelines CI/CD, BotBrowser prend en charge un timing entièrement déterministe via le flag de seed de bruit :
chrome --bot-profile="/path/to/profile.enc" \
--bot-time-scale=1.0 \
--bot-noise-seed=42 \
--user-data-dir="$(mktemp -d)"
Le même profil, la même échelle de temps et le même seed de bruit produisent des empreintes de timing identiques entre les sessions, les machines hôtes et les systèmes d'exploitation.
Configuration et utilisation
Utilisation CLI de base
chrome --bot-profile="/path/to/profile.enc" \
--bot-time-scale=1.0 \
--user-data-dir="$(mktemp -d)"
Intégration Playwright
const { chromium } = require('playwright-core');
(async () => {
const browser = await chromium.launch({
executablePath: '/path/to/botbrowser/chrome',
args: [
'--bot-profile=/path/to/profile.enc',
'--bot-time-scale=1.0',
],
headless: true,
});
const context = await browser.newContext({ viewport: null });
const page = await context.newPage();
await page.goto('https://example.com');
// Hardware values and timing signals match the loaded profile
await browser.close();
})();
Intégration Puppeteer
const puppeteer = require('puppeteer-core');
(async () => {
const browser = await puppeteer.launch({
executablePath: '/path/to/botbrowser/chrome',
args: [
'--bot-profile=/path/to/profile.enc',
'--bot-time-scale=0.92',
'--bot-noise-seed=42',
],
headless: true,
defaultViewport: null,
});
const page = await browser.newPage();
await page.goto('https://example.com');
await browser.close();
})();
Configuration combinée
Pour une protection complète, combinez le contrôle du timing avec d'autres paramètres de profil :
chrome --bot-profile="/path/to/profile.enc" \
--bot-time-scale=1.0 \
--bot-noise-seed=42 \
--proxy-server="socks5://user:pass@proxy:1080" \
--bot-config-timezone="America/New_York" \
--bot-config-locale="en-US"
Vérification
Après avoir lancé BotBrowser avec un profil, vérifiez que les valeurs matérielles et le timing sont contrôlés :
// Check hardware properties
console.log('CPU Cores:', navigator.hardwareConcurrency);
console.log('Device Memory:', navigator.deviceMemory, 'GB');
// Measure timing consistency
async function measureTiming() {
const results = [];
for (let i = 0; i < 5; i++) {
const start = performance.now();
for (let j = 0; j < 100000; j++) Math.sqrt(j);
results.push(performance.now() - start);
}
return results;
}
const timings = await measureTiming();
console.log('Timing samples:', timings);
console.log('Variance:', Math.max(...timings) - Math.min(...timings));
Points à vérifier :
navigator.hardwareConcurrencycorrespond au nombre de coeurs configuré dans le profilnavigator.deviceMemorycorrespond à la mémoire configurée dans le profil- Les échantillons de timing montrent un comportement cohérent entre les exécutions avec le même profil et seed
- Les outils publics de test d'empreinte (CreepJS, BrowserLeaks) ne montrent aucune anomalie
Bonnes pratiques
-
Utilisez toujours un profil complet. Les valeurs matérielles, le timing et les autres signaux doivent tous s'aligner. Un profil garantit la cohérence sur chaque surface d'empreinte.
-
Utilisez
--bot-time-scaleintentionnellement. Definissez l'echelle pour reduire le biais temporel de la classe materielle cible du profil. Les valeurs typiques vont de 0.80 a 0.99. Les valeurs plus basses compressent les intervalles de timing de maniere plus agressive. -
Utilisez le mode déterministe pour CI/CD. Combinez
--bot-noise-seedavec--bot-time-scalepour des résultats reproductibles dans les pipelines de test automatisés. -
Faites correspondre la géographie du proxy à l'identité du profil. Un profil configuré pour un appareil basé aux Etats-Unis devrait utiliser un proxy américain. La cohérence du timing combinée avec une géolocalisation inadéquate affaiblit la cohérence globale de l'empreinte.
-
Evitez de mélanger les extensions de navigateur qui modifient le timing. BotBrowser gère toute la protection du timing nativement au niveau du moteur. Les extensions tierces qui modifient
performance.now()ou les propriétés matérielles entreront en conflit.
Questions fréquentes
La précision de performance.now() diffère-t-elle entre les navigateurs ?
Oui. Différents navigateurs implémentent différents niveaux de précision d'horodatage, en partie comme mesure d'atténuation contre les attaques de canal auxiliaire de type Spectre. Chrome, Firefox et Safari réduisent chacun la précision différemment. Le système de profils de BotBrowser tient compte des caractéristiques de précision attendues du navigateur cible.
Les sites web peuvent-ils détecter que les valeurs de timing sont contrôlées ?
Si le contrôle du timing est appliqué de manière incohérente (par exemple, modifier performance.now() mais pas Date.now() ou performance.mark()), les sites web peuvent détecter la divergence. BotBrowser applique la mise à l'échelle du timing uniformément sur toutes les sources de timing au niveau du moteur, empêchant les incohérences entre API.
Est-ce que --bot-time-scale affecte la performance réelle de la page ?
Non. Le flag contrôle comment les valeurs de timing sont rapportées, pas la rapidité réelle d'exécution de JavaScript. Les pages se chargent et s'exécutent à pleine vitesse ; seules les mesures rapportées au JavaScript reflètent le timing mis à l'échelle.
Comment la concurrence matérielle interagit-elle avec les Web Workers ?
BotBrowser garantit que la valeur rapportée de navigator.hardwareConcurrency est cohérente avec le nombre de Web Workers qui peuvent s'exécuter en parallèle. Le profil gère les deux valeurs ensemble.
Puis-je utiliser des échelles de temps différentes pour différents profils ?
Oui. Chaque instance de navigateur utilise sa propre valeur --bot-time-scale. Vous pouvez exécuter plusieurs instances simultanément avec des profils et des caractéristiques de timing différents.
Qu'en est-il du timing SharedArrayBuffer ?
SharedArrayBuffer combiné avec Atomics peut être utilisé comme timer haute résolution. Le contrôle du timing au niveau du moteur de BotBrowser s'applique aussi aux canaux de timing de mémoire partagée, maintenant la cohérence sur toutes les méthodes de mesure du timing.
Les empreintes de timing survivent-elles aux mises à jour du navigateur ?
Sur les navigateurs non protégés, oui. L'empreinte de timing est liée au matériel, pas à la version du navigateur. Les profils BotBrowser sont versionnés, donc vous pouvez maintenir des caractéristiques de timing cohérentes entre les mises à jour de profils.
Quelle est la différence entre --bot-time-seed et --bot-time-scale ?
Ils servent des objectifs complementaires. --bot-time-scale compresse les intervalles de performance.now() (accepte une valeur a virgule flottante inferieure a 1.0, plage typique 0.80-0.99) pour reduire les signaux de biais temporel. --bot-time-seed controle la distribution du timing sur les operations individuelles du navigateur, produisant un profil de timing par operation unique pour chaque valeur de graine. Vous pouvez utiliser les deux ensemble : --bot-time-scale definit la compression de timing au niveau macro, tandis que --bot-time-seed ajoute une diversite de timing au niveau micro dans ce profil.
Résumé
Les API de timing de performance exposent des caractéristiques matérielles qui servent de signaux de suivi stables. performance.now(), navigator.hardwareConcurrency et navigator.deviceMemory forment ensemble une identité matérielle qui persiste entre les sessions et survit aux mesures de confidentialité courantes. BotBrowser controle tous ces signaux au niveau du moteur du navigateur via son systeme de profils, --bot-time-scale pour la compression des intervalles de timing, et --bot-time-seed pour la diversite de timing par operation. Ensemble, ces flags garantissent des résultats cohérents en interne qui correspondent a l'identité de l'appareil cible. Pour une protection associée, consultez protection d'empreinte Canvas, controle d'empreinte WebGL et controle du taux de rafraichissement.
Articles Connexes
Faites passer BotBrowser de la recherche à la production
Utilisez ces guides pour comprendre le modèle, puis passez à la validation multi-plateforme, aux contextes isolés et au déploiement navigateur prêt pour l'échelle.