@anonympins/fingerprint 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -36,7 +36,9 @@ class SecurityProfiles
36
36
  'botScore' => 1.0, // Poids pour le score de bot explicite
37
37
  'cookieDroppingScore' => 0.9, // Pénalité élevée pour la suppression de cookies
38
38
  'threatIntelScore' => 0.4, // Poids pour le renseignement sur les menaces (ex: IP de proxy connu)
39
+ 'clientHintsInconsistencyScore' => 0.7, // Penalizes inconsistency between User-Agent and Client-Hints
39
40
  'clickVarianceScore' => 0.6, // Poids pour la variance des clics
41
+ 'subnetScore' => 0.5, // Pénalise les sous-réseaux IP avec une activité suspecte agrégée
40
42
  ],
41
43
  'thresholds' => ['low' => 20, 'medium' => 45, 'high' => 75, 'block' => 95],
42
44
  'patterns' => [
@@ -76,7 +78,9 @@ class SecurityProfiles
76
78
  'botScore' => 1.0,
77
79
  'cookieDroppingScore' => 1.0, // Pénalité maximale
78
80
  'threatIntelScore' => 0.7, // Poids élevé pour les menaces connues (Tor, etc.)
79
- 'clickVarianceScore' => 0.7, // Poids élevé pour la variance des clics
81
+ 'clientHintsInconsistencyScore' => 0.9, // Very high penalty in strict mode
82
+ 'clickVarianceScore' => 0.7, // High weight for click variance
83
+ 'subnetScore' => 0.7, // Poids plus élevé en mode strict
80
84
  ],
81
85
  'thresholds' => ['low' => 10, 'medium' => 35, 'high' => 65, 'block' => 90],
82
86
  'patterns' => [
@@ -115,8 +119,10 @@ class SecurityProfiles
115
119
  'tlsSpoofingScore' => 0.7,
116
120
  'botScore' => 0.5,
117
121
  'cookieDroppingScore' => 0.8, // Important pour les clients API qui doivent maintenir un état
118
- 'threatIntelScore' => 0.5, // Les API sont souvent ciblées par des IPs malveillantes
119
- 'clickVarianceScore' => 0.3, // Poids faible car non applicable aux API
122
+ 'threatIntelScore' => 0.5, // APIs are often targeted by malicious IPs
123
+ 'clientHintsInconsistencyScore' => 0.6, // Relevant signal for APIs
124
+ 'clickVarianceScore' => 0.3, // Low weight as not applicable to APIs
125
+ 'subnetScore' => 0.8, // Très important pour les API pour détecter les botnets
120
126
  ],
121
127
  'thresholds' => ['low' => 25, 'medium' => 50, 'high' => 80, 'block' => 95],
122
128
  'patterns' => [
@@ -157,8 +163,10 @@ class SecurityProfiles
157
163
  'tlsSpoofingScore' => 0.6,
158
164
  'botScore' => 0.8,
159
165
  'cookieDroppingScore' => 0.7, // Moins critique, mais toujours un signal
160
- 'threatIntelScore' => 0.3, // Moins prioritaire pour un blog
161
- 'clickVarianceScore' => 0.5, // Poids modéré pour la variance des clics
166
+ 'threatIntelScore' => 0.3, // Lower priority for a blog
167
+ 'clientHintsInconsistencyScore' => 0.5,
168
+ 'clickVarianceScore' => 0.5, // Moderate weight for click variance
169
+ 'subnetScore' => 0.4, // Utile contre le spam de commentaires coordonné
162
170
  ],
163
171
  'thresholds' => ['low' => 25, 'medium' => 55, 'high' => 80, 'block' => 95],
164
172
  'patterns' => [
@@ -198,8 +206,10 @@ class SecurityProfiles
198
206
  'tlsSpoofingScore' => 0.9,
199
207
  'botScore' => 1.0,
200
208
  'cookieDroppingScore' => 1.0, // Crucial pour la détection de bots e-commerce
201
- 'threatIntelScore' => 0.8, // Très important pour l'e-commerce (proxies de scalping)
202
- 'clickVarianceScore' => 0.8, // Poids très élevé pour la variance des clics
209
+ 'threatIntelScore' => 0.8, // Very important for e-commerce (scalping proxies)
210
+ 'clientHintsInconsistencyScore' => 0.9, // Very important for e-commerce
211
+ 'clickVarianceScore' => 0.8, // Very high weight for click variance
212
+ 'subnetScore' => 0.9, // Crucial contre les attaques de scalping distribuées
203
213
  ],
