@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.
- package/CHANGELOG.md +33 -0
- package/README.md +1080 -1076
- package/composer.json +3 -2
- package/package.json +9 -6
- package/phpunit.xml +2 -1
- package/src/js/fingerprint.js +3946 -3448
- package/src/php/Config/SecurityProfiles.php +17 -7
- package/src/php/FingerprintBuilder.php +185 -184
- package/src/php/FingerprintClient.php +19 -5
- package/src/php/FingerprintEngine.php +990 -850
- package/src/php/Ja3AnomalyDetector.php +228 -0
- package/src/php/RequestContext.php +4 -0
- package/src/php/Store/StoreManager.php +10 -0
- package/src/php/Tests/FingerprintEngineTest.php +299 -218
- package/src/php/Tests/IpReputationTest.php +157 -0
- package/src/php/Tests/Ja3AnomalyDetectorTest.php +180 -0
- package/src/php/Utils/BigInt.php +77 -34
- package/src/php/Utils/RequestUtils.php +325 -25
|
@@ -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
|
-
'
|
|
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, //
|
|
119
|
-
'
|
|
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, //
|
|
161
|
-
'
|
|
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, //
|
|
202
|
-
'
|
|
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
|
-
//
|
|
177
|
-
|
|
178
|
-
$
|
|
179
|
-
$
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
$
|
|
183
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|