Compter les visiteurs sans cookies : le hash salé quotidien
Le hash salé quotidien étape par étape, avec du code à exécuter, son erreur dans les deux sens, et un tableau clair de ce qu’il empêche ou non.
Si l’on ne stocke rien dans le navigateur, comment savoir que deux pages vues viennent de la même personne ? On ne le sait pas, pas avec certitude. On fait une bonne supposition à partir de ce que chaque requête transporte déjà, et on s’arrange pour que cette supposition expire. Voici exactement comment Trailic procède, ce qu’il compte mal, et ce contre quoi il protège. L’essentiel vaut pour tout outil construit sur un hash visiteur salé.
La recette
Chaque requête donne au serveur deux informations utiles : l’adresse d’où elle vient et le user agent du navigateur. Telles quelles, elles feraient un identifiant durable, précisément ce qu’on veut éviter. Alors :
- On prend la date du jour, en UTC.
- On dérive le sel du jour d’un secret connu du seul serveur.
- On hache le sel, le site, l’adresse et le user agent. En mode anonyme, le mode par défaut, l’adresse est d’abord raccourcie : IPv4 en /24, IPv6 en /48.
- On garde le résultat comme visiteur du jour, et on laisse partir l’adresse.
Voici le code, tiré de src/infrastructure/crypto/visitor-hash.ts et réduit à ce qu’on peut lancer avec node visitor.mjs :
import { createHash, createHmac } from "node:crypto";
const dailySalt = (secret, day) =>
createHmac("sha256", secret).update(`visitor-salt:${day}`).digest("hex");
const visitorOf = ({ secret, siteId, day, address, userAgent }) =>
createHash("sha256")
.update(`${dailySalt(secret, day)}\n${siteId}\n${address}\n${userAgent}`)
.digest("hex")
.slice(0, 32); // 128 bits, stockés sous forme d'uuid
console.log(visitorOf({
secret: "example-secret-do-not-use",
siteId: "site-a",
day: "2026-11-06",
address: "203.0.113.7",
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
}));
Je l’ai lancé en changeant une entrée à la fois :
| Changement | Le résultat commence par |
|---|---|
| Aucun | b82b3467 |
| Le lendemain | 08ad149c |
| Un autre site | 23b1fdf1 |
| Une autre adresse | 328e90e4 |
| Chrome 142 au lieu de 141 | f1904028 |
Chaque changement donne une valeur sans rapport. Tout le principe est là : demain, la même personne est quelqu’un d’autre, et sur un autre site aussi. Le collecteur garde ensuite un pays, un type d’appareil, un navigateur, un système et le hash. Ni l’adresse ni le user agent ne sont conservés.
Une erreur dans les deux sens
Il sous-estime, parce que tous ceux qui partagent une adresse et un user agent comptent pour un seul visiteur. Un bureau de quarante personnes sur la même version de Chrome, derrière une seule adresse publique, compte pour un. Les opérateurs mobiles font à peu près pareil : le CGNAT, comme l’explique le CEPD, regroupe plusieurs abonnés derrière une même adresse publique. Les navigateurs accentuent le phénomène. La réduction du user agent de Chrome fige la version mineure à 0.0.0 et remplace les détails du système par des valeurs fixes : les chaînes varient moins qu’avant.
Il surestime, parce que tout changement d’entrée crée un nouveau visiteur :
- Un téléphone qui passe du Wi-Fi à la 4G : deux visiteurs.
- Un navigateur qui se met à jour dans la journée : deux visiteurs.
- Un ordinateur et un téléphone : deux visiteurs.
- Une visite à cheval sur minuit UTC : deux visiteurs. Chez nous, c’est 1 h du matin en hiver et 2 h en été. Mais c’est 19 h à New York en hiver, et ça compte si votre trafic vient de là-bas.
Voici l’effet sur une journée, avec des chiffres inventés pour montrer le calcul :
| Effet | |
|---|---|
| Vraies personnes ce jour-là | 1 000 |
| 120 d’entre elles partagent adresse et navigateur avec quelqu’un, et se réduisent à 50 | −70 |
| 100 d’entre elles changent de réseau dans la journée | +100 |
| 40 d’entre elles utilisent deux appareils | +40 |
| Compté | 1 070 |
Le total n’est faux que de 7 %, alors que 210 comptes se sont trompés en dessous, dans des sens opposés, et le chiffre ne vous en montre rien. Voilà pourquoi je le traite comme une bonne courbe de tendance. Aujourd’hui contre mardi dernier, avec la même méthode et sur le même site, ça veut dire quelque chose. Le chiffre absolu mérite moins de confiance.
Les périodes s’additionnent. Un visiteur étant unique par site et par jour, ceux d’une semaine sont la somme de ses jours. Quelqu’un qui vient trois jours compte trois fois, sans dédoublonnage possible, et c’est voulu. Les « visiteurs du mois » de Trailic sont donc des personnes-jours. Pour plusieurs sites, un espace de travail additionne de la même façon les visiteurs de ses sites.
Les collisions accidentelles ne sont pas le problème. Avec 128 bits et un milliard de visiteurs par jour, la probabilité que deux entrées différentes donnent le même résultat est d’environ 1,5 × 10⁻²¹ (n² ÷ 2¹²⁹). Les erreurs viennent des entrées partagées.
Ce qu’il empêche, et ce qu’il n’empêche pas
| Qui | Ce qu’il peut faire |
|---|---|
| Quelqu’un qui lit la base | Voir des hash, des chemins, des pays. Pour les visiteurs anonymes, il ne peut dire ni qui est venu, ni relier un jour au suivant ou un site à un autre. Les personnes identifiées y figurent sous l’identifiant que vous avez envoyé |
| Le navigateur d’un visiteur | Rien à lire : pas de cookie, et rien dans le stockage avant identify |
Quelqu’un qui a TRAILIC_SECRET et connaît l’adresse et le navigateur d’une personne | Calculer son hash pour n’importe quel jour, et la retrouver |
| Quelqu’un qui a le secret, sans personne en tête | Essayer les adresses. Il y a 2³² adresses IPv4 : à un million de hash par seconde, un user agent connu prend environ 72 minutes. L’IPv6 est bien trop vaste pour cela |
| Votre hébergeur | Voir les adresses dans ses journaux, comme pour toute requête web. Le hash n’y change rien |
| N’importe qui sur Internet | Envoyer de faux événements, car les clés de site sont publiques. Les contrôles de domaine et les limites compliquent la tâche sans l’empêcher |
| Deux personnes derrière une adresse et un navigateur | Être fusionnées et, dans la demi-heure avant identify, confondues |
Les troisième et quatrième lignes viennent d’un choix discutable. Dans leur documentation, vérifiée en novembre 2026, Plausible dit renouveler et supprimer son sel toutes les 24 heures. PostHog dit supprimer chaque sel une fois les événements du jour traités. Matomo dit jeter chaque jour sa graine aléatoire, sans moyen de la récupérer. Trailic, lui, ne stocke aucun sel : il le dérive du secret, pour que la collecte d’un événement n’ait jamais à lire une base, ce qui laisse la base dormir sur une offre serverless. Le prix : les jours passés restent calculables par qui détient le secret. Si vous faites tourner une instance, protégez ce secret comme le mot de passe de votre base. Le changer met fin à toutes les clés de site de tous vos projets, et rend tous les visiteurs nouveaux pour le reste de la journée.
Si vous pensez que supprimer les sels vaut une lecture en base, ouvrez une issue.
Un hash n’est pas une anonymisation
« Haché » ne veut pas dire « anonyme ». Le considérant 26 du RGPD traite les données pseudonymisées, attribuables grâce à des informations supplémentaires, comme concernant une personne identifiable. Le projet de lignes directrices du CEPD sur la pseudonymisation ajoute qu’empêcher l’attribution n’implique pas l’anonymat. L’avis du G29 sur les techniques d’anonymisation parle d’une mesure de sécurité utile, « mais non une méthode d’anonymisation ». Il cite le hachage par clé avec clé enregistrée, qui est exactement ce modèle : le responsable du traitement peut ré-exécuter la fonction, un attaquant sans la clé en pratique non. Supprimer la clé, comme le décrivent les trois outils ci-dessus, réduit encore le risque de corrélation. Des éditeurs qualifient leur hash d’anonyme. Comme l’exploitant de celui-ci peut le recalculer, je ne le ferais pas, et je le traiterais comme une donnée personnelle pseudonymisée tant qu’un juriste ne vous dit pas le contraire.
Côté régulateur, la grille d’auto-évaluation de la CNIL accepte un hash fondé sur les caractéristiques du terminal s’il intègre une composante propre au site et une composante temporelle : celui-ci a les deux. Sa page sur la proxyfication, qui porte sur les transferts hors de l’Union, demande aussi « un niveau de collision suffisant », et 128 bits n’en offrent pour ainsi dire aucun. Rien de tout cela ne vaut exemption : voyez la question du bandeau cookies.
Si vous construisez le vôtre
- Mettez le site dans le hash, sinon deux de vos sites peuvent comparer leurs visiteurs.
- Mettez le jour dans le hash, sinon vous avez construit un identifiant durable.
- Ne journalisez jamais l’adresse à côté du résultat. Un journal gardé un mois annule la rotation.
- Vérifiez ce que votre proxy fait des adresses. Vercel écrase
X-Forwarded-Forà la réception : une simple réécriture entre deux projets Vercel fait voir au collecteur l’adresse du proxy, et tous les visiteurs n’en font plus qu’un. Trailic a un relais pour cela, décrit dans faire tourner Trailic.
La page notre approche de la vie privée liste tout ce qui est collecté par ailleurs.
Où en est Trailic
Trailic est une première version. Il fonctionne de bout en bout sur une instance de test, avec de vrais navigateurs, un vrai assistant et cinq millions d’événements synthétiques, mais il n’a encore collecté aucun trafic réel : l’erreur décrite plus haut est donc raisonnée, pas mesurée. Son tampon Vercel Queues n’a tourné que contre une doublure de ce service, son modèle de données peut changer et il n’existe pas de chemin de mise à jour entre versions. Il n’y a pas de version hébergée, et la bibliothèque n’est pas encore sur npm : elle se trouve dans le dépôt, sous packages/trailic. N’en faites pas encore votre seule mesure d’audience. Lisez le code avant de croire cette description.
Sources
- Trailic,
visitor-hash.tset spécification du produit, section 3 (en anglais) - RGPD, règlement (UE) 2016/679, considérant 26
- G29, avis 05/2014 sur les techniques d’anonymisation
- CNIL, outil d’auto-évaluation, juillet 2025
- CNIL, mesure d’audience et transferts de données, 15 novembre 2023
- CEPD, lignes directrices 01/2025 sur la pseudonymisation, version soumise à consultation (en anglais)
- CEPD, lignes directrices 2/2023, avec la note sur le CGNAT (en anglais)
- Chromium, réduction du user agent (en anglais)
- Plausible, politique de données (en anglais)
- PostHog, le suivi sans cookies, source de la page (en anglais)
- Matomo, utilise-t-il une empreinte ? (en anglais)