204
214
  'thresholds' => ['low' => 15, 'medium' => 40, 'high' => 70, 'block' => 90],
205
215
  'patterns' => [
@@ -1,185 +1,186 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint;
6
-
7
- /**
8
- * Classe pour construire une empreinte composite (Multi-Hash).
9
- * Format de sortie : "grp1:hash1|grp2:hash2|grp3:hash3"
10
- */
11
- class FingerprintBuilder
12
- {
13
- /**
14
- * @var array<string, string|int>
15
- */
16
- private array $components = [];
17
-
18
- /**
19
- * Ajoute un composant à l'empreinte.
20
- * La valeur est hachée pour l'anonymiser et réduire sa taille.
21
- *
22
- * @param string $group Le nom du groupe (ex: 'hw', 'screen', 'geo').
23
- * @param string|int|bool|null $value La valeur brute à hacher.
24
- * @return self
25
- */
26
- public function add(string $group, $value): self
27
- {
28
- if ($value === null || $value === '') {
29
- return $this;
30
- }
31
- // On hache la valeur individuellement.
32
- $this->components[$group] = self::cyrb53((string)$value);
33
- return $this;
34
- }
35
-
36
- /**
37
- * Ajoute un composant brut sans le hacher.
38
- * Utile pour les métriques qui doivent être lues telles quelles par le serveur.
39
- *
40
- * @param string $group Le nom du groupe.
41
- * @param string|int|null $value La valeur brute.
42
- * @return self
43
- */
44
- public function addRaw(string $group, $value): self
45
- {
46
- if ($value === null) {
47
- return $this;
48
- }
49
- $this->components[$group] = $value;
50
- return $this;
51
- }
52
-
53
- /**
54
- * Génère la chaîne de l'empreinte finale.
55
- * Les composants sont triés par clé pour garantir un ordre déterministe.
56
- *
57
- * @return string
58
- */
59
- public function __toString(): string
60
- {
61
- // ksort trie le tableau par clé.
62
- ksort($this->components);
63
-
64
- $parts = [];
65
- foreach ($this->components as $key => $hash) {
66
- $parts[] = "{$key}:{$hash}";
67
- }
68
-
69
- return implode('|', $parts);
70
- }
71
-
72
- /**
73
- * Compare deux empreintes et retourne un score de similarité (de 0 à 1).
74
- * Utilise une pondération pour donner plus d'importance aux invariants forts (Canvas, GPU, JA3).
75
- *
76
- * @param string|null $fpString1 Empreinte A.
77
- * @param string|null $fpString2 Empreinte B.
78
- * @return float
79
- */
80
- public static function compare(?string $fpString1, ?string $fpString2): float
81
- {
82
- if (empty($fpString1) || empty($fpString2)) {
83
- return 0.0;
84
- }
85
-
86
- $parse = function (string $str): array {
87
- $map = [];
88
- foreach (explode('|', $str) as $part) {
89
- $pair = explode(':', $part, 2);
90
- if (count($pair) === 2 && !empty($pair[0]) && !empty($pair[1])) {
91
- $map[$pair[0]] = $pair[1];
92
- }
93
- }
94
- return $map;
95
- };
96
-
97
- $map1 = $parse($fpString1);
98
- $map2 = $parse($fpString2);
99
-
100
- $volatileKeys = [
101
- 'ch_ua', 'ch_platform', 'ch_mobile', 'ch_model', 'ch_arch', 'ch_bitness',
102
- 'cookie_keys', 'upgrade', 'network', 'http_ver',
103
- 'x_forwarded_for', 'x_real_ip', 'cf_connecting_ip'
104
- ];
105
-
106
- $weights = [
107
- 'cvs' => 5.0, 'gpu' => 4.0, 'ja3' => 3.5, 'ja4' => 4.0,
108
- 'h2_settings' => 3.0, 'tcp_fp' => 2.5, 'ua' => 2.0,
109
- 'client_fp_hash' => 3.0, 'browser' => 1.5, 'os_version' => 1.5,
110
- 'device_type' => 1.0, 'hw' => 1.5, 'scr' => 1.0, 'os' => 0.8, 'geo' => 0.5,
111
- ];
112
-
113
- $weightedMatches = 0.0;
114
- $totalWeight = 0.0;
115
-
116
- $allKeys = array_unique(array_merge(array_keys($map1), array_keys($map2)));
117
-
118
- foreach ($allKeys as $key) {
119
- // On ignore les clés volatiles pour cette comparaison spécifique.
120
- if (in_array($key, $volatileKeys, true)) { // @phpstan-ignore-line
121
- continue;
122
- }
123
-
124
- // On ne compare que les clés qui ont un poids défini.
125
- $weight = $weights[$key] ?? null;
126
- if ($weight === null) continue;
127
-
128
- $totalWeight += $weight;
129
- if (isset($map1[$key]) && isset($map2[$key])) {
130
- if ($map1[$key] === $map2[$key]) {
131
- $weightedMatches += $weight;
132
- }
133
- }
134
- }
135
-
136
- return $totalWeight === 0.0 ? 0.0 : $weightedMatches / $totalWeight;
137
- }
138
-
139
- /**
140
- * Algorithme de hachage cyrb53 (rapide et faible taux de collision).
141
- * Porté depuis la version JavaScript.
142
- *
143
- * @param string $str La chaîne à hacher.
144
- * @param int $seed Une graine optionnelle.
145
- * @return string Le hash sous forme de chaîne de caractères.
146
- */
147
- public static function cyrb53(string $str, int $seed = 0): string
148
- {
149
- $h1 = 0xdeadbeef ^ $seed;
150
- $h2 = 0x41c6ce57 ^ $seed;
151
-
152
- for ($i = 0, $l = strlen($str); $i < $l; $i++) {
153
- $ch = ord($str[$i]);
154
- $h1 = self::imul($h1 ^ $ch, 2654435761);
155
- $h2 = self::imul($h2 ^ $ch, 1597334677);
156
- }
157
-
158
- $h1 = self::imul($h1 ^ ($h1 >> 16), 2246822507) ^ self::imul($h2 ^ ($h2 >> 13), 3266489909);
159
- $h2 = self::imul($h2 ^ ($h2 >> 16), 2246822507) ^ self::imul($h1 ^ ($h1 >> 13), 3266489909);
160
-
161
- // En PHP, les opérations sur les grands nombres peuvent être délicates.
162
- // On utilise bcmath pour une arithmétique de précision arbitraire, garantissant le même résultat que JS.
163
- $val_h2 = bcadd(bcmul((string)(2097151 & $h2), '4294967296'), (string)($h1 >= 0 ? $h1 : $h1 + 4294967296));
164
- return $val_h2;
165
- }
166
-
167
- /**
168
- * Émule la multiplication 32-bit `Math.imul` de JavaScript.
169
- *
170
- * @param int $a
171
- * @param int $b
172
- * @return int Un entier signé 32-bit.
173
- */
174
- private static function imul(int $a, int $b): int
175
- {
176
- // Utiliser GMP pour la multiplication pour éviter le dépassement en float sur les systèmes 64-bit.
177
- $gmp_a = gmp_init($a);
178
- $gmp_b = gmp_init($b);
179
- $gmp_result = gmp_mul($gmp_a, $gmp_b);
180
-
181
- // Tronquer le résultat à 32 bits et le convertir en entier signé.
182
- $truncated = gmp_and($gmp_result, '0xFFFFFFFF');
183
- return gmp_intval(gmp_sign($truncated) < 0 ? gmp_sub($truncated, '0x100000000') : $truncated);
184
- }
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint;
6
+
7
+ /**
8
+ * Classe pour construire une empreinte composite (Multi-Hash).
9
+ * Format de sortie : "grp1:hash1|grp2:hash2|grp3:hash3"
10
+ */
11
+ class FingerprintBuilder
12
+ {
13
+ /**
14
+ * @var array<string, string|int>
15
+ */
16
+ private array $components = [];
17
+
18
+ /**
19
+ * Ajoute un composant à l'empreinte.
20
+ * La valeur est hachée pour l'anonymiser et réduire sa taille.
21
+ *
22
+ * @param string $group Le nom du groupe (ex: 'hw', 'screen', 'geo').
23
+ * @param string|int|bool|null $value La valeur brute à hacher.
24
+ * @return self
25
+ */
26
+ public function add(string $group, $value): self
27
+ {
28
+ if ($value === null || $value === '') {
29
+ return $this;
30
+ }
31
+ // On hache la valeur individuellement.
32
+ $this->components[$group] = self::cyrb53((string)$value);
33
+ return $this;
34
+ }
35
+
36
+ /**
37
+ * Ajoute un composant brut sans le hacher.
38
+ * Utile pour les métriques qui doivent être lues telles quelles par le serveur.
39
+ *
40
+ * @param string $group Le nom du groupe.
41
+ * @param string|int|null $value La valeur brute.
42
+ * @return self
43
+ */
44
+ public function addRaw(string $group, $value): self
45
+ {
46
+ if ($value === null) {
47
+ return $this;
48
+ }
49
+ $this->components[$group] = $value;
50
+ return $this;
51
+ }
52
+
53
+ /**
54
+ * Génère la chaîne de l'empreinte finale.
55
+ * Les composants sont triés par clé pour garantir un ordre déterministe.
56
+ *
57
+ * @return string
58
+ */
59
+ public function __toString(): string
60
+ {
61
+ // ksort trie le tableau par clé.
62
+ ksort($this->components);
63
+
64
+ $parts = [];
65
+ foreach ($this->components as $key => $hash) {
66
+ $parts[] = "{$key}:{$hash}";
67
+ }
68
+
69
+ return implode('|', $parts);
70
+ }
71
+
72
+ /**
73
+ * Compare deux empreintes et retourne un score de similarité (de 0 à 1).
74
+ * Utilise une pondération pour donner plus d'importance aux invariants forts (Canvas, GPU, JA3).
75
+ *
76
+ * @param string|null $fpString1 Empreinte A.
77
+ * @param string|null $fpString2 Empreinte B.
78
+ * @return float
79
+ */
80
+ public static function compare(?string $fpString1, ?string $fpString2): float
81
+ {
82
+ if (empty($fpString1) || empty($fpString2)) {
83
+ return 0.0;
84
+ }
85
+
86
+ $parse = function (string $str): array {
87
+ $map = [];
88
+ foreach (explode('|', $str) as $part) {
89
+ $pair = explode(':', $part, 2);
90
+ if (count($pair) === 2 && !empty($pair[0]) && !empty($pair[1])) {
91
+ $map[$pair[0]] = $pair[1];
92
+ }
93
+ }
94
+ return $map;
95
+ };
96
+
97
+ $map1 = $parse($fpString1);
98
+ $map2 = $parse($fpString2);
99
+
100
+ $volatileKeys = [
101
+ 'ch_ua', 'ch_platform', 'ch_mobile', 'ch_model', 'ch_arch', 'ch_bitness',
102
+ 'cookie_keys', 'upgrade', 'network', 'http_ver',
103
+ 'x_forwarded_for', 'x_real_ip', 'cf_connecting_ip'
104
+ ];
105
+
106
+ $weights = [
107
+ 'cvs' => 5.0, 'gpu' => 4.0, 'ja3' => 3.5, 'ja4' => 4.0, 'ja4s' => 4.0, 'ja4h' => 3.8,
108
+ 'h2_settings' => 3.0, 'tcp_fp' => 2.5, 'ua' => 2.0,
109
+ 'client_fp_hash' => 3.0, 'browser' => 1.5, 'os_version' => 1.5,
110
+ 'device_type' => 1.0, 'hw' => 1.5, 'scr' => 1.0, 'os' => 0.8, 'geo' => 0.5,
111
+ ];
112
+
113
+ $weightedMatches = 0.0;
114
+ $totalWeight = 0.0;
115
+
116
+ $allKeys = array_unique(array_merge(array_keys($map1), array_keys($map2)));
117
+
118
+ foreach ($allKeys as $key) {
119
+ // On ignore les clés volatiles pour cette comparaison spécifique.
120
+ if (in_array($key, $volatileKeys, true)) { // @phpstan-ignore-line
121
+ continue;
122
+ }
123
+
124
+ // On ne compare que les clés qui ont un poids défini.
125
+ $weight = $weights[$key] ?? null;
126
+ if ($weight === null) continue;
127
+
128
+ $totalWeight += $weight;
129
+ if (isset($map1[$key]) && isset($map2[$key])) {
130
+ if ($map1[$key] === $map2[$key]) {
131
+ $weightedMatches += $weight;
132
+ }
133
+ }
134
+ }
135
+
136
+ return $totalWeight === 0.0 ? 0.0 : $weightedMatches / $totalWeight;
137
+ }
138
+
139
+ /**
140
+ * Algorithme de hachage cyrb53 (rapide et faible taux de collision).
141
+ * Porté depuis la version JavaScript.
142
+ *
143
+ * @param string $str La chaîne à hacher.
144
+ * @param int $seed Une graine optionnelle.
145
+ * @return string Le hash sous forme de chaîne de caractères.
146
+ */
147
+ public static function cyrb53(string $str, int $seed = 0): string
148
+ {
149
+ $h1 = 0xdeadbeef ^ $seed;
150
+ $h2 = 0x41c6ce57 ^ $seed;
151
+
152
+ for ($i = 0, $l = strlen($str); $i < $l; $i++) {
153
+ $ch = ord($str[$i]);
154
+ $h1 = self::imul($h1 ^ $ch, 2654435761);
155
+ $h2 = self::imul($h2 ^ $ch, 1597334677);
156
+ }
157
+
158
+ $h1 = self::imul($h1 ^ ($h1 >> 16), 2246822507) ^ self::imul($h2 ^ ($h2 >> 13), 3266489909);
159
+ $h2 = self::imul($h2 ^ ($h2 >> 16), 2246822507) ^ self::imul($h1 ^ ($h1 >> 13), 3266489909);
160
+
161
+ // En PHP, les opérations sur les grands nombres peuvent être délicates.
162
+ // On utilise bcmath pour une arithmétique de précision arbitraire, garantissant le même résultat que JS.
163
+ $val_h2 = bcadd(bcmul((string)(2097151 & $h2), '4294967296'), (string)($h1 >= 0 ? $h1 : $h1 + 4294967296));
164
+ return $val_h2;
165
+ }
166
+
167
+ /**
168
+ * Émule la multiplication 32-bit `Math.imul` de JavaScript.
169
+ *
170
+ * @param int $a
171
+ * @param int $b
172
+ * @return int Un entier signé 32-bit.
173
+ */
174
+ private static function imul(int $a, int $b): int
175
+ {
176
+ // Emulation of JavaScript's Math.imul for signed 32-bit integer multiplication.
177
+ // This version correctly handles overflows on 64-bit systems.
178
+ $ah = ($a >> 16) & 0xffff;
179
+ $al = $a & 0xffff;
180
+ $bh = ($b >> 16) & 0xffff;
181
+ $bl = $b & 0xffff;
182
+ $lo = $al * $bl;
183
+ $hi = (($lo >> 16) + ($al * $bh) + ($ah * $bl)) & 0xffff;
184
+ return (($hi << 16) | ($lo & 0xffff)) | 0;
185
+ }
185
186
  }
@@ -3,6 +3,7 @@
3
3
  declare(strict_types=1);
4
4
 
5
5
  namespace Anonympins\Fingerprint;
6
+ use Anonympins\Fingerprint\Config\SecurityProfiles;
6
7
 
7
8
  /**
8
9
  * FingerprintClient - Wrapper PHP pour la bibliothèque de fingerprinting côté client.
@@ -38,8 +39,7 @@ class FingerprintClient
38
39
  {
39
40
  $this->clientScriptPath = $clientScriptPath;
40
41
 
41
- // Configuration par défaut si non fournie
42
- $this->clientConfig = array_merge([
42
+ $defaultConfig = [
43
43
  'mouse' => true,
44
44
  'keystrokes' => true,
45
45
  'clicks' => true,
@@ -47,8 +47,13 @@ class FingerprintClient
47
47
  'fetch' => [
48
48
  'handleChallenges' => true,
49
49
  'probationaryTtl' => 30000, // 30 seconds
50
- ]
51
- ], $clientConfig);
50
+ ],
51
+ 'wasm' => true, // Activer la tentative de chargement du module WASM
52
+ 'wasmPath' => '/fp.js' // Chemin vers le script de chargement WASM
53
+ ];
54
+
55
+ // Utiliser une fusion profonde pour permettre de surcharger des sous-clés
56
+ $this->clientConfig = SecurityProfiles::deepMerge($defaultConfig, $clientConfig);
52
57
 
53
58
  try {
54
59
  // Génère un nonce pour CSP si possible, pour une sécurité renforcée.
@@ -94,8 +99,17 @@ class FingerprintClient
94
99
  // Le script d'initialisation qui sera inclus dans la page.
95
100
  $initScript = <<<JS
96
101
  document.addEventListener('DOMContentLoaded', function() {
102
+ const config = {$configJson};
97
103
  if (window.ClientLibrary) {
98
- window.ClientLibrary.initializeClient({$configJson});
104
+ if (config.wasmPath) {
105
+ const wasmScript = document.createElement('script');
106
+ wasmScript.src = config.wasmPath;
107
+ wasmScript.async = true;
108
+ wasmScript.nonce = '{$this->nonce}';
109
+ document.head.appendChild(wasmScript);
110
+ }
111
+
112
+ window.ClientLibrary.initializeClient(config);
99
113
  } else {
100
114
  console.error('Fingerprint client library not loaded.');
101
115
  }