Ce projet a l'intention de fournir une description complète et une réimplémentation de l'API Web WhatsApp, qui mènera éventuellement à un client personnalisé. WhatsApp Web fonctionne en interne à l'aide de WebSockets; Ce projet le fait aussi.
Il n'est pas nécessaire d'installer ou de gérer les versions Python et Node, le fichier shell.nix définit un environnement avec toutes les dépendances incluses pour exécuter ce projet.
Il y a un fichier .envrc dans le dossier racine qui est appelé automatiquement lorsque cd ing (modification du répertoire) en projet, si le programme direnv est installé avec nix vous devriez obtenir une sortie comme ceci:
> cd ~ /dev/whatsapp
Installing node modules
npm WARN prepare removing existing node_modules/ before installation
> [email protected] install /home/rainy/dev/whatsapp/node_modules/fsevents
> node-gyp rebuild
make: Entering directory ' /home/rainy/dev/whatsapp/node_modules/fsevents/build '
SOLINK_MODULE(target) Release/obj.target/.node
COPY Release/.node
make: Leaving directory ' /home/rainy/dev/whatsapp/node_modules/fsevents/build '
> [email protected] postinstall /home/rainy/dev/whatsapp/node_modules/nodemon
> node bin/postinstall || exit 0
added 310 packages in 3.763s
Done.
$$ $$ $$ $$
$$ | $ $$ | $$ | $$ |
$$ | $$ $ $$ | $$$$$$ $ $$$$$$ $$$$$$ $$$$$$ $ $$$$$$ $$$$$$ $$$$$$
$$ $$ $$ $ $ | $$ __ $$ _ ___ $$ \ _ $$ _ | $$ _____ | _ ___ $$ $$ __ $$ $$ __ $$
$$$$ _ $$$$ | $$ | $$ | $$$$$$ $ | $$ | $ $$$$ $ $$$$$$ $ | $$ / $$ | $$ / $$ |
$$ $ / $ $$ | $$ | $$ | $$ __ $$ | $$ | $$ _ ___ $$ $$ __ $$ | $$ | $$ | $$ | $$ |
$$ / $ $ | $$ | $$ | $ $$$$$$ | $ $$ $ | $$$$$$ $ | $ $$$$$$ | $$$$$$ $ | $$$$$$ $ |
_ _/ _ _ | _ _ | _ _ | _ ______ | _ ___/ _ ______/ _ ______ | $$ ____/ $$ ____/
$$ | $$ |
$$ | $$ |
_ _ | _ _ |
Node v13.13.0
Python 2.7.17
Try running server with: npm start
[nix-shell: ~ /dev/whatsapp]$ Si vous n'utilisez pas direnv ou si vous souhaitez simplement entrer manuellement dans l'environnement de construction, faites:
nix-shellDans la racine du projet
Avant de pouvoir exécuter l'application, assurez-vous que le logiciel suivant a installé:
async await est utilisée)pip suivants installés:websocket-client et git+https://github.com/dpallot/simple-websocket-server.git pour agir en tant que serveur et client WebSocket.curve25519-donna et pycrypto pour les trucs de chiffrement.pyqrcode pour la génération de code QR.protobuf pour lire et écrire le format de conversation binaire.curve25519-donna nécessite Microsoft Visual C ++ 9.0 et vous devez copier stdint.h en C:UsersYOUR USERNAMEAppDataLocalProgramsCommonMicrosoftVisual C++ for Python9.0VCinclude . Avant de démarrer l'application pour la première fois, exécutez npm install -f pour installer tous les nœuds et pip install -r requirements.txt pour toutes les dépendances Python.
Enfin, pour enfin le lancer, il suffit d'exécuter npm start sur les OS et npm run win de Linux sur Windows. En utilisant la fantaisie concurrently et la magie nodemon , les trois composants locaux seront démarrés après l'autre et lorsque vous modifierez un fichier, le module modifié redémarrera automatiquement pour appliquer les modifications.
Un ajout récent est une version de la routine de décryptage traduite en JavaScript in-browser. Exécutez node index_jsdemo.js (juste nécessaire car les navigateurs ne permettent pas de modifier les en-têtes HTTP pour WebSockets), puis ouvrez client/login-via-js-demo.html comme fichier normal dans n'importe quel navigateur. La sortie de la console doit afficher des messages binaires déchiffrés après avoir scanné le code QR.
Adiwajshing a créé Baileys, une bibliothèque de nœuds qui implémente l'API Web WhatsApp.
Nduks a effectué une réimplémentation dactylographiée à WAJS.
P4KL0NC4T a créé Kyros, un package Python qui implémente l'API Web WhatsApp.
Avec WhatsAppWeb-RS, Wiomoc a créé un client Web WhatsApp dans Rust.
Rhymen a créé Go-WhatsApp, un package Go qui implémente l'API Web WhatsApp.
Vzaramel a créé WhatsAppWeb-Clj, une bibliothèque Clojure L'implémente l'API Web WhatsApp.
Le projet est organisé de la manière suivante. Notez les ports utilisés et assurez-vous qu'ils ne sont pas utilisés ailleurs avant de démarrer l'application.
WhatsApp Web crypte les données à l'aide de plusieurs algorithmes différents. Il s'agit notamment d'AES 256 CBC, Curve25519 en tant que schéma d'accord de clé Diffie-Hellman, HKDF pour générer le secret partagé étendu et HMAC avec SHA256.
Le démarrage de la session Web WhatsApp se produit en se connectant à l'un de ses serveurs WebSocket à wss://w[1-8].web.whatsapp.com/ws ( wss:// signifie que la connexion WebSocket est sécurisée; w[1-8] signifie que n'importe quel nombre entre 1 et 8 peut suivre le w ). Assurez-vous également que, lors de l'établissement de la connexion, l' Origin: https://web.whatsapp.com est définie, sinon la connexion sera rejetée.
Lorsque vous envoyez des messages à un whatsapp websocket, ils doivent être dans un format spécifique. Il est assez simple et ressemble à messageTag,JSON , par exemple 1515590796,["data",123] . Notez qu'apparemment, la balise de message peut être n'importe quoi. Cette application utilise principalement l'horodatage actuel comme balise, juste pour être un peu unique. WhatsApp lui-même utilise souvent des balises de message comme s1 , 1234.--0 ou quelque chose comme ça. De toute évidence, la balise de message peut ne pas contenir de virgule. De plus, les objets JSON sont possibles ainsi que la charge utile.
Pour vous connecter sur un Websocket ouvert, suivez ces étapes:
clientId , qui doit être 16 octets codés Base64 (c'est-à-dire 25 caractères). Cette application utilise simplement 16 octets aléatoires, c'est-à-dire base64.b64encode(os.urandom(16)) dans Python.messageTag,["admin","init",[0,3,2390],["Long browser description","ShortBrowserDesc"],"clientId",true] .messageTag et clientId par les valeurs que vous avez choisies avant[0,3,2390] spécifie la version Web WhatsApp actuelle. La dernière valeur change fréquemment. Cela devrait être assez compatible en arrière."Long browser description" est une chaîne arbitraire qui sera affichée dans l'application WhatsApp dans la liste des clients Web WhatsApp enregistrés après avoir scanné le code QR."ShortBrowserDesc" n'a pas encore été observé nulle part mais est également arbitraire.status : devrait être 200ref : Dans l'application, ceci est traité comme l'ID de serveur; Important pour la génération QR, voir ci-dessousttl : est 20000, peut-être que le moment après que le code QR devient invalideupdate : un drapeau booléencurr : la version Web WhatsApp actuelle, par exemple 0.2.7314time : l'horodatage auquel le serveur a répondu, en tant que millisecondes à virgule flottante, par exemple 1515592039037.0curve25519.Private() .privateKey.get_public() .ref de l'étape 4base64.b64encode(publicKey.serialize())pyqrcode ) et scannez-la à l'aide de l'application WhatsApp.ttl ).messageTag,["admin","Conn","reref"] .status : devrait être de 200 (autres: 304 - Réutiliser la réf.ref : Nouvelle référencettl : temps d'expirationConn : Array contient un objet JSON en deuxième élément avec des informations de connexion contenant les attributs suivants et bien d'autres:battery : le pourcentage de batterie actuel de votre téléphonebrowserToken : Utilisé pour se déconnecter sans connexion WebSocket active (non implémentée)clientToken : utilisé pour reprendre ses sessions fermées aka "Remember Me" (non encore implémenté)phone : un objet avec des informations détaillées sur votre téléphone, par exemple, device_manufacturer , device_model , os_build_number , os_versionplatform : votre téléphone téléphonique, par exemple androidpushname : le nom de votre avez fourni WhatsAppsecret (rappelez-vous cela!)serverToken : Utilisé pour reprendre ses sessions fermées aka "Remember Me" (non encore implémenté)wid : Votre numéro de téléphone dans le format d'identification du chat (voir ci-dessous)Stream : Array a quatre éléments au total, donc la charge utile entière est comme ["Stream","update",false,"0.2.7314"]Props : Array contient un objet JSON comme deuxième élément avec plusieurs propriétés comme imageMaxKBytes (1024), maxParticipants (257), videoMaxEdge (960) et autressecret de Conn en tant que base64 et le stocker comme secret . Ce secret décodé sera long de 144 octets.sharedSecret . L'application le fait en utilisant privateKey.get_shared_key(curve25519.Public(secret[:32]), lambda a:a) .sharedSecret à 80 octets en utilisant HKDF. Appelez cette valeur sharedSecretExpanded .HmacSha256(sharedSecretExpanded[32:64], secret[:32] + secret[64:]) . Comparez cette valeur au secret[32:64] . S'ils ne sont pas égaux, interdit la connexion.sharedSecretExpanded[64:] + secret[64:] comme keysEncrypted .sharedSecretExpanded[:32] comme clé, c'est-à-dire le stockage AESDecrypt(sharedSecretExpanded[:32], keysEncrypted) as keysDecrypted .keysDecrypted mesure 64 octets de long et contient deux clés, chacune de 32 octets de long. L' encKey est utilisé pour décrypter les messages binaires qui vous sont envoyés par le serveur Web WhatsApp ou crypter les messages binaires que vous envoyez au serveur. Le macKey est nécessaire pour valider les messages qui vous sont envoyés:encKey : keysDecrypted[:32]macKey : keysDecrypted[32:64]init , vérifiez si vous avez serverToken et clientToken .messageTag,["admin","login","clientToken","serverToken","clientId","takeover"]{"status": 200} . Autres statuts:tos Accès refusserverToken ET EXPIRÉE ET clientToken , vous serez mis au défi de confirmer que vous avez toujours des clés de chiffrement valides.messageTag,["Cmd",{"type":"challenge","challenge":"BASE_64_ENCODED_STRING=="}]challenge String de Base64, signez-le avec votre mackey, encodez-le avec Base64 et envoyez messageTag,["admin","challenge","BASE_64_ENCODED_STRING==","serverToken","clientId"]{"status": 200} , mais cela ne signifie rien.goodbye,,["admin","Conn","disconnect"] .encKey avec votre macKey et encodez avec Base64. Disons que c'est votre logoutToken .https://dyn.web.whatsapp.com/logout?t=browserToken&m=logoutTokenMaintenant que vous avez les deux clés, validant et décryptant les messages que le serveur vous a envoyé est assez facile. Notez que cela n'est nécessaire que pour les messages binaires , tous les JSON que vous recevez restent simples. Les messages binaires ont toujours 32 octets au début qui spécifient la somme de contrôle HMAC. Les messages JSON et binaires ont une étiquette de message à leur tout début qui peut être rejetée, c'est-à-dire que la partie seulement après que le premier caractère de virgule est significative.
macKey (ici messageContent est l' ensemble du message binaire): HmacSha256(macKey, messageContent[32:]) . Si cette valeur n'est pas égale à messageContent[:32] , le message qui vous est envoyé par le serveur n'est pas valide et doit être jeté.encKey : AESDecrypt(encKey, messageContent[32:]) .Les données que vous obtenez dans la dernière étape ont un format binaire qui est décrit ci-dessous. Même s'il est binaire, vous pouvez toujours y voir plusieurs chaînes, en particulier le contenu des messages que vous avez envoyée est assez évident là-bas.
Le script python backend/decoder.py implémente la classe MessageParser . Il est capable de créer une structure JSON à partir de données binaires dans lesquelles les données sont encore organisées de manière plutôt désordonnée. La section sur la manipulation des nœuds ci-dessous expliquera comment les nœuds sont réorganisés par la suite.
MessageParser a initialement besoin de quelques données, puis les traite l'octet par octet, c'est-à-dire comme un flux. Il a quelques constantes et beaucoup de méthodes qui s'appuient toutes les unes sur les autres.
[None,None,None,"200","400","404","500","501","502","action","add", "after","archive","author","available","battery","before","body", "broadcast","chat","clear","code","composing","contacts","count", "create","debug","delete","demote","duplicate","encoding","error", "false","filehash","from","g.us","group","groups_v2","height","id", "image","in","index","invis","item","jid","kind","last","leave", "live","log","media","message","mimetype","missing","modify","name", "notification","notify","out","owner","participant","paused", "picture","played","presence","preview","promote","query","raw", "read","receipt","received","recipient","recording","relay", "remove","response","resume","retry","s.whatsapp.net","seconds", "set","size","status","subject","subscribe","t","text","to","true", "type","unarchive","unavailable","url","user","value","web","width", "mute","read_only","admin","creator","short","update","powersave", "checksum","epoch","block","previous","409","replaced","reason", "spam","modify_tag","message_info","delivery","emoji","title", "description","canonical-url","matched-text","star","unstar", "media_key","filename","identity","unread","page","page_count", "search","media_message","security","call_log","profile","ciphertext", "invite","gif","vcard","frequent","privacy","blacklist","whitelist", "verify","location","document","elapsed","revoke_invite","expiration", "unsubscribe","disable"]- pour 10 , . pour 11 et