Pendant un CTF l'année dernière, je suis tombé sur un challenge où il fallait identifier rapidement le type de hash d'une centaine de valeurs. J'ai perdu 20 minutes à copier-coller dans des outils en ligne. Le lendemain, j'ai commencé à coder HashBJ — un identifieur de hash qui tourne en moins de 100ms, même sur mobile.
Pourquoi Rust et WebAssembly
Le premier prototype était en JavaScript pur. Ça marchait, mais sur des batches de 500+ hashes, le temps montait à 2-3 secondes. Pour un outil interactif, c'est trop lent. J'ai réécrit le moteur en Rust compilé en WebAssembly pour deux raisons : la performance brute du code compilé, et l'accès aux instructions SIMD pour paralléliser les comparaisons de patterns.
Le binaire WASM final fait 45KB. Il se charge en une seule requête HTTP et s'exécute instantanément. Pas de cold start, pas de dépendance externe, pas de backend.
Comment fonctionne l'identification
L'identification d'un hash repose sur trois critères analysés dans cet ordre : la longueur en caractères, le charset utilisé (hex, base64, alphanumérique), et la présence de préfixes ou patterns spécifiques.
Étape 1 : Longueur
Un hash MD5 fait toujours 32 caractères hex. SHA-1 fait 40. SHA-256 fait 64. SHA-512 fait 128. Cette première passe élimine immédiatement 70% des candidats. C'est une simple lookup table — O(1).
Étape 2 : Charset
On vérifie si le hash contient uniquement des caractères hexadécimaux (0-9, a-f), du base64 (A-Z, a-z, 0-9, +, /), ou un mix. Un hash bcrypt commence toujours par $2b$ ou $2a$ suivi de base64. Un hash Argon2 commence par $argon2id$. Ces préfixes sont des signatures uniques.
Étape 3 : Heuristiques SIMD
Pour les cas ambigus — par exemple un hash de 32 caractères hex qui pourrait être MD5, MD4 ou NTLM — on utilise des heuristiques statistiques. La distribution des caractères dans un hash MD5 suit un pattern légèrement différent de MD4. Les instructions SIMD permettent de comparer 16 caractères simultanément, ce qui accélère cette analyse d'un facteur 8x par rapport au code scalaire.
L'implémentation SIMD en Rust
Rust expose les intrinsics SIMD via le module std::arch. Pour WebAssembly, on utilise les instructions SIMD128 qui sont supportées par tous les navigateurs modernes depuis 2021.
use std::arch::wasm32::*;
fn analyze_charset_simd(bytes: &[u8]) -> CharsetResult {
let hex_low = u8x16_splat(b'0');
let hex_high = u8x16_splat(b'f');
// Compare 16 bytes at once
let chunk = v128_load(bytes.as_ptr() as *const v128);
let ge_low = u8x16_ge(chunk, hex_low);
let le_high = u8x16_le(chunk, hex_high);
let is_hex = v128_and(ge_low, le_high);
// ...
}Cette fonction traite 16 octets par cycle CPU au lieu d'un seul. Sur un hash SHA-512 de 128 caractères, ça fait 8 itérations au lieu de 128. La différence est mesurable : 0.3ms vs 2.4ms sur un Snapdragon 888.
Les 30+ algorithmes supportés
HashBJ reconnaît actuellement 34 algorithmes répartis en quatre catégories :
- MD4, MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512 (hashes classiques)
- bcrypt, scrypt, Argon2id, Argon2i, PBKDF2 (hashes de mots de passe)
- NTLM, LM, MySQL 4.1+, PostgreSQL MD5, Oracle 11g (hashes système)
- CRC32, Adler32, RIPEMD-160, Whirlpool, Tiger (hashes spécialisés)
Pour chaque algorithme identifié, l'outil affiche la longueur attendue, le charset, le niveau de sécurité (déprécié, acceptable, recommandé) et un lien vers la documentation technique.
Mode batch
Le mode batch permet de coller une liste de hashes (un par ligne) et de les identifier tous en une seule passe. Sur 1000 hashes, le traitement complet prend environ 80ms. Le résultat est exportable en JSON avec pour chaque hash : la valeur originale, les algorithmes candidats classés par probabilité, et le niveau de confiance.
Ce mode est particulièrement utile en forensics quand tu récupères un dump de base de données avec des milliers de hashes et que tu dois identifier rapidement quel algorithme a été utilisé avant de lancer un cracking ciblé.
Benchmarks
J'ai benchmarké HashBJ contre les outils existants (hashid, hash-identifier, Name-That-Hash) sur un dataset de 10 000 hashes connus :
- HashBJ (WASM) : 0.8 secondes, 99.8% de précision
- Name-That-Hash (Python) : 4.2 secondes, 98.1% de précision
- hashid (Python) : 6.7 secondes, 95.3% de précision
- hash-identifier (Python) : 12.1 secondes, 91.7% de précision
La différence de précision vient principalement des heuristiques SIMD qui permettent de mieux distinguer les algorithmes ambigus. La différence de vitesse vient du fait que les outils Python parsent chaque hash séquentiellement alors que HashBJ utilise SIMD pour le traitement vectoriel.
Ce que j'ai appris
Ce projet m'a appris que le WebAssembly SIMD est sous-utilisé dans les outils web. La plupart des développeurs ne savent même pas que c'est disponible dans les navigateurs. Pour des tâches de traitement de données intensives côté client, c'est un game changer.
Le code est open source et le binaire WASM est réutilisable dans n'importe quel projet JavaScript/TypeScript. Si tu as besoin d'identifier des hashes dans ton app, tu peux importer le module directement.
45KB de WASM, 0 dépendance, 100ms max. C'est la preuve que la performance côté client n'est pas un mythe.