Este proyecto tiene la intención de proporcionar una descripción completa y una reimplementación de la API web de WhatsApp, que eventualmente conducirá a un cliente personalizado. WhatsApp Web funciona internamente usando WebSockets; Este proyecto también lo hace.
No es necesario instalar o administrar versiones de Python y Node, el archivo shell.nix define un entorno con todas las dependencias incluidas para ejecutar este proyecto.
Hay un archivo .envrc en la carpeta raíz que se llama automáticamente cuando cd ING (Cambio de directorio) se proyecta, si el programa direnv está instalado junto con nix debe obtener una salida como esta:
> 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 no usa direnv o simplemente desea ingresar manualmente al entorno de compilación: DO:
nix-shellEn la raíz del proyecto
Antes de poder ejecutar la aplicación, asegúrese de tener el siguiente software instalado:
async await )pip instalados:websocket-client y git+https://github.com/dpallot/simple-websocket-server.git para actuar como servidor y cliente WebSocket.curve25519-donna y pycrypto para las cosas de cifrado.pyqrcode para la generación de código QR.protobuf para leer y escribir el formato de conversación binaria.curve25519-donna requiere Microsoft Visual C ++ 9.0 y debe copiar stdint.h en C:UsersYOUR USERNAMEAppDataLocalProgramsCommonMicrosoftVisual C++ for Python9.0VCinclude . Antes de comenzar la aplicación por primera vez, ejecute npm install -f para instalar todos pip install -r requirements.txt para todas las dependencias de Python.
Por último, para finalmente iniciarlo, simplemente ejecute npm start en el sistema operativo basado en Linux y npm run win en Windows. Utilizando Fancy concurrently y nodemon Magic, los tres componentes locales se iniciarán entre sí y cuando edite un archivo, el módulo cambiado se reiniciará automáticamente para aplicar los cambios.
Una adición reciente es una versión de la rutina de descifrado traducida a JavaScript en el navegador. Ejecute node index_jsdemo.js (solo necesarios porque los navegadores no permiten cambiar los encabezados HTTP para WebSockets), luego abra client/login-via-js-demo.html como un archivo normal en cualquier navegador. La salida de la consola debe mostrar mensajes binarios descifrados después de escanear el código QR.
Adiwajshing creó Baileys, una biblioteca de nodos que implementa la API web de WhatsApp.
Ndunks hizo un reimplemento mecanografiado en WAJS.
P4KL0NC4T creó Kyros, un paquete Python que implementa la API web de WhatsApp.
Con WhatsAppWeb-RS, Wiomoc creó un cliente web de WhatsApp en Rust.
Rhymen creó Go-Whatsapp, un paquete GO que implementa la API web de WhatsApp.
Vzaramel creó WhatsAppWeb-Clj, una biblioteca de Clojure, implementa la API web de WhatsApp.
El proyecto está organizado de la siguiente manera. Tenga en cuenta los puertos usados y asegúrese de que no estén en uso en otro lugar antes de comenzar la aplicación.
WhatsApp Web encripta los datos utilizando varios algoritmos diferentes. Estos incluyen AES 256 CBC, Curve25519 como esquema de acuerdo clave Diffie-Hellman, HKDF para generar el secreto compartido extendido y HMAC con SHA256.
Comenzar la sesión web de WhatsApp ocurre simplemente conectarse a uno de sus servidores WebSocket en wss://w[1-8].web.whatsapp.com/ws ( wss:// significa que la conexión WebSocket es segura; w[1-8] significa que cualquier número entre 1 y 8 puede seguir la w ). También asegúrese de que, al establecer la conexión, el Origin: https://web.whatsapp.com esté configurado, de lo contrario la conexión será rechazada.
Cuando envía mensajes a un WhatsApp Web Websocket, deben estar en un formato específico. Es bastante simple y parece messageTag,JSON , por ejemplo 1515590796,["data",123] . Tenga en cuenta que aparentemente la etiqueta de mensaje puede ser cualquier cosa. Esta aplicación utiliza principalmente la marca de tiempo actual como etiqueta, solo para ser un poco única. Whatsapp a menudo usa etiquetas de mensajes como s1 , 1234.--0 o algo así. Obviamente, la etiqueta del mensaje puede no contener una coma. Además, los objetos JSON son posibles y la carga útil.
Para iniciar sesión en un WebSocket abierto, siga estos pasos:
clientId , que debe ser 16 bytes codificados en Base64 (es decir, 25 caracteres). Esta aplicación solo usa 16 bytes aleatorios, es decir, base64.b64encode(os.urandom(16)) en Python.messageTag,["admin","init",[0,3,2390],["Long browser description","ShortBrowserDesc"],"clientId",true] .messageTag y clientId por los valores que eligió antes[0,3,2390] especifica la versión web de WhatsApp actual. El último valor cambia con frecuencia. Sin embargo, debería ser bastante compatible con el revés."Long browser description" es una cadena arbitraria que se mostrará en la aplicación WhatsApp en la lista de clientes web de WhatsApp registrados después de escanear el código QR."ShortBrowserDesc" aún no se ha observado en ninguna parte, pero también es arbitraria.status : debería ser 200ref : En la aplicación, esto se trata como la ID del servidor; Importante para la generación QR, ver más abajottl : es 20000, tal vez el tiempo después de que el código QR se vuelve inválidoupdate : una bandera booleanacurr : la versión web actual de WhatsApp, por ejemplo 0.2.7314time : la marca de tiempo en la que respondió el servidor, como milisegundos de punto flotante, por ejemplo, 1515592039037.0curve25519.Private() .privateKey.get_public() .ref desde el paso 4base64.b64encode(publicKey.serialize())pyqrcode ) y escanee con la aplicación WhatsApp.ttl ).messageTag,["admin","Conn","reref"] .status : Debe ser 200 (otros: 304 - Reutilización anterior Ref, 429 - Nueva referencia denegada)ref : Nueva referenciattl : tiempo de vencimientoConn : Array contiene objeto JSON como segundo elemento con información de conexión que contiene los siguientes atributos y muchos más:battery : el porcentaje de batería actual de su teléfonobrowserToken : se usa para iniciar sesión sin una conexión activa de WebSocket (aún no se implementa)clientToken : Solía reanudar las sesiones cerradas, también conocido como "Remember Me" (aún no implementado)phone : un objeto con información detallada sobre su teléfono, por ejemplo, device_manufacturer , device_model , os_build_number , os_versionplatform : el sistema operativo de su teléfono, por ejemplo, androidpushname : el nombre tuyo que proporcionaste whatsappsecret (¡recuerda esto!)serverToken : Solía reanudar sesiones cerradas, también conocida como "Recuérdame" (aún no se ha implementado)wid : su número de teléfono en el formato de identificación de chat (ver más abajo)Stream : Array tiene cuatro elementos en total, por lo que toda la carga útil es como ["Stream","update",false,"0.2.7314"]Props : Array contiene objeto JSON como segundo elemento con varias propiedades como imageMaxKBytes (1024), maxParticipants (257), videoMaxEdge (960) y otrossecret de Conn como base64 y almacenándolo como secret . Este secreto decodificado tendrá 144 bytes de largo.sharedSecret . La aplicación lo hace usando privateKey.get_shared_key(curve25519.Public(secret[:32]), lambda a:a) .sharedSecret a 80 bytes usando HKDF. Llame a este valor sharedSecretExpanded .HmacSha256(sharedSecretExpanded[32:64], secret[:32] + secret[64:]) . Compare este valor con secret[32:64] . Si no son iguales, aborta el inicio de sesión.sharedSecretExpanded[64:] + secret[64:] como keysEncrypted .sharedSecretExpanded[:32] como clave, es decir, almacenar AESDecrypt(sharedSecretExpanded[:32], keysEncrypted) como keysDecrypted .keysDecrypted tiene 64 bytes de largo y contiene dos teclas, cada una de 32 bytes de largo. El encKey se utiliza para descifrar mensajes binarios que le envían el servidor web de WhatsApp o en cifrado de mensajes binarios que envía al servidor. Se necesita el macKey para validar los mensajes que se le envían:encKey : keysDecrypted[:32]macKey : keysDecrypted[32:64]init , verifique si tiene serverToken y clientToken .messageTag,["admin","login","clientToken","serverToken","clientId","takeover"]{"status": 200} . Otros estados:tos en el JSON: si es igual o superior a 2, ha violado TOSserverToken y clientToken antiguos o caducados, se le desafiará a confirmar que todavía tiene claves de cifrado válidas.messageTag,["Cmd",{"type":"challenge","challenge":"BASE_64_ENCODED_STRING=="}]challenge String desde Base64, firme con su Mackey, codifica con Base64 y envíe messageTag,["admin","challenge","BASE_64_ENCODED_STRING==","serverToken","clientId"]{"status": 200} , pero no significa nada.goodbye,,["admin","Conn","disconnect"] .encKey con su macKey y codifíquela con Base64. Digamos que es su logoutToken .https://dyn.web.whatsapp.com/logout?t=browserToken&m=logoutTokenAhora que tiene las dos claves, validando y descifrar mensajes que el servidor le envió es bastante fácil. Tenga en cuenta que esto solo se necesita para mensajes binarios , todo lo que recibe se mantiene claro. Los mensajes binarios siempre tienen 32 bytes al principio que especifican la suma de verificación HMAC. Tanto JSON como los mensajes binarios tienen una etiqueta de mensaje al principio que se puede descartar, es decir, solo la porción después de que el primer personaje de coma sea significativo.
macKey (aquí messageContent es todo el mensaje binario): HmacSha256(macKey, messageContent[32:]) . Si este valor no es igual al messageContent[:32] , el mensaje que le envía el servidor no es válido y debe descartarse.encKey : AESDecrypt(encKey, messageContent[32:]) .Los datos que obtenga en el paso final tiene un formato binario que se describe a continuación. Aunque es binario, aún puede ver varias cuerdas, especialmente el contenido de los mensajes que envió es bastante obvio allí.
El script de Python backend/decoder.py implementa la clase MessageParser . Es capaz de crear una estructura JSON a partir de datos binarios en los que los datos aún se organizan de una manera bastante desordenada. La sección sobre el manejo de nodos a continuación discutirá cómo se reorganizan los nodos.
MessageParser inicialmente solo necesita algunos datos y luego los procesa byte by byte, es decir, como flujo. Tiene un par de constantes y muchos métodos que se construyen entre sí.
[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"]- para 10 , . para 11 y