このプロジェクトは、WhatsApp Web APIの完全な説明と再実装を提供する予定であり、最終的にカスタムクライアントにつながります。 WhatsApp Webは、WebSocketsを使用して内部で機能します。このプロジェクトも同様です。
Pythonバージョンとノードバージョンをインストールまたは管理する必要はありません。ファイルshell.nix 、このプロジェクトを実行するために含まれるすべての依存関係を持つ環境を定義します。
ルートフォルダーには.envrcファイルがあり、 cd (ディレクトリの変更)がプロジェクトに自動的に呼ばれます。プログラムdirenvがnixとともにインストールされている場合は、次のような出力を取得する必要があります。
> 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]$ direnvを使用しない場合、またはビルド環境に手動で入りたい場合は次のことです。
nix-shellプロジェクトルートで
アプリケーションを実行する前に、次のソフトウェアがインストールされていることを確認してください。
async await構文が使用されるため)pipパッケージをインストールしたPython 2.7:websocket-clientおよびgit+https://github.com/dpallot/simple-websocket-server.git WebSocketサーバーおよびクライアントとして動作するため。curve25519-donnaおよびpycrypto for the Cycryption。pyqrcode 。protobuf 。curve25519-donnaにはMicrosoft Visual C ++ 9.0が必要であり、 stdint.hをC:UsersYOUR USERNAMEAppDataLocalProgramsCommonMicrosoftVisual C++ for Python9.0VCinclude 。アプリケーションを初めて開始する前に、 npm install -f実行して、すべてのPython依存関係のすべてのノードとpip install -r requirements.txtをインストールします。
最後に、最終的に起動するには、LinuxベースのOSおよびnpm run winでnpm startを実行するだけです。 concurrentlyとnodemonマジックを使用して、3つのローカルコンポーネントすべてが互いに開始され、ファイルを編集すると、変更されたモジュールが自動的に再起動して変更を適用します。
最近の追加は、ブラウザー内JavaScriptに翻訳された復号化ルーチンのバージョンです。 node index_jsdemo.jsを実行します(ブラウザがWebSocketのHTTPヘッダーの変更が許可されていないため、必要なだけです)、その後、任意のブラウザで通常のファイルとしてオープンclient/login-via-js-demo.html 。コンソール出力は、QRコードをスキャンした後、復号化されたバイナリメッセージを表示する必要があります。
Adiwahshingは、WhatsApp Web APIを実装するノードライブラリであるBaileysを作成しました。
ndunksは、WAJSでタイプスクリプトの再実装を行いました。
P4KL0NC4Tは、WhatsApp Web APIを実装するPythonパッケージであるKyrosを作成しました。
WhatsAppWeb-RSを使用すると、WiomocはRustのWhatsApp Webクライアントを作成しました。
Rhymenは、WhatsApp Web APIを実装するGo-WhatsAppを作成しました。
Vzaramelは、WhatsAppWeb-Cljを作成しました。
このプロジェクトは、次のように組織されています。使用済みのポートに注意し、アプリケーションを開始する前に他の場所で使用されていないことを確認してください。
WhatsApp Webは、いくつかの異なるアルゴリズムを使用してデータを暗号化します。これらには、AES 256 CBC、Diffie-Hellmanキー契約スキームとしてのCurve25519、拡張共有秘密を生成するためのHKDF、SHA256でHMACが含まれます。
WhatsApp Webセッションの開始wss://w[1-8].web.whatsapp.com/ws ( wss://は、Websocket接続が安全であることを意味します; w[1-8] 1〜8の任意の数がwに従うことができることを意味します)を意味します。また、接続を確立するときに、HTTPヘッダーのOrigin: https://web.whatsapp.comが設定されていることを確認してください。そうしないと、接続が拒否されます。
WhatsApp Web WebSocketにメッセージを送信する場合、特定の形式である必要があります。それは非常にシンプルで、 messageTag,JSON 、たとえば1515590796,["data",123]のように見えます。どうやらメッセージタグは何でもできることに注意してください。このアプリケーションは、主に現在のタイムスタンプをタグとして使用していますが、少しユニークになります。 WhatsApp自体は、 s1 1234.--0などのメッセージタグを使用することがよくあります。明らかに、メッセージタグにはコンマが含まれていない場合があります。さらに、JSONオブジェクトはペイロードと同様に可能です。
Open WebSocketでログインするには、次の手順に従ってください。
clientIdを生成します。このアプリケーションは、Pythonで16のランダムバイト、つまりbase64.b64encode(os.urandom(16))を使用するだけです。messageTag,["admin","init",[0,3,2390],["Long browser description","ShortBrowserDesc"],"clientId",true]のようになります。messageTagとclientId置き換える必要があります[0,3,2390]パートは、現在のWhatsApp Webバージョンを指定しています。最後の値は頻繁に変化します。しかし、それは非常に逆互換性があるはずです。"Long browser description" QRコードをスキャンした後、登録されたWhatsApp WebクライアントのリストのWhatsAppアプリに表示される任意の文字列です。"ShortBrowserDesc"まだどこにも観察されていませんが、同様にarbitrary意的です。status :200でなければなりませんref :アプリケーションでは、これはサーバーIDとして扱われます。 QR生成にとって重要です。以下を参照してくださいttl :20000年です。QRコードが無効になった後の時間かもしれませんupdate :ブールフラグcurr :現在のWhatsApp Webバージョン、Eg 0.2.7314time :サーバーがフローティングポイントミリ秒として応答したタイムスタンプ、例: 1515592039037.0curve25519.Private()で独自の秘密鍵を生成します。privateKey.get_public() 。ref属性base64.b64encode(publicKey.serialize())pyqrcodeを使用するなど)に変換し、whatsappアプリを使用してスキャンします。ttl )。messageTag,["admin","Conn","reref"]を送信して行います。status :200である必要があります(他のもの:304-以前のRef、429 -New Refが拒否されました)ref :new refttl :有効期限Conn :配列には、次の属性などを含む接続情報を含む2番目の要素としてJSONオブジェクトが含まれています。battery :携帯電話の現在のバッテリー率browserToken :アクティブなWebsocket接続なしでログアウトするために使用されます(まだ実装されていません)clientToken :閉じたセッションを再開するために使用されます。phone :携帯電話に関する詳細情報を含むオブジェクト、例: device_manufacturer 、 device_model 、 os_build_number 、 os_versionplatform :携帯電話OS、例えばandroidpushname :WhatsAppを提供したお客様の名前secret (これを覚えておいてください!)serverToken :閉じたセッションの再開に使用されていました。wid :チャット識別形式の電話番号(以下を参照)Stream :配列には合計4つの要素があるため、ペイロード全体は["Stream","update",false,"0.2.7314"]のようなものです。Props :配列には、JSONオブジェクトがimageMaxKBytes (1024)、 maxParticipants (257)、 videoMaxEdge (960)などのいくつかのプロパティを持つ2番目の要素として含まれています。Connからsecret Base64としてデコードし、 secretとして保存することから始めます。このデコードされた秘密は、144バイトの長さになります。sharedSecretと呼びます。アプリケーションprivateKey.get_shared_key(curve25519.Public(secret[:32]), lambda a:a)を使用して行います。sharedSecret 80バイトに拡張します。この値をsharedSecretExpandedと呼びます。HmacSha256(sharedSecretExpanded[32:64], secret[:32] + secret[64:])を計算してそれを行います。この値をsecret[32:64] 。それらが等しくない場合は、ログインを中止します。sharedSecretExpanded[64:] + secret[64:] keysEncryptedとして保存しました。AESDecrypt(sharedSecretExpanded[:32], keysEncrypted) sharedSecretExpanded[:32]を備えたAESを使用してAESを使用して復号化keysDecrypted必要があります。keysDecrypted変数の長さは64バイトで、2つのキーがあり、それぞれ32バイトの長さがあります。 encKey 、WhatsApp Webサーバーによって送信されたバイナリメッセージを復号化するか、サーバーに送信するバイナリメッセージを暗号化するために使用されます。 macKey 、あなたに送信されたメッセージを検証するために必要です。encKey : keysDecrypted[:32]macKey : keysDecrypted[32:64]initコマンドを送信した後、 serverTokenとclientTokenがあるかどうかを確認してください。messageTag,["admin","login","clientToken","serverToken","clientId","takeover"]送信します。{"status": 200}で応答する必要があります。その他のステータス:tosフィールドをチェックしてください:それが2つに等しい場合、または2つ以上の場合、TOSに違反しましたserverTokenおよびclientTokenを使用する場合、あなたはまだ有効な暗号化キーがあることを確認するために挑戦されます。messageTag,["Cmd",{"type":"challenge","challenge":"BASE_64_ENCODED_STRING=="}]のように見えます。challenge文字列をデコードし、マッキーで署名し、base64でエンコードしmessageTag,["admin","challenge","BASE_64_ENCODED_STRING==","serverToken","clientId"]します。{"status": 200}で応答する必要がありますが、何も意味しません。goodbye,,["admin","Conn","disconnect"]送信するだけです。encKeyにmacKeyに署名し、Base64でエンコードします。それがあなたのlogoutTokenだとしましょう。https://dyn.web.whatsapp.com/logout?t=browserToken&m=logoutToken = logouttokenに投稿リクエストを送信します2つのキーができたので、サーバーが送信したメッセージの検証と解読は非常に簡単です。これはバイナリメッセージにのみ必要であり、あなたが受け取るすべてのJSONは平凡なままです。バイナリメッセージは、HMACチェックサムを指定する最初の32バイトを常に備えています。 JSONメッセージとバイナリメッセージの両方に、最初に廃棄できるメッセージタグがあります。つまり、最初のコンマ文字が重要な後の部分のみです。
macKeyでハッシュしてメッセージを検証します(ここでは、 messageContentバイナリメッセージ全体です): HmacSha256(macKey, messageContent[32:]) 。この値がmessageContent[:32]に等しくない場合、サーバーから送信されたメッセージは無効であり、破棄する必要があります。encKey : AESDecrypt(encKey, messageContent[32:])を使用してメッセージコンテンツを復号化します。最終ステップで取得したデータには、以下で説明されているバイナリ形式があります。それはバイナリですが、いくつかの文字列を見ることができます。特に、送信したメッセージの内容は非常に明白です。
pythonスクリプトbackend/decoder.py MessageParserクラスを実装します。データがまだかなり乱雑な方法で編成されているバイナリデータからJSON構造を作成できます。以下のノード処理に関するセクションでは、ノードがその後どのように再編成されるかについて説明します。
MessageParser最初にデータを必要とするだけで、Byte、つまりストリームとしてバイトを処理します。いくつかの定数と、すべてが互いに構築される多くの方法があります。
[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"]-を返します. 11および