ब्लॉग पर वापस जाएँ
Security10 min read

पेयरिंग प्रवाह

यह लेख बताता है कि दो PlainApp डिवाइस पहली बार आपस में विश्वास कैसे स्थापित करते हैं — वे एक-दूसरे को कैसे खोजते हैं, कुंजी का आदान-प्रदान कैसे करते हैं, और उस साझा ChaCha20 ट्रांसपोर्ट कुंजी तक कैसे पहुँचते हैं जिससे बाद में हर चैट संदेश, फ़ाइल स्थानांतरण और प्रेज़ेंस पिंग कूटलेखित (encrypted) होते हैं। इस कुंजी का उपयोग करने वाली चैट और चैनल आर्किटेक्चर अलग Chat Architecture लेख में कवर की गई है।

विषय-सूची

पेयरिंग क्यों आवश्यक है {#why-pairing-exists}

PlainApp में कोई केंद्रीय अकाउंट सर्वर नहीं है। इसलिए डिवाइसों को बात करने से पहले दो सवालों का जवाब देना होता है:

  1. "तुम कौन हो?" — हर डिवाइस पहली बार लॉन्च होने पर एक स्थिर clientId बनाता है (13-अक्षर का आईडी जो इसके Ed25519 की सामग्री से व्युत्पन्न होता है)। यही एकमात्र पहचानकर्ता है जिसका उपयोग राउटिंग, प्रेज़ेंस और चैनल सदस्यता के लिए किया जाता है।
  2. "क्या मैं तुम पर भरोसा कर सकता हूँ?" — पहचान की पुष्टि के लिए बिना किसी सर्वर के, यह सुनिश्चित करने का एकमात्र तरीका कि पीयर वही है जो वह दावा करता है, यह है कि कोई इंसान दोनों डिवाइस पर पेयरिंग की पुष्टि करे और प्रोटोकॉल क्रिप्टोग्राफ़िक हस्ताक्षरों को सत्यापित करे।

पेयरिंग एक एकल आर्टिफ़ैक्ट तैयार करती है: डेटाबेस में एक DPeer पंक्ति जिसमें status="paired", एक ChaCha20 key (साझा ट्रांसपोर्ट रहस्य), और पीयर की Ed25519 public_key (भविष्य के संदेश हस्ताक्षरों को सत्यापित करने के लिए) होते हैं। चैट सबसिस्टम का हर बाद का प्रोटोकॉल मान लेता है कि ये दोनों फ़ील्ड मौजूद हैं।

Diagram 1
1

ट्रस्ट मॉडल और क्रिप्टोग्राफी

पेयरिंग दो स्वतंत्र क्रिप्टोग्राफ़िक प्रिमिटिव का उपयोग करती है:

PrimitivePurposeLifecycle
Ed25519 (signature)पेयरिंग अनुरोध और प्रतिक्रिया को प्रमाणित करें। यह सत्यापित करता है कि "यह वाकई उसी डिवाइस से आया है जो भेजने का दावा करता है" और रीप्ले से बचने के लिए टाइमस्टैम्प को बाँधता है।साइनिंग कुंजी डिवाइस की दीर्घकालिक पहचान कुंजी है। इसका सार्वजनिक भाग DPeer.public_key के रूप में संग्रहीत होता है और बाद में हर चैट संदेश हस्ताक्षर सत्यापित करने के लिए PeerChatParser.decrypt द्वारा उपयोग होता है।
X25519-style ECDH (key agreement)एक साझा रहस्य तैयार करें जो ChaCha20 ट्रांसपोर्ट कुंजी बन जाए। दोनों डिवाइस उसी रहस्य की गणना करते हैं बिना उसे कभी प्रेषित किए।प्रति पेयरिंग सत्र एक अल्पकालिक की-पेयर बनाया जाता है, और साझा कुंजी गणना होने के तुरंत बाद त्याग दिया जाता है। परिणामी 32-बाइट रहस्य DPeer.key के रूप में संग्रहीत होता है और पेयरिंग के जीवनकाल तक पुनः उपयोग होता है।

इसमें कोई PIN नहीं, कोई QR कोड नहीं, कोई आउट-ऑफ-बैंड कोड नहीं है। विश्वास कैसे स्थापित होता है:

  1. कोई इंसान रिस्पॉन्डर डिवाइस पर Accept टैप करता है (उपयोगकर्ता यह दावा कर रहा है कि "हाँ, यही वह डिवाइस है जिससे मैं पेयर करना चाहता हूँ")।
  2. दोनों पक्ष अनुरोध/प्रतिक्रिया पर एक-दूसरे के Ed25519 हस्ताक्षर को सत्यापित करते हैं (यह सिद्ध करते हुए कि रिस्पॉन्डर उसी डिवाइस से बात कर रहा है जिसने सत्र शुरू किया था और इसके विपरीत)।
  3. दोनों संदेशों पर ±5 मिनट का टाइमस्टैम्प विंडो (पुराने कैप्चर किए गए हैंडशेक के रीप्ले से बचाव)।

यह असममितता महत्वपूर्ण है: एकमात्र मानवीय पुष्टि मैन-इन-द-मिडिल के प्रति संवेदनशील होगी (हमलावर अलग-अलग दोनों पक्षों से पेयर कर सकता था)। ECDH सार्वजनिक कुंजी पर Ed25519 हस्ताक्षर इसे रोकता है — रिस्पॉन्डर सत्यापित करता है कि अनुरोध उसी Ed25519 कुंजी द्वारा हस्ताक्षरित था जिसने सत्र शुरू किया, और इसके विपरीत, इसलिए कोई MITM अपनी खुद की ECDH कुंजी पारदर्शी रूप से प्रतिस्थापित नहीं कर सकता बिना दीर्घकालिक साइनिंग कुंजी को नियंत्रित किए।

कंपोनेंट मानचित्र {#component-map}

सभी पेयरिंग कोड discover/ पैकेज में रहता है (chat/peer/pair/ में नहीं):

Diagram 2
2

फ़ाइल स्थान

ComponentPath (under shared/src/commonMain/kotlin/com/ismartcoding/plain/)
LANDiscoverManagerdiscover/LANDiscoverManager.kt
PairingCorediscover/PairingCore.kt
PairingInitiatordiscover/PairingInitiator.kt
PairingResponderdiscover/PairingResponder.kt
PairingSecuritydiscover/PairingSecurity.kt
PairingSessionStorediscover/PairingSessionStore.kt
PairingPeerStorediscover/PairingPeerStore.kt
PairingMessengerdiscover/PairingMessenger.kt

डिस्कवरी चरण {#discovery-phase}

पेयरिंग होने से पहले, डिवाइसों को एक-दूसरे को खोजना होगा। LANDiscoverManager ऐप शुरू होते ही निरंतर चलता रहता है:

Diagram 3
3

डायरेक्टेड डिस्कवरी क्यों कूटलेखित है

ब्रॉडकास्ट DISCOVER कुछ भी संवेदनशील प्रकट नहीं करता (केवल fromId=clientId), इसलिए LAN पर किसी भी डिवाइस का इसे देखना ठीक है। हालाँकि, डायरेक्टेड वेरिएंट तब उपयोग होता है जब एक डिवाइस पहले से किसी दूसरे के clientId को जानता है (जैसे वह पेयर्ड है लेकिन पीयर का IP बदल गया है) और उसे जगाना चाहता है। लक्ष्य clientId को पीयर की साझा कुंजी से कूटलेखित करने का अर्थ है:

  • सही पीयर toId को डिक्रिप्ट कर सकता है, खुद को पहचान सकता है, और उत्तर दे सकता है।
  • LAN पर हर दूसरा डिवाइस केवल सिफरटेक्स्ट देखता है — वे यह नहीं गिन सकते कि भेजने वाला किन clientId तक पहुँचने का प्रयास कर रहा है।

यह एक छोटा लेकिन वास्तविक गोपनीयता गुण है: निष्क्रिय LAN पर्यवेक्षक यह नहीं बना सकते कि कौन किसके साथ पेयर्ड है।

रिप्लाई में Aware फ़्लैग्स

DISCOVER_REPLY में awareSupported और awareRunning शामिल होते हैं। ये डेटाबेस में संग्रहीत नहीं होते — ये PeerCacher में इन-मेमोरी रहते हैं और हर रिप्लाई पर रिफ़्रेश होते हैं (और BLE scan-response serviceData से भी)। ट्रांसपोर्ट परत इनसे परामर्श करती है ताकि तय कर सके कि Wi-Fi Aware लिंक का प्रयास करें या सीधे BLE पर चले जाएँ।

पेयरिंग अनुक्रम (सुखद पथ)

जब दोनों डिवाइस एक ही LAN पर हों और उपयोगकर्ता पेयरिंग स्वीकार कर ले, तो अंत-से-अंत प्रवाह:

Diagram 4
4

दोनों पक्ष स्वतंत्र रूप से पीयर क्यों संग्रहित करते हैं

ध्यान दें कि दोनों — इनिशिएटर (चरण 9) और रिस्पॉन्डर (चरण 7) — दूसरे डिवाइस के लिए PairingPeerStore.save(...) को कॉल करते हैं। यह जानबूझकर है: हर डिवाइस के पास एक DPeer पंक्ति बन जाती है जो दूसरे के clientId द्वारा की-ड है, जिसमें साझा ChaCha20 कुंजी की अपनी प्रति और दूसरे की Ed25519 सार्वजनिक कुंजी होती है। कोई केंद्रीय रजिस्ट्री नहीं है — पेयरिंग सममित और स्व-निहित है।

रिस्पॉन्डर कुंजी पहले क्यों गणना करता है

रिस्पॉन्डर का acceptPairingRequest स्वीकार होते ही साझा कुंजी गणना करता है और उसे स्थायी कर देता है। इसका अर्थ है कि रिस्पॉन्डर कूटलेखित ट्रैफ़िक प्राप्त करना शुरू कर सकता है इससे पहले कि प्रतिक्रिया इनिशिएटर तक वापस पहुँचे। यदि प्रतिक्रिया ट्रांज़िट में खो जाती है, तब भी रिस्पॉन्डर पेयर्ड है — केवल इनिशिएटर को पुनः प्रयास करना होगा।

की एक्सचेंज विवरण

पेयरिंग का क्रिप्टोग्राफ़िक क्रोड़ एक मानक X25519-style ECDH की समझौता है, लेकिन उस पर एक Ed25519 हस्ताक्षर जोड़ा गया है ताकि उसे प्रमाणित किया जा सके।

Diagram 5
5

हस्ताक्षर वास्तव में क्या सुरक्षित करता है

हस्ताक्षरित पेलोड (toSignatureData()) स्थिर अनुरोध फ़ील्ड्स का एक विहित संयोजन है: fromId, fromName, port, deviceType, ecdhPublicKey, signaturePublicKey, timestamp, और ipsecdhPublicKey को दीर्घकालिक signaturePublicKey के साथ हस्ताक्षर करके, प्रोटोकॉल अल्पकालिक कुंजी को डिवाइस की पहचान से बाँधता है। कोई हमलावर ट्रांज़िट में अपनी ECDH सार्वजनिक कुंजी को हस्ताक्षर को अमान्य किए बिना प्रतिस्थापित नहीं कर सकता — और दीर्घकालिक Ed25519 कुंजी को नियंत्रित किए बिना वे हस्ताक्षर जाली नहीं कर सकते।

यही मैन-इन-द-मिडिल को हराता है: भले ही हमलावर दोनों डिवाइस के बीच हर पैकेट रिले करे, वे कूटलेखित ट्रैफ़िक को नहीं पढ़ सकते (क्योंकि उनके पास किसी भी पक्ष की ECDH निजी कुंजी नहीं है) और अपनी ECDH कुंजी प्रतिस्थापित नहीं कर सकते (क्योंकि हस्ताक्षर टूट जाएँगे)।

रिस्पॉन्डर स्वीकार/अस्वीकार प्रवाह

जब PAIR_REQUEST आता है तो रिस्पॉन्डर पक्ष एक UI संवाद दिखाता है। उपयोगकर्ता या तो स्वीकार या अस्वीकार कर सकता है।

Diagram 6
6

रिस्पॉन्डर स्वीकार होते ही PairingSuccessEvent क्यों फायर करता है

रिस्पॉन्डर का acceptPairingRequest प्रतिक्रिया भेजने से पहले PairingPeerStore.save(...) को कॉल करता है और PairingSuccessEvent फायर करता है। यह जानबूझकर है: यदि प्रतिक्रिया इनिशिएटर तक कभी नहीं पहुँचती (नेटवर्क गड़बड़), तब भी रिस्पॉन्डर पेयर्ड है — अगली बार जब इनिशिएटर पेयर करने का प्रयास करेगा, रिस्पॉन्डर की पहले से मौजूद DPeer पंक्ति प्रेज़ेंस सिस्टम द्वारा उठा ली जाएगी। इनिशिएटर बस पुनः प्रयास करता है; रिस्पॉन्डर को फिर से पुष्टि करने की आवश्यकता नहीं।

रद्द प्रवाह {#cancel-flow}

कोई भी पक्ष चल रही पेयरिंग को रद्द कर सकता है।

Diagram 7
7

ध्यान दें कि DPairingCancel केवल LAN unicast पर भेजा जाता है (इनिशिएटर के पास डिस्कवरी चरण से रिस्पॉन्डर का IP पहले से है), जबकि अस्वीकार प्रतिक्रिया दोनों LAN और BLE पर भेजी जाती है क्योंकि रिस्पॉन्डर निश्चित नहीं होता कि इनिशिएटर किस ट्रांसपोर्ट पर पहुँच योग्य है।

द्वि-चैनल वितरण (LAN + BLE) {#dual-channel-delivery-lan--ble}

जब रिस्पॉन्डर DPairingResponse भेजता है, तो वह दोनों LAN और BLE पर एक साथ ऐसा करता है। इनिशिएटर पहली प्रतिलिपि स्वीकार करता है और डुप्लिकेट को चुपचाप त्याग देता है।

Diagram 8
8

BlePairingSessionStore क्यों मौजूद है

जब PAIR_REQUEST BLE पर आता है, तो रिस्पॉन्डर के पास इनिशिएटर का LAN IP नहीं होता — केवल उसका BLE MAC पता। BlePairingSessionStore peerId → MAC मैप करता है ताकि प्रतिक्रिया को आवश्यक होने पर BLE पर वापस रूट किया जा सके। यह एक छोटा, इन-मेमोरी, अल्पकालिक मानचित्र है जो केवल BLE-रूटेड अनुरोधों के लिए आबाद होता है और प्रतिक्रिया भेज देने पर साफ़ कर दिया जाता है।

सत्र और पीयर स्टोरेज

पेयरिंग में दो स्टोर भाग लेते हैं, जिनके जीवनकाल बहुत अलग हैं:

Diagram 9
9

clientId ही एकमात्र स्थायी पहचानकर्ता क्यों है

Android हर कनेक्शन पर BLE MAC पता यादृच्छिक बना देता है, इसलिए इसे संग्रहित करना व्यर्थ होगा। clientId डिवाइस की दीर्घकालिक Ed25519 की सामग्री से व्युत्पन्न होता है, इसलिए यह है:

  • स्थिर — ऐप पुनःस्थापना (reinstall) पर भी (कुंजी प्लेटफ़ॉर्म कीस्टोर में है)।
  • स्व-प्रमाणित — जो कोई भी clientId का दावा करता है उसे यह सिद्ध करना होगा कि वह संबंधित Ed25519 निजी कुंजी रखता है (हर हस्ताक्षरित संदेश पर सत्यापित)।
  • गोपनीयता-संरक्षित — केवल 8-बाइट SHA-256 उपसर्ग (shortId) ही डिस्कवरी के लिए BLE पर प्रसारित होता है; पूर्ण clientId केवल उन डिवाइस को प्रकट होता है जिनके साथ आप वास्तव में पेयर करते हैं।

सुरक्षा गुण {#security-properties}

PropertyHow it's achieved
Confidentialityसभी ट्रांसपोर्ट ECDH-व्युत्पन्न साझा कुंजी से ChaCha20 कूटलेखित हैं। कुंजी पेयरिंग के बाद दोनों डिवाइस से कभी बाहर नहीं जाती।
Authenticationहर हस्ताक्षरित संदेश (पेयरिंग अनुरोध/प्रतिक्रिया, चैट createChatItem, चैनल invite/update/kick) भेजने वाले की संग्रहीत public_key के विरुद्ध Ed25519-सत्यापित होता है।
IntegrityEd25519 हस्ताक्षर पूर्ण अनुरोध बॉडी को कवर करते हैं; कोई भी छेड़छाड़ हस्ताक्षर को अमान्य कर देती है।
Replay resistance±5 min timestamp window (PeerChatParser और PairingSecurity द्वारा लागू)। ChatMessageReceiver.seenSignatures विंडो के भीतर डुप्लिकेट हटाता है।
Man-in-the-middle resistanceअल्पकालिक ECDH सार्वजनिक कुंजी दीर्घकालिक Ed25519 सार्वजनिक कुंजी के साथ हस्ताक्षरित है। कोई MITM हस्ताक्षर तोड़े बिना अपनी ECDH कुंजी प्रतिस्थापित नहीं कर सकता।
Forward secrecy (limited)ECDH की-पेयर प्रति पेयरिंग सत्र अल्पकालिक होते हैं। दीर्घकालिक Ed25519 कुंजी का बाद में समझौता होने पर भी पिछला ट्रैफ़िक डिक्रिप्ट नहीं होता (साझा कुंजी भी आवश्यक है — लेकिन यदि दोनों ECDH निजी कुंजी और संग्रहीत DPeer.key मिटा दी जाएँ, तो पिछले कैप्चर डिक्रिप्ट नहीं हो सकते)।
Denial-of-service resistanceonDatagram हर संदेश को try/catch में लपेटता है ताकि कोई विकृत पैकेट डिस्कवरी रिसीवर को मार न सके। PeerCircuitBreaker 2 विफलताओं के बाद 30 सेकंड के लिए एक अविश्वसनीय ट्रांसपोर्ट को छोड़ देता है।
Privacy (directed discovery)LANDiscoverManager.discoverSpecificDevice लक्ष्य clientId को पीयर की कुंजी से कूटलेखित करता है — निष्क्रिय LAN पर्यवेक्षक यह नहीं गिन सकते कि कौन किसके साथ पेयर्ड है।
Identity stabilityclientId प्लेटफ़ॉर्म कीस्टोर में दीर्घकालिक Ed25519 की सामग्री से व्युत्पन्न है — पुनःस्थापना पर स्थिर, स्व-प्रमाणित, और फ़ोन नंबर या ईमेल से बँधा नहीं।

पेयरिंग किसके विरुद्ध बचाव नहीं करती

  • भौतिक डिवाइस समझौता (compromise)। यदि कोई हमलावर पेयर्ड डिवाइस पर root प्राप्त कर ले, तो वे साझा कुंजी को डेटाबेस से पढ़ सकते हैं और उस पीयर की नकल कर सकते हैं। साझा ट्रांसपोर्ट कुंजी के लिए कोई हार्डवेयर-समर्थित की-स्टोर प्रवर्तन नहीं है (केवल Ed25519 साइनिंग कुंजी के लिए, SignatureHelper के माध्यम से)।
  • सक्रिय रिले हमले। एक हमलावर जो दो डिवाइस के बीच BLE और LAN ट्रैफ़िक को एक साथ रिले कर सकता है जो सोचते हैं कि वे एक-दूसरे से पेयर कर रहे हैं, सैद्धांतिक रूप से खुद को बीच में स्थापित कर सकता है — लेकिन ECDH सार्वजनिक कुंजी पर Ed25519 हस्ताक्षर का अर्थ है कि वे ट्रैफ़िक को नहीं पढ़ सकते, केवल रिले कर सकते हैं। यह वही तुलन है जो बिना न्यूमेरिक तुलना के ब्लूटूथ पेयरिंग में होता है।
  • नेटवर्क-स्तरीय अवरोधन। एक फ़ायरवॉल UDP मल्टीकास्ट को ब्लॉक कर सकता है, BLE को जाम किया जा सकता है, और Wi-Fi Aware अनुपलब्ध हो सकता है। सिस्टम उत्तम रूप से गिरावट करता है (पेयर्ड पीयर के लिए BLE गारंटीड फ़ॉलबैक है) लेकिन सक्रिय रूप से शत्रुतापूर्ण नेटवर्क को बायपास नहीं कर सकता।

स्टेट मशीन सारांश {#state-machine-recap}

Diagram 10
10

आगे पढ़ने के लिए

  • Chat Architecture — साझा कुंजी का किसके लिए उपयोग होता है: पीयर चैट भेजना/प्राप्त करना, चैनल फ़ैन-आउट, प्रेज़ेंस, फ़ाइल डाउनलोड।
  • apitest/groups/discovery.sh — निष्पादन योग्य टेस्ट प्लान जो डिस्कवरी और पेयरिंग API सतह को अंत-से-अंत अभ्यास करता है।