ব্লগে ফিরে যান
Security10 min read

পেয়ারিং ফ্লো

এই নিবন্ধে ব্যাখ্যা করা হয়েছে কীভাবে দুটি PlainApp ডিভাইস প্রথমবার বিশ্বাস স্থাপন করে — কীভাবে তারা একে অপরকে আবিষ্কার করে, কী বিনিময় করে, এবং সেই শেয়ার্ড ChaCha20 ট্রান্সপোর্ট কী-এ পৌঁছায় যা পরবর্তীতে প্রতিটি চ্যাট বার্তা, ফাইল ট্রান্সফার এবং প্রেজেন্স পিং এনক্রিপ্ট করতে ব্যবহৃত হয়। এই কী গ্রহণ করে এমন চ্যাট ও চ্যানেল আর্কিটেকচার সম্পর্কে পৃথক Chat Architecture নিবন্ধে আলোচনা করা হয়েছে।

সূচিপত্র

পেয়ারিং কেন প্রয়োজন {#why-pairing-exists}

PlainApp-এ কোনো কেন্দ্রীয় অ্যাকাউন্ট সার্ভার নেই। তাই ডিভাইসগুলোকে কথা বলার আগে দুটি প্রশ্নের উত্তর দিতে হয়:

১. "তুমি কে?" — প্রতিটি ডিভাইস প্রথম লঞ্চে একটি স্থিতিশীল clientId তৈরি করে (তার Ed25519 কী উপাদান থেকে উদ্ভূত একটি ১৩-অক্ষরের আইডি)। এটিই রাউটিং, প্রেজেন্স এবং চ্যানেল মেম্বারশিপের জন্য ব্যবহৃত একমাত্র আইডেন্টিফায়ার। ২. "আমি কি তোমাকে বিশ্বাস করতে পারি?" — পরিচয়ের গ্যারান্টি দেওয়ার জন্য কোনো সার্ভার না থাকলে, নিশ্চিত হওয়ার একমাত্র উপায় হলো একজন মানুষের উভয় ডিভাইসে পেয়ারিং নিশ্চিত করা এবং প্রোটোকলের ক্রিপ্টোগ্রাফিক স্বাক্ষর যাচাই করা।

পেয়ারিং একটি মাত্র আর্টিফ্যাক্ট তৈরি করে: ডাটাবেসে একটি DPeer সারি, যার status="paired", একটি ChaCha20 key (শেয়ার্ড ট্রান্সপোর্ট সিক্রেট) এবং পিয়ারের Ed25519 public_key (ভবিষ্যতে বার্তার স্বাক্ষর যাচাই করার জন্য)। চ্যাট সাবসিস্টেমের প্রতিটি পরবর্তী প্রোটোকল ধরে নেয় যে এই দুটি ফিল্ড বিদ্যমান।

Diagram 1
1

ট্রাস্ট মডেল ও ক্রিপ্টোগ্রাফি {#trust-model--cryptography}

পেয়ারিং দুটি স্বাধীন ক্রিপ্টোগ্রাফিক প্রিমিটিভ ব্যবহার করে:

প্রিমিটিভউদ্দেশ্যলাইফসাইকেল
Ed25519 (স্বাক্ষর)পেয়ারিং রিকোয়েস্ট ও রেসপন্স অথেনটিকেট করা। যাচাই করে যে "এটি সত্যিই সেই ডিভাইস থেকে এসেছে যা পাঠানোর দাবি করছে" এবং রিপ্লে রোধে টাইমস্ট্যাম্প বাইন্ড করে।সাইনিং কীটি ডিভাইসের দীর্ঘমেয়াদী আইডেন্টিটি কী। এর পাবলিক অংশ DPeer.public_key হিসেবে স্টোর করা হয় এবং পরে PeerChatParser.decrypt দ্বারা প্রতিটি চ্যাট বার্তার স্বাক্ষর যাচাই করতে ব্যবহৃত হয়।
X25519-স্টাইল ECDH (কী এগ্রিমেন্ট)একটি শেয়ার্ড সিক্রেট তৈরি করা যা ChaCha20 ট্রান্সপোর্ট কী হয়ে যায়। দুটি ডিভাইস এটি কখনো ট্রান্সমিট না করেই একই সিক্রেট কম্পিউট করে।প্রতি পেয়ারিং সেশনে ইফেমেরাল কী পেয়ার তৈরি করা হয়, শেয়ার্ড কী কম্পিউট হওয়ার পর তাৎক্ষণিকভাবে ডিসকার্ড করা হয়। ফলস্বরূপ ৩২-বাইট সিক্রেটটি DPeer.key হিসেবে স্টোর করা হয় এবং পেয়ারিংয়ের সম্পূর্ণ জীবনকাল ধরে পুনঃব্যবহৃত হয়।

এখানে কোনো PIN নেই, কোনো QR কোড নেই, কোনো আউট-অফ-ব্যান্ড কোড নেই। বিশ্বাস স্থাপিত হয়:

১. রেসপন্ডার ডিভাইসে একজন মানুষের Accept ট্যাপ করা (ব্যবহারকারী দাবি করছেন যে "হ্যাঁ, এটিই সেই ডিভাইস যার সাথে আমি পেয়ার করতে চাই")। ২. উভয় পক্ষ রিকোয়েস্ট/রেসপন্সে অন্যের Ed25519 স্বাক্ষর যাচাই করা (প্রমাণ করে যে রেসপন্ডার সেই একই ডিভাইসের সাথে কথা বলছে যা সেশন শুরু করেছিল এবং বিপরীতক্রমেও)। ৩. উভয় বার্তায় একটি ±৫ মিনিট টাইমস্ট্যাম্প উইন্ডো (পুরোনো ক্যাপচার করা হ্যান্ডশেকের রিপ্লে রোধ)।

এই অসমমিতি গুরুত্বপূর্ণ: একক মানবিক নিশ্চিতকরণ ম্যান-ইন-দ্য-মিডল আক্রমণের জন্য ঝুঁকিপূর্ণ হতো (আক্রমণকারী আলাদাভাবে উভয় পক্ষের সাথে পেয়ার করতে পারত)। ECDH পাবলিক কী-এর উপর Ed25519 স্বাক্ষর এটি রোধ করে — রেসপন্ডার যাচাই করে যে রিকোয়েস্টটি সেই একই Ed25519 কী দ্বারা স্বাক্ষরিত হয়েছে যা সেশন শুরু করেছিল এবং বিপরীতক্রমেও, তাই একজন MITM দীর্ঘমেয়াদী সাইনিং কী নিয়ন্ত্রণ না করে নিজের ECDH কী স্বচ্ছভাবে প্রতিস্থাপন করতে পারে না।

কম্পোনেন্ট ম্যাপ {#component-map}

সমস্ত পেয়ারিং কোড discover/ প্যাকেজে রয়েছে (chat/peer/pair/-এ নয়):

Diagram 2
2

ফাইলের অবস্থান

কম্পোনেন্টPath (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 স্ক্যান-রেসপন্স serviceData থেকেও)। ট্রান্সপোর্ট লেয়ার এগুলো পরামর্শ করে সিদ্ধান্ত নেয় যে Wi-Fi Aware লিংক চেষ্টা করবে নাকি সরাসরি BLE-এ চলে যাবে।

পেয়ারিং সিকোয়েন্স (হ্যাপি পাথ) {#pairing-sequence-happy-path}

যখন উভয় ডিভাইস একই LAN-এ থাকে এবং ব্যবহারকারী পেয়ারিং গ্রহণ করেন, তখন এন্ড-টু-এন্ড ফ্লো:

Diagram 4
4

কেন উভয় পক্ষ স্বাধীনভাবে পিয়ার স্টোর করে

লক্ষ্য করুন যে উভয় ইনিশিয়েটর (ধাপ ৯) এবং রেসপন্ডার (ধাপ ৭) অন্য ডিভাইসের জন্য PairingPeerStore.save(...) কল করে। এটি ইচ্ছাকৃত: প্রতিটি ডিভাইস অন্যটির clientId দ্বারা কীড একটি DPeer সারি পায়, যাতে শেয়ার্ড ChaCha20 কী-এর নিজস্ব কপি এবং অন্যের Ed25519 পাবলিক কী থাকে। কোনো কেন্দ্রীয় রেজিস্ট্রি নেই — পেয়ারিংটি সিমেট্রিক এবং স্বয়ংসম্পূর্ণ।

কেন রেসপন্ডার প্রথমে কী কম্পিউট করে

রেসপন্ডারের acceptPairingRequest গ্রহণের সাথে সাথে শেয়ার্ড কী কম্পিউট করে এবং তা পারসিস্ট করে। এর অর্থ রেসপন্ডার রেসপন্স ইনিশিয়েটরের কাছে ফিরে যাওয়ার আগেই এনক্রিপ্টেড ট্রাফিক গ্রহণ শুরু করতে পারে। যদি রেসপন্স ট্রানজিটে হারিয়ে যায়, রেসপন্ডার এখনও পেয়ার্ড — শুধুমাত্র ইনিশিয়েটরকে পুনঃপ্রচেষ্টা করতে হবে।

কী এক্সচেঞ্জের বিস্তারিত {#key-exchange-details}

পেয়ারিংয়ের ক্রিপ্টোগ্রাফিক কোর হলো একটি স্ট্যান্ডার্ড X25519-স্টাইল ECDH কী এগ্রিমেন্ট, কিন্তু এটি অথেনটিকেট করতে উপরে একটি Ed25519 স্বাক্ষর স্তর যুক্ত করা হয়েছে।

Diagram 5
5

স্বাক্ষর আসলে কী সুরক্ষিত করে

স্বাক্ষরিত পেলোড (toSignatureData()) হলো স্থিতিশীল রিকোয়েস্ট ফিল্ডগুলোর একটি ক্যানোনিক্যাল কনক্যাটেনেশন: fromId, fromName, port, deviceType, ecdhPublicKey, signaturePublicKey, timestamp এবং ips। ecdhPublicKey দীর্ঘমেয়াদী signaturePublicKey-এর সাথে একসাথে স্বাক্ষর করার মাধ্যমে, প্রোটোকল ইফেমেরাল কী-কে ডিভাইসের আইডেন্টিটির সাথে বাইন্ড করে। একজন আক্রমণকারী স্বাক্ষর অকার্যকর না করে ট্রানজিটে নিজের ECDH পাবলিক কী প্রতিস্থাপন করতে পারে না — এবং দীর্ঘমেয়াদী Ed25519 কী নিয়ন্ত্রণ না করে স্বাক্ষর জাল করতে পারে না।

এটিই ম্যান-ইন-দ্য-মিডলকে পরাস্ত করে: আক্রমণকারী দুটি ডিভাইসের মধ্যে প্রতিটি প্যাকেট রিলে করলেও, তারা এনক্রিপ্টেড ট্রাফিক পড়তে পারে না (কারণ তাদের কাছে কোনো পক্ষের ECDH প্রাইভেট কী নেই) এবং নিজের ECDH কী প্রতিস্থাপন করতে পারে না (কারণ স্বাক্ষর ভেঙে যাবে)।

রেসপন্ডার অ্যাকসেপ্ট/ডিক্লাইন ফ্লো {#responder-acceptdecline-flow}

যখন একটি 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-রাউটেড রিকোয়েস্টের জন্য পপুলেট করা হয় এবং রেসপন্স পাঠানোর পর ক্লিয়ার করা হয়।

সেশন ও পিয়ার স্টোরেজ {#session--peer-storage}

পেয়ারিংয়ে দুটি স্টোর অংশগ্রহণ করে, যাদের লাইফটাইম একেবারেই আলাদা:

Diagram 9
9

কেন clientId একমাত্র পারসিস্টেড আইডেন্টিফায়ার

Android প্রতিটি সংযোগে BLE MAC অ্যাড্রেস র্যান্ডমাইজ করে, তাই এটি স্টোর করা অর্থহীন হবে। clientId ডিভাইসের দীর্ঘমেয়াদী Ed25519 কী উপাদান থেকে উদ্ভূত, তাই এটি:

  • অ্যাপ রিইনস্টল জুড়ে স্থিতিশীল (কীটি প্ল্যাটফর্ম কীস্টোরে থাকে)।
  • সেলফ-অথেনটিকেটিং — clientId দাবি করা যেকেউ প্রমাণ করতে হবে যে তার কাছে সংশ্লিষ্ট Ed25519 প্রাইভেট কী আছে (প্রতিটি স্বাক্ষরিত বার্তায় যাচাই করা হয়)।
  • প্রাইভেসি-সংরক্ষণকারী — আবিষ্কারের জন্য BLE-এ শুধুমাত্র একটি ৮-বাইট SHA-256 প্রিফিক্স (shortId) ব্রডকাস্ট করা হয়; সম্পূর্ণ clientId শুধুমাত্র সেই ডিভাইসে প্রকাশ করা হয় যার সাথে আপনি আসলে পেয়ার করেন।

নিরাপত্তা বৈশিষ্ট্য {#security-properties}

বৈশিষ্ট্যকীভাবে অর্জিত
গোপনীয়তাসমস্ত ট্রান্সপোর্ট ECDH-উদ্ভূত শেয়ার্ড কী দিয়ে ChaCha20 এনক্রিপ্ট করা। পেয়ারিংয়ের পর কীটি কখনো দুটি ডিভাইস ছাড়া যায় না।
অথেনটিকেশনপ্রতিটি স্বাক্ষরিত বার্তা (পেয়ারিং রিকোয়েস্ট/রেসপন্স, চ্যাট createChatItem, চ্যানেল invite/update/kick) প্রেরকের স্টোর করা public_key-এর বিরুদ্ধে Ed25519-ভেরিফাইড হয়।
ইন্টিগ্রিটিEd25519 স্বাক্ষর সম্পূর্ণ রিকোয়েস্ট বডি কভার করে; যেকোনো ট্যাম্পারিং স্বাক্ষর অকার্যকর করে।
রিপ্লে প্রতিরোধ±5 min timestamp window (PeerChatParser এবং PairingSecurity দ্বারা এনফোর্স করা)। ChatMessageReceiver.seenSignatures উইন্ডোর মধ্যে ডিডাপ করে।
ম্যান-ইন-দ্য-মিডল প্রতিরোধইফেমেরাল ECDH পাবলিক কী দীর্ঘমেয়াদী Ed25519 পাবলিক কী-এর সাথে একসাথে স্বাক্ষরিত। একজন MITM স্বাক্ষর না ভেঙে নিজের ECDH কী প্রতিস্থাপন করতে পারে না।
ফরোয়ার্ড সিক্রেসি (সীমিত)ECDH কী পেয়ার প্রতি পেয়ারিং সেশনে ইফেমেরাল। পরে দীর্ঘমেয়াদী Ed25519 কী কম্প্রোমাইজ করা হলেও অতীত ট্রাফিক ডিক্রিপ্ট করতে পারে না (শেয়ার্ড কীও এখনও প্রয়োজন — কিন্তু যদি উভয় ECDH প্রাইভেট কী এবং স্টোর করা DPeer.key মুছে ফেলা হয়, অতীত ক্যাপচার ডিক্রিপ্ট করা যায় না)।
ডিনায়াল-অফ-সার্ভিস প্রতিরোধonDatagram প্রতিটি বার্তাকে try/catch-এ মোড়ে যাতে একটি ত্রুটিপূর্ণ প্যাকেট ডিসকভারি রিসিভারকে বন্ধ করতে না পারে। PeerCircuitBreaker ২টি ব্যর্থতার পর ৩০ সেকেন্ডের জন্য একটি অস্থিতিশীল ট্রান্সপোর্ট স্কিপ করে।
প্রাইভেসি (ডিরেক্টেড ডিসকভারি)LANDiscoverManager.discoverSpecificDevice টার্গেট clientId পিয়ারের কী দিয়ে এনক্রিপ্ট করে — প্যাসিভ LAN অবজারভাররা কে কার সাথে পেয়ার করেছে তা জানতে পারে না।
আইডেন্টিটি স্থিতিশীলতাclientId প্ল্যাটফর্ম কীস্টোরে দীর্ঘমেয়াদী Ed25519 কী উপাদান থেকে উদ্ভূত — রিইনস্টল জুড়ে স্থিতিশীল, সেলফ-অথেনটিকেটিং, এবং ফোন নম্বর বা ইমেইলের সাথে আবদ্ধ নয়।

পেয়ারিং যা থেকে রক্ষা করে না

  • ফিজিক্যাল ডিভাইস কম্প্রোমাইজ। যদি কোনো আক্রমণকারী পেয়ার্ড ডিভাইসে root অর্জন করে, তবে তারা ডাটাবেস থেকে শেয়ার্ড কী পড়তে পারে এবং সেই পিয়ারের ছদ্মবেশ ধারণ করতে পারে। শেয়ার্ড ট্রান্সপোর্ট কী-এর জন্য কোনো হার্ডওয়্যার-ব্যাকড কী স্টোর এনফোর্সমেন্ট নেই (শুধুমাত্র Ed25519 সাইনিং কী-এর জন্য, SignatureHelper-এর মাধ্যমে)।
  • অ্যাকটিভ রিলে আক্রমণ। একজন আক্রমণকারী যে দুটি ডিভাইসের মধ্যে BLE এবং LAN ট্রাফিক একইসাথে রিলে করতে পারে (যারা ভাবছে তারা একে অপরের সাথে পেয়ার করছে) তাত্ত্বিকভাবে নিজেকে মাঝখানে স্থাপন করতে পারে — কিন্তু ECDH পাবলিক কী-এর Ed25519 স্বাক্ষরের অর্থ হলো তারা ট্রাফিক পড়তে পারে না, শুধু রিলে করতে পারে। এটি numeric comparison ছাড়া Bluetooth পেয়ারিংয়ের মতো একই ট্রেড-অফ।
  • নেটওয়ার্ক-লেভেল ব্লকিং। একটি ফায়ারওয়াল UDP মাল্টিকাস্ট ব্লক করতে পারে, BLE জ্যাম করা যেতে পারে, এবং Wi-Fi Aware অনুপলব্ধ হতে পারে। সিস্টেমটি গ্রেসফুলি ডিগ্রেড করে (পেয়ার্ড পিয়ারদের জন্য BLE গ্যারান্টিড ফলব্যাক) কিন্তু একটি সক্রিয়ভাবে শত্রু নেটওয়ার্ক বাইপাস করতে পারে না।

স্টেট মেশিন সারাংশ {#state-machine-recap}

Diagram 10
10

আরও পড়ার জন্য

  • Chat Architecture — শেয়ার্ড কী কী কাজে ব্যবহৃত হয়: পিয়ার চ্যাট সেন্ড/রিসিভ, চ্যানেল ফ্যান-আউট, প্রেজেন্স, ফাইল ডাউনলোড।
  • apitest/groups/discovery.sh — এক্সিকিউটেবল টেস্ট প্ল্যান যা ডিসকভারি এবং পেয়ারিং API সারফেস এন্ড-টু-এন্ড অনুশীলন করে।