Este projeto pretende fornecer uma descrição completa e reimplementação da API da Web do WhatsApp, que acabará por levar a um cliente personalizado. Whatsapp Web funciona internamente usando websockets; Este projeto também faz.
Não há necessidade de instalar ou gerenciar versões Python e Node, o arquivo shell.nix define um ambiente com todas as dependências incluídas para executar este projeto.
Há um arquivo .envrc na pasta root que é chamada automaticamente quando cd (alterando o diretório) para o projeto, se o programa direnv for instalado junto com nix você deve obter uma saída 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]$ Se você não usa direnv ou apenas deseja entrar manualmente no ambiente de construção:
nix-shellna raiz do projeto
Antes de executar o aplicativo, verifique se você tem o seguinte software instalado:
async await é usada)pip instalados:websocket-client e git+https://github.com/dpallot/simple-websocket-server.git para atuar como servidor e cliente da WebSocket.curve25519-donna e pycrypto para o material de criptografia.pyqrcode para geração de código QR.protobuf para ler e escrever o formato de conversa binária.curve25519-donna é necessário o Microsoft Visual C ++ 9.0 e você precisa copiar stdint.h em C:UsersYOUR USERNAMEAppDataLocalProgramsCommonMicrosoftVisual C++ for Python9.0VCinclude . Antes de iniciar o aplicativo pela primeira vez, execute npm install -f para instalar todos os nó e pip install -r requirements.txt para todas as dependências do Python.
Por fim, para finalmente lançá -lo, basta executar npm start na npm run win com base no Linux no Windows. Usando a Fancy concurrently e nodemon Magic, todos os três componentes locais serão iniciados um com o outro e, quando você editar um arquivo, o módulo alterado será reiniciado automaticamente para aplicar as alterações.
Uma adição recente é uma versão da rotina de descriptografia traduzida para JavaScript no navegador. Execute node index_jsdemo.js (apenas necessário porque os navegadores não permitem a alteração dos cabeçalhos HTTP para websockets) e abra client/login-via-js-demo.html como um arquivo normal em qualquer navegador. A saída do console deve mostrar mensagens binárias descriptografadas após a digitalização do código QR.
Adiwajshing criou a Baileys, uma biblioteca de nós que implementa a API da Web do WhatsApp.
Os ndunks fizeram uma reimplementação digital no WAJS.
O P4KL0NC4T criou o Kyros, um pacote Python que implementa a API da Web do WhatsApp.
Com o WhatsAppWeb-RS, o Wiomoc criou um cliente da Web Whatsapp em Rust.
O Rhymen criou Go-Whatsapp, um pacote Go que implementa a API da Web do WhatsApp.
Vzaramel criou o WhatsAppweb-clj, uma biblioteca de clojure os implementos da API da Web do WhatsApp.
O projeto está organizado da seguinte maneira. Observe as portas usadas e verifique se elas não estão em uso em outros lugares antes de iniciar o aplicativo.
Whatsapp Web criptografa os dados usando vários algoritmos diferentes. Isso inclui o AES 256 CBC, CURVE25519 como esquema de concordância Diffie-Hellman, HKDF para gerar o segredo compartilhado e o HMAC e o HMAC com SHA256.
Iniciar a sessão da web do WhatsApp acontece apenas conectando-se a um de seus servidores websocket em wss://w[1-8].web.whatsapp.com/ws ( wss:// significa que a conexão WebSocket é segura; w[1-8] significa que qualquer número entre 1 e 8 pode seguir o w ). Verifique também que, ao estabelecer a conexão, a Origin: https://web.whatsapp.com estiver definida, caso contrário, a conexão será rejeitada.
Quando você envia mensagens para um web websocket Whatsapp, elas precisam estar em um formato específico. É bastante simples e parece messageTag,JSON , por exemplo, 1515590796,["data",123] . Observe que aparentemente a tag de mensagem pode ser qualquer coisa. Este aplicativo usa principalmente o registro de data e hora atual como tag, apenas para ser um pouco exclusivo. O próprio WhatsApp geralmente usa tags de mensagem como s1 , 1234.--0 ou algo assim. Obviamente, a tag de mensagem pode não conter uma vírgula. Além disso, os objetos JSON são possíveis e da carga útil.
Para fazer login em um WebSocket aberto, siga estas etapas:
clientId , que precisa ser 16 bytes codificados por 64 (ou seja, 25 caracteres). Este aplicativo utiliza apenas 16 bytes aleatórios, ou seja, base64.b64encode(os.urandom(16)) em Python.messageTag,["admin","init",[0,3,2390],["Long browser description","ShortBrowserDesc"],"clientId",true] .messageTag e clientId pelos valores que você escolheu antes[0,3,2390] especifica a versão Web WhatsApp atual. O último valor muda com frequência. Deveria ser compatível com o atraso."Long browser description" é uma sequência arbitrária que será mostrada no aplicativo WhatsApp na lista de clientes da Web WhatsApp registrados depois de digitalizar o código QR."ShortBrowserDesc" ainda não foi observado em nenhum lugar, mas também é arbitrário.status : deve ser 200ref : No aplicativo, isso é tratado como o ID do servidor; importante para a geração QR, veja abaixottl : é 20000, talvez o tempo após o código QR se torne inválidoupdate : uma bandeira booleanacurr : A versão atual da Web WhatsApp, por exemplo, 0.2.7314time : o registro de data e hora em que o servidor respondeu, como milissegundos de ponto flutuante, por exemplo, 1515592039037.0curve25519.Private() .privateKey.get_public() .ref da Etapa 4base64.b64encode(publicKey.serialize())pyqrcode ) e digitalize -a usando o aplicativo WhatsApp.ttl ).messageTag,["admin","Conn","reref"] .status : deve ser 200 (outros: 304 - reutilize referência anterior, 429 - novo ref negado)ref : Novo Refttl : Tempo de expiraçãoConn : Array contém objeto JSON como segundo elemento com informações de conexão que contêm os seguintes atributos e muito mais:battery : a porcentagem atual da bateria do seu telefonebrowserToken : Usado para fazer logout sem conexão ativa do WebSocket (ainda não implementada)clientToken : Usado para retomar as sessões fechadas, também conhecidas como "Remember Me" (ainda não implementado)phone : Um objeto com informações detalhadas sobre o seu telefone, por exemplo, device_manufacturer , device_model , os_build_number , os_versionplatform : seu sistema operacional, por exemplo, androidpushname : o seu nome você forneceu o whatsappsecret (lembre -se disso!)serverToken : Usado para retomar as sessões fechadas, também conhecidas como "Remember Me" (ainda não implementado)wid : seu número de telefone no formato de identificação de bate -papo (veja abaixo)Stream : o Array possui quatro elementos no total, então toda a carga útil é como ["Stream","update",false,"0.2.7314"]Props : a matriz contém objeto JSON como segundo elemento com várias propriedades como imageMaxKBytes (1024), maxParticipants (257), videoMaxEdge (960) e outrossecret de Conn como base64 e armazenando -o como secret . Este segredo decodificado terá 144 bytes de comprimento.sharedSecret . O aplicativo faz isso usando privateKey.get_shared_key(curve25519.Public(secret[:32]), lambda a:a) .sharedSecret para 80 bytes usando HKDF. Chame esse valor sharedSecretExpanded .HmacSha256(sharedSecretExpanded[32:64], secret[:32] + secret[64:]) . Compare esse valor com o secret[32:64] . Se eles não forem iguais, aborte o login.sharedSecretExpanded[64:] + secret[64:] como keysEncrypted .sharedSecretExpanded[:32] como chave, ou seja, armazenar AESDecrypt(sharedSecretExpanded[:32], keysEncrypted) como keysDecrypted .keysDecrypted tem 64 bytes de comprimento e contém duas teclas, cada uma de 32 bytes de comprimento. O encKey é usado para descriptografar mensagens binárias enviadas a você pelo servidor da web do WhatsApp ou criptografar mensagens binárias que você envia para o servidor. O macKey é necessário para validar as mensagens enviadas a você:encKey : keysDecrypted[:32]macKey : keysDecrypted[32:64]init , verifique se você tem serverToken e clientToken .messageTag,["admin","login","clientToken","serverToken","clientId","takeover"]{"status": 200} . Outros status:tos no JSON: se for igual ou superior a 2, você violou os TOsserverToken e clientToken antigo ou expirado, você será desafiado para confirmar que ainda possui chaves de criptografia válidas.messageTag,["Cmd",{"type":"challenge","challenge":"BASE_64_ENCODED_STRING=="}]challenge da base64, assine -a com sua mackey, codifique -a de volta com base64 e envie messageTag,["admin","challenge","BASE_64_ENCODED_STRING==","serverToken","clientId"]{"status": 200} , mas não significa nada.goodbye,,["admin","Conn","disconnect"] .encKey com o macKey e a codifique com a base64. Digamos que seja o seu logoutToken .https://dyn.web.whatsapp.com/logout?t=browserToken&m=logoutTokenAgora que você tem as duas chaves, validando e descriptografando mensagens que o servidor enviado para você é bastante fácil. Observe que isso é necessário apenas para mensagens binárias , todo o JSON que você recebe permanece claro. As mensagens binárias sempre têm 32 bytes no início que especificam a soma de verificação do HMAC. As mensagens JSON e binárias têm uma tag de mensagem em seu início que pode ser descartado, ou seja, apenas a parte após o primeiro personagem de vírgula.
macKey (aqui messageContent é a mensagem binária inteira ): HmacSha256(macKey, messageContent[32:]) . Se esse valor não for igual ao messageContent[:32] , a mensagem enviada a você pelo servidor será inválida e deve ser descartada.encKey : AESDecrypt(encKey, messageContent[32:]) .Os dados que você recebe na etapa final possuem um formato binário descrito a seguir. Embora seja binário, você ainda pode ver várias cordas, especialmente o conteúdo das mensagens que você enviou é bastante óbvio por lá.
O backend/decoder.py implementa a classe MessageParser . Ele é capaz de criar uma estrutura JSON a partir de dados binários nos quais os dados ainda estão organizados de maneira bastante confusa. A seção sobre manuseio de nós abaixo discutirá como os nós serão reorganizados posteriormente.
MessageParser inicialmente precisa apenas de alguns dados e, em seguida, o processa byte byte, ou seja, como um fluxo. Possui algumas constantes e muitos métodos que se desenvolvem.
[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 e