PyGOST लाइब्रेरी (शुद्ध पाइथन में GOST क्रिप्टोग्राफिक
प्राइमरी) के एक डेवलपर के रूप में, मुझे अक्सर अपने घुटने पर सबसे सरल सुरक्षित संदेश को लागू करने के तरीके के बारे में प्रश्न मिलते हैं। कई लोग लागू क्रिप्टोग्राफी को काफी सरल चीज मानते हैं और एक संचार चैनल पर सुरक्षित रूप से भेजने के लिए एक ब्लॉक सिफर के लिए एक .encrypt () कॉल पर्याप्त होगा। दूसरों का मानना है कि लागू क्रिप्टोग्राफी कुछ की नियति है, और यह स्वीकार्य है कि टेलिग्राम जैसी धनी कंपनियां गणित ऑलिम्पीड्स
एक सुरक्षित प्रोटोकॉल को
लागू नहीं कर सकती हैं ।
इस सबने मुझे यह लेख लिखने के लिए प्रेरित किया कि यह दिखाने के लिए कि क्रिप्टोग्राफ़िक प्रोटोकॉल और सुरक्षित आईएम का कार्यान्वयन इतना मुश्किल काम नहीं है। हालांकि, अपने स्वयं के प्रमाणीकरण और प्रमुख समझौते प्रोटोकॉल का आविष्कार करना इसके लायक नहीं है।
लेख विशेष रूप से GOST क्रिप्टोग्राफिक एल्गोरिदम PyGOST पुस्तकालय और
ASD.1 PyDERASN लाइब्रेरी के साथ संदेशों की एन्कोडिंग (जिसके बारे में मैंने पहले ही
लिखा था )। पूर्वापेक्षा: यह इतना सरल होना चाहिए कि इसे एक शाम (या कार्य दिवस) में खरोंच से लिखा जा सके, अन्यथा यह अब एक सरल कार्यक्रम नहीं है। संभवतः इसमें त्रुटियां, अनावश्यक कठिनाइयाँ, कमियाँ हैं, साथ ही यह एसिंको लाइब्रेरी का उपयोग करके मेरा पहला कार्यक्रम है।
डिजाइन आईएम
आरंभ करने के लिए, आपको यह समझने की आवश्यकता है कि हमारा आईएम कैसा दिखेगा। सादगी के लिए, प्रतिभागियों की किसी भी खोज के बिना, यह सहकर्मी से सहकर्मी नेटवर्क होने दें। हम व्यक्तिगत रूप से किस पते पर इंगित करेंगे: वार्ताकार से संवाद करने के लिए पोर्ट।
मैं समझता हूं कि इस समय, दो मनमाने कंप्यूटरों के बीच प्रत्यक्ष संचार की उपलब्धता की धारणा व्यवहार में आईएम की प्रयोज्यता का एक महत्वपूर्ण सीमा है। लेकिन अधिक डेवलपर्स सभी प्रकार के NAT-traversal बैसाखी को लागू करेंगे, अब हम IPv4 इंटरनेट पर बने रहेंगे, मनमाने ढंग से कंप्यूटर के बीच संचार की निराशाजनक संभावना के साथ। ठीक है, आप घर और काम पर आईपीवी 6 की कमी को कितना सहन कर सकते हैं?
हमारे पास एक मित्र-से-मित्र नेटवर्क होगा: सभी संभव वार्ताकारों को पहले से जाना जाना चाहिए। सबसे पहले, यह सब कुछ बहुत सरल करता है: खुद को पेश किया, पाया या नहीं मिला एक नाम / कुंजी, डिस्कनेक्ट या काम करना जारी रखना, वार्ताकार को जानना। दूसरे, सामान्य मामले में, यह सुरक्षित है और कई हमलों को बाहर करता है।
IM इंटरफ़ेस
बेकार परियोजनाओं के क्लासिक समाधानों के करीब होगा, जो मुझे उनके अतिसूक्ष्मवाद और यूनिक्स-मार्ग दर्शन के लिए वास्तव में पसंद है। प्रत्येक इंटरस्क्यूटर के लिए एक IM प्रोग्राम तीन यूनिक्स डोमेन सॉकेट के साथ एक निर्देशिका बनाता है:
- में - वार्ताकार को भेजे गए संदेश इसमें दर्ज किए गए हैं;
- बाहर - वार्ताकार से प्राप्त संदेश इससे पढ़े जाते हैं;
- राज्य - इसे पढ़ने से, हम यह पता लगाएंगे कि क्या इंटरलाक्यूटर अब जुड़ा हुआ है, कनेक्शन पता / पोर्ट।
इसके अलावा, एक कॉन सॉकेट बनाया जाता है, जिसे होस्ट पोर्ट पर लिखते हुए, हम एक रिमोट इंटरलाक्यूटर से कनेक्शन शुरू करते हैं।
| - ऐलिस
| | - में
| | - बाहर
| `- राज्य
| - बॉब
| | - में
| | - बाहर
| `- राज्य
`- कॉन
यह दृष्टिकोण आपको आईएम परिवहन और उपयोगकर्ता इंटरफ़ेस के स्वतंत्र कार्यान्वयन करने की अनुमति देता है, क्योंकि स्वाद और रंग के लिए कोई दोस्त नहीं है, आप सभी को खुश नहीं करेंगे।
Tmux और / या
मल्टीटैल का उपयोग करके, आप सिंटैक्स हाइलाइटिंग के साथ एक मल्टी-विंडो इंटरफ़ेस प्राप्त कर सकते हैं। और
rlwrap के साथ
, आप संदेशों को दर्ज करने के लिए GNU Readline- संगत स्ट्रिंग प्राप्त कर सकते हैं।
वास्तव में, बेकार प्रोजेक्ट FIFO फ़ाइलों का उपयोग करते हैं। व्यक्तिगत रूप से, मैं समझ नहीं पा रहा था कि चयनित थ्रेड्स से हाथ से बने सब्सट्रेट के बिना प्रतिस्पर्धा में फ़ाइलों के साथ काम करने के लिए एसिंको में कैसे काम कर रहा हूं (मैं लंबे समय से ऐसी चीजों के लिए
गो भाषा का उपयोग कर रहा हूं)। इसलिए, मैंने यूनिक्स डोमेन सॉकेट के साथ मिलने का फैसला किया। दुर्भाग्य से, यह 2001 इको करना असंभव बनाता है: 470: मृत :: बेब 6666> कॉन। मैंने इस समस्या को हल करते हुए
समाज का उपयोग किया: गूंज 2001: 470: डेड :: बेब 6666 | सोसाइटी - UNIX-CONNECT: कॉन, सोसाइटी READLINE UNIX-CONNECT: alice / in।
प्रारंभिक असुरक्षित प्रोटोकॉल
टीसीपी का उपयोग परिवहन के रूप में किया जाता है: यह वितरण और उसके आदेश की गारंटी देता है। यूडीपी गारंटी देता है कि न तो कोई और न ही (जो क्रिप्टोग्राफी लागू होने पर उपयोगी होगा), और पायथन में
एससीटीपी समर्थन बॉक्स से बाहर है।
दुर्भाग्य से, टीसीपी में एक संदेश की कोई अवधारणा नहीं है, लेकिन केवल बाइट्स की एक धारा है। इसलिए, संदेशों के लिए एक प्रारूप के साथ आना आवश्यक है ताकि उन्हें इस धारा में आपस में साझा किया जा सके। हम लाइन फीड चरित्र का उपयोग करने के लिए सहमत हो सकते हैं। शुरुआत के लिए, यह उपयुक्त है, हालांकि, जब हम अपने संदेशों को एन्क्रिप्ट करना शुरू करते हैं, तो यह प्रतीक सिफरटेक्स्ट में कहीं भी दिखाई दे सकता है। इसलिए, प्रोटोकॉल नेटवर्क पर लोकप्रिय हैं, संदेश की पहली लंबाई बाइट्स में भेजते हैं। उदाहरण के लिए, पायथन में, बॉक्स से बाहर xdrlib है, जो आपको एक समान
XDR प्रारूप के साथ काम करने की अनुमति देता है।
हम टीसीपी पढ़ने के साथ सही और कुशलता से काम नहीं करेंगे - हम कोड को सरल करते हैं। हम एक अंतहीन लूप में सॉकेट से डेटा पढ़ते हैं जब तक कि हम पूरा संदेश डिकोड नहीं करते। आप इस दृष्टिकोण के लिए प्रारूप के रूप में XML के साथ JSON का भी उपयोग कर सकते हैं। लेकिन जब क्रिप्टोग्राफी को जोड़ा जाता है, तो डेटा को हस्ताक्षरित और प्रमाणित करना होगा - और इसके लिए बाइट-बाय-बाइट समान वस्तुओं के समान प्रतिनिधित्व की आवश्यकता होगी, जो JSON / XML प्रदान नहीं करता है (डंप भिन्न हो सकते हैं)।
XDR इस तरह के कार्य के लिए उपयुक्त है, हालांकि, मैं DER एन्कोडिंग और
PyDERASN लाइब्रेरी के साथ ASN.1 को
चुनता हूं , क्योंकि हमारे पास हाथ पर उच्च-स्तरीय ऑब्जेक्ट होंगे, जिनके साथ काम करने के लिए अक्सर अधिक सुखद और सुविधाजनक होते हैं। स्कीमालेस
बेंकोडोड ,
मैसेजपैक , या
सीबीओआर के विपरीत , ASN.1 स्वचालित रूप से हार्ड-कोडेड स्कीम के खिलाफ डेटा को मान्य करेगा।
प्राप्त संदेश Msg होगा: या तो एक पाठ MsgText (अब तक एक पाठ क्षेत्र के साथ) या एक हाथ मिलाने का संदेश MsgHandshake (जिसमें वार्ताकार का नाम प्रसारित होता है)। अब यह अधूरा दिखता है, लेकिन यह भविष्य के लिए एक चुनौती है।
┌─────┐ ┌─────┐
│PeerA│ │PeerB│
└──┬──┘ └──┬──┘
)MsgHandshake (IdA) H
│ >> │
│ │
)MsgHandshake (IdB) H
─────────────────│ <─────────────────│
│ │
Ext MsgText () ext
│ >> │
│ │
Ext MsgText () ext
─────────────────│ <─────────────────│
│ │
क्रिप्टोग्राफी के बिना आईएम
जैसा कि मैंने कहा, सॉकेट के साथ सभी कार्यों के लिए एसिंसीओ लाइब्रेरी का उपयोग किया जाएगा। लॉन्च के समय हम उम्मीद करते हैं:
parser = argparse.ArgumentParser(description="GOSTIM") parser.add_argument( "--our-name", required=True, help="Our peer name", ) parser.add_argument( "--their-names", required=True, help="Their peer names, comma-separated", ) parser.add_argument( "--bind", default="::1", help="Address to listen on", ) parser.add_argument( "--port", type=int, default=6666, help="Port to listen on", ) args = parser.parse_args() OUR_NAME = UTF8String(args.our_name) THEIR_NAMES = set(args.their_names.split(","))
अपना खुद का नाम (--our-name alice) सेट करें। अल्पविराम सभी अपेक्षित वार्ताकारों को सूचीबद्ध करता है (- उनका नाम-नाम बॉब, पूर्व संध्या)। वार्ताकारों में से प्रत्येक के लिए, यूनिक्स सॉकेट्स के साथ एक निर्देशिका बनाई जाती है, साथ ही प्रत्येक के लिए, बाहर, राज्य:
for peer_name in THEIR_NAMES: makedirs(peer_name, mode=0o700, exist_ok=True) out_queue = asyncio.Queue() OUT_QUEUES[peer_name] = out_queue asyncio.ensure_future(asyncio.start_unix_server( partial(unixsock_out_processor, out_queue=out_queue), path.join(peer_name, "out"), )) in_queue = asyncio.Queue() IN_QUEUES[peer_name] = in_queue asyncio.ensure_future(asyncio.start_unix_server( partial(unixsock_in_processor, in_queue=in_queue), path.join(peer_name, "in"), )) asyncio.ensure_future(asyncio.start_unix_server( partial(unixsock_state_processor, peer_name=peer_name), path.join(peer_name, "state"), )) asyncio.ensure_future(asyncio.start_unix_server(unixsock_conn_processor, "conn"))
उपयोगकर्ता से सॉकेट में संदेश कतार में भेजे जाते हैं IN_QUEUES:
async def unixsock_in_processor(reader, writer, in_queue: asyncio.Queue) -> None: while True: text = await reader.read(MaxTextLen) if text == b"": break await in_queue.put(text.decode("utf-8"))
वार्ताकारों के संदेश OUT_QUEUES कतार में भेजे जाते हैं, जहाँ से डेटा आउट सॉकेट को लिखा जाता है:
async def unixsock_out_processor(reader, writer, out_queue: asyncio.Queue) -> None: while True: text = await out_queue.get() writer.write(("[%s] %s" % (datetime.now(), text)).encode("utf-8")) await writer.drain()
जब राज्य सॉकेट से पढ़ते हैं, तो प्रोग्राम वार्ताकार के पते के लिए PEER_ALIVE शब्दकोश में दिखता है। यदि वार्ताकार का अभी तक कोई संबंध नहीं है, तो एक खाली लाइन लिखी जाती है।
async def unixsock_state_processor(reader, writer, peer_name: str) -> None: peer_writer = PEER_ALIVES.get(peer_name) writer.write( b"" if peer_writer is None else (" ".join([ str(i) for i in peer_writer.get_extra_info("peername")[:2] ]).encode("utf-8") + b"\n") ) await writer.drain() writer.close()
जब एक पता कॉन सॉकेट को लिखा जाता है, तो कनेक्शन का "आरंभकर्ता" फ़ंक्शन लॉन्च किया जाता है:
async def unixsock_conn_processor(reader, writer) -> None: data = await reader.read(256) writer.close() host, port = data.decode("utf-8").split(" ") await initiator(host=host, port=int(port))
सर्जक पर विचार करें। सबसे पहले, वह स्पष्ट रूप से निर्दिष्ट होस्ट / पोर्ट से एक कनेक्शन खोलता है और अपने नाम के साथ एक हैंडशेक संदेश भेजता है:
130 async def initiator(host, port): 131 _id = repr((host, port)) 132 logging.info("%s: dialing", _id) 133 reader, writer = await asyncio.open_connection(host, port) 134
फिर यह दूर की ओर से प्रतिक्रिया की प्रतीक्षा करता है। Msg ASN.1 योजना के अनुसार प्राप्त प्रतिक्रिया को डिकोड करने का प्रयास। हम मानते हैं कि संपूर्ण संदेश एक टीसीपी सेगमेंट द्वारा भेजा जाएगा और हम इसे .read () कहे जाने पर प्राप्त करेंगे। हम सत्यापित करते हैं कि हमें बिल्कुल हैंडशेक संदेश मिला है।
141
हम सत्यापित करते हैं कि हम जिस व्यक्ति से बात कर रहे हैं उसका नाम हमें ज्ञात है। यदि नहीं, तो कनेक्शन तोड़ दें। हम जांचते हैं कि क्या हमने पहले ही उसके साथ एक संबंध स्थापित कर लिया है (वार्ताकार ने फिर से हमें कनेक्ट करने की आज्ञा दी है) और इसे बंद कर दें। संदेश पाठ के साथ पायथन स्ट्रिंग्स को IN_QUEUES कतार में रखा गया है, लेकिन एक विशेष मूल्य कोई भी नहीं है, जो msg_sender को कोरटाइन को काम करने से रोकने के लिए संकेत देता है ताकि वह अपने लेखक के बारे में भूल जाए, जो एक पुराने टीसीपी कनेक्शन से जुड़ा है।
159 msg_handshake = msg.value 160 peer_name = str(msg_handshake["peerName"]) 161 if peer_name not in THEIR_NAMES: 162 logging.warning("unknown peer name: %s", peer_name) 163 writer.close() 164 return 165 logging.info("%s: session established: %s", _id, peer_name) 166
msg_sender आउटगोइंग संदेशों (सॉकेट में से कतारबद्ध) को स्वीकार करता है, उन्हें MsgText संदेश में अनुक्रमित करता है, और उन्हें टीसीपी कनेक्शन भेजता है। यह किसी भी क्षण टूट सकता है - हम इसे स्पष्ट रूप से रोक रहे हैं।
async def msg_sender(peer_name: str, writer) -> None: in_queue = IN_QUEUES[peer_name] while True: text = await in_queue.get() if text is None: break writer.write(Msg(("text", MsgText(( ("text", UTF8String(text)), )))).encode()) try: await writer.drain() except ConnectionResetError: del PEER_ALIVES[peer_name] return logging.info("%s: sent %d characters message", peer_name, len(text))
अंत में, आरंभकर्ता सॉकेट से संदेशों को पढ़ने के एक अंतहीन चक्र में प्रवेश करता है। जाँचता है कि क्या यह एक पाठ संदेश है, और OUT_QUEUES में वह स्थान है जहाँ से उन्हें संबंधित वार्ताकार के आउट सॉकेट पर भेजा जाएगा। आप केवल .read () और संदेश को डिकोड क्यों नहीं कर सकते? क्योंकि यह संभव है कि उपयोगकर्ता के कई संदेश ऑपरेटिंग सिस्टम के बफर में एकत्र किए जाएंगे और एक टीसीपी सेगमेंट द्वारा भेजे जाएंगे। हम पहले एक को डीकोड कर सकते हैं, और उसके बाद वाले हिस्से का हिस्सा बफर में रह सकता है। किसी भी आपात स्थिति में, हम TCP कनेक्शन को बंद करते हैं और msg_sender coroutine (OUT_QUEUES कतार में कोई नहीं भेजकर) को रोकते हैं।
174 buf = b"" 175
आइए मुख्य कोड पर वापस जाएं। सभी coroutines बनाने के बाद, जिस समय प्रोग्राम शुरू होता है, हम TCP सर्वर शुरू करते हैं। प्रत्येक स्थापित कनेक्शन के लिए, वह एक उत्तरवर्ती कोरटाइन बनाता है।
logging.basicConfig( level=logging.INFO, format="%(levelname)s %(asctime)s: %(funcName)s: %(message)s", ) loop = asyncio.get_event_loop() server = loop.run_until_complete(asyncio.start_server(responder, args.bind, args.port)) logging.info("Listening on: %s", server.sockets[0].getsockname()) loop.run_forever()
उत्तरदाता सर्जक के समान है और सभी समान कार्यों को प्रतिबिंबित करता है, लेकिन पढ़ने के संदेशों का एक अंतहीन लूप सरलता के लिए तुरंत शुरू होता है। अब हैंडशेक प्रोटोकॉल प्रत्येक पक्ष से एक संदेश भेजता है, लेकिन भविष्य में, कनेक्शन के आरंभकर्ता से दो होंगे, जिसके बाद पाठ संदेश तुरंत भेजे जा सकते हैं।
72 async def responder(reader, writer): 73 _id = writer.get_extra_info("peername") 74 logging.info("%s: connected", _id) 75 buf = b"" 76 msg_expected = "handshake" 77 peer_name = None 78 while True: 79
सुरक्षित प्रोटोकॉल
हमारे संचार को सुरक्षित करने का समय आ गया है। हमें सुरक्षा से क्या मतलब है और हम क्या चाहते हैं:
- प्रेषित संदेशों की गोपनीयता;
- प्रेषित संदेशों की प्रामाणिकता और अखंडता - उनके परिवर्तन का पता लगाना चाहिए;
- रिप्ले हमलों के खिलाफ सुरक्षा - तथ्य यह है कि संदेश खो गए हैं या पीछे हट गए हैं, का पता लगाया जाना चाहिए (और हम डिस्कनेक्ट करने का निर्णय लेते हैं);
- पूर्व-चालित सार्वजनिक कुंजियों द्वारा वार्ताकारों की पहचान और प्रमाणीकरण - हमने पहले ही तय कर लिया है कि हम मित्र-से-मित्र नेटवर्क बना रहे हैं। प्रमाणीकरण के बाद ही हम समझ पाएंगे कि हम किसके साथ संवाद कर रहे हैं;
- परिपूर्ण फ़ॉरवर्ड सीक्रेसी प्रॉपर्टीज़ (PFS) की उपस्थिति - हमारी लंबे समय से चली आ रही हस्ताक्षर कुंजी का समझौता पिछले सभी पत्राचार को पढ़ने की संभावना की ओर नहीं ले जाना चाहिए। इंटरसेप्टेड ट्रैफ़िक रिकॉर्ड करना बेकार हो जाता है;
- केवल टीसीपी सत्र के भीतर संदेशों (परिवहन और हैंडशेक) की वैधता / वैधता। एक और सत्र से (एक ही वार्ताकार के साथ) सही ढंग से हस्ताक्षरित / प्रमाणित संदेशों का सम्मिलन संभव नहीं होना चाहिए;
- निष्क्रिय पर्यवेक्षक को उपयोगकर्ता पहचानकर्ताओं को नहीं देखना चाहिए, लंबे समय तक सार्वजनिक कुंजी प्रेषित की जानी चाहिए, न ही उनसे हैश। एक निष्क्रिय पर्यवेक्षक से किसी प्रकार का गुमनामी।
हैरानी की बात है, लगभग हर कोई चाहता है कि यह किसी भी हैंडशेक प्रोटोकॉल में कम से कम हो, और उपरोक्त में से बहुत कम अंततः घर-विकसित प्रोटोकॉल के लिए किए जाते हैं। इसलिए अब हम नई चीजों का आविष्कार नहीं करेंगे। मैं निश्चित रूप से प्रोटोकॉल बनाने के लिए
शोर ढांचे का उपयोग करने की सलाह दूंगा, लेकिन चलो कुछ सरल चुनें।
सबसे लोकप्रिय दो प्रोटोकॉल हैं:
- टीएलएस एक जटिल प्रोटोकॉल है जिसमें बग, स्कूलों, कमजोरियों, खराब विचार, जटिलता और कमियों का एक लंबा इतिहास है (हालांकि, यह टीएलएस 1.3 पर बहुत अधिक लागू नहीं होता है)। लेकिन हम इसे जटिलता के कारण नहीं मानते हैं।
- IKE के साथ IPsec - गंभीर क्रिप्टोग्राफ़िक समस्याएं नहीं हैं, हालांकि वे भी सरल नहीं हैं। यदि आप IKEv1 और IKEv2 के बारे में पढ़ते हैं, तो उनका स्रोत STS , ISO / IEC IS 9798-3 और SIGMA (SIGn-and-MAc) प्रोटोकॉल हैं - एक शाम में लागू करने के लिए पर्याप्त सरल।
SIGMA, STS / ISO प्रोटोकॉल के विकास में अंतिम कड़ी के रूप में कैसे अच्छा है? यह हमारी सभी आवश्यकताओं को संतुष्ट करता है (वार्ताकारों के पहचानकर्ताओं को "छिपाना" सहित), क्रिप्टोग्राफ़िक समस्याओं को नहीं जानता है। यह न्यूनतर है - प्रोटोकॉल संदेश से कम से कम एक तत्व को हटाने से इसकी असुरक्षा हो जाएगी।
आइए सरलतम होमग्रोन प्रोटोकॉल से सिग्मा पर जाएं। सबसे बुनियादी ऑपरेशन जो हम में रुचि रखते हैं,
कुंजी मिलान है : एक समारोह जिसमें आउटपुट दोनों प्रतिभागियों को समान मूल्य प्राप्त होगा जो कि एक सममित कुंजी के रूप में उपयोग किया जा सकता है। विवरण में जाने के बिना: प्रत्येक पक्ष एक पंचांग (केवल एक ही सत्र के भीतर प्रयुक्त) कुंजी जोड़ी (सार्वजनिक और निजी कुंजी) उत्पन्न करता है, सार्वजनिक कुंजियों का आदान-प्रदान करता है, मेल मिलाप समारोह को कॉल करता है, जिसके इनपुट पर वे अपनी निजी कुंजी और वार्ताकार की सार्वजनिक कुंजी संचारित करते हैं।
┌─────┐ ┌─────┐
│PeerA│ │PeerB│
└──┬──┘ └──┬──┘
│ इडा, पूबा P P
│────────────── >> │────────────── rPrvA, PubA = DHgen () │
╚═══════════════════╝ ╚═══════════════════╝ ╚═══════════════════╝
│ आईडीबी, पबबी P P
│ <│ ║PrvB, PubB = DHgen () ───────────────│
╚═══════════════════╝ ╚═══════════════════╝ ╚═══════════════════╝
────┐ ────┐
Ey Pकेय = डीएच (पीआरवीए, पबबी) ey
<╚═══════╤═══════════╝ ╚═══════╤═══════════╝
│ │
│ │
कोई भी बीच में हस्तक्षेप कर सकता है और सार्वजनिक कुंजी को अपने स्वयं के साथ बदल सकता है - इस प्रोटोकॉल में वार्ताकारों का कोई प्रमाणीकरण नहीं है। लंबे समय तक रहने वाली चाबियों के साथ एक हस्ताक्षर जोड़ें।
┌─────┐ ┌─────┐
│PeerA│ │PeerB│
└──┬──┘ └──┬──┘
│इडा, पब, साइन (साइनप्रवा, (पब)) │,
│─────────────────────────────────> │ ║SignPrvA, SignPubA = लोड () ║
│ ║ │PrvA, PubA = DHgen () ║
╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══
│IdB, PubB, साइन (SignPrvB, (PubB)),,
│ <│ PSignPrvB, SignPubB = load () ─────────────────────────────────│
│ ║ rPrvB, PubB = DHgen () ║
╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══
│ │ │
│ │verify (SignPubB, ...) ify ify
<<=Key = DH (PrvA, PubB) ║ ║
│ │ │
│ │
ऐसा हस्ताक्षर काम नहीं करेगा, क्योंकि यह एक विशिष्ट सत्र से बंधा नहीं है। ऐसे संदेश अन्य प्रतिभागियों के साथ सत्र के लिए भी उपयुक्त हैं। पूरे संदर्भ को सब्सक्राइब किया जाना चाहिए। यह A के दूसरे संदेश को जोड़ने के लिए भी बाध्य करता है।
इसके अलावा, अपने स्वयं के पहचानकर्ता को हस्ताक्षर के रूप में जोड़ना महत्वपूर्ण है, क्योंकि, अन्यथा, हम IdXXX को प्रतिस्थापित कर सकते हैं और संदेश को एक अन्य प्रसिद्ध वार्ताकार की कुंजी के साथ फिर से हस्ताक्षर कर सकते हैं।
प्रतिबिंब के हमलों को रोकने के लिए, यह आवश्यक है कि हस्ताक्षर के तहत तत्व उनके अर्थ में स्पष्ट रूप से परिभाषित स्थानों पर हों: यदि कोई संकेत (PUBA, PubB) है, तो B को हस्ताक्षर करना होगा (PubB, PubA)। यह क्रमबद्ध डेटा की संरचना और प्रारूप को चुनने के महत्व को भी इंगित करता है। उदाहरण के लिए, ASN.1 DER एन्कोडिंग में सेट को सॉर्ट किया जाता है: SET OF (PUBA, PUBB) SET OF (PUBB, PUBA) के समान होगा।
┌─────┐ ┌─────┐
│PeerA│ │PeerB│
└──┬──┘ └──┬──┘
│ इडा, पूबा P P
│────────────────────────────────────────────> │ ║SignPrvA, साइनपुब्बा = लोड () load
│ ║ │PrvA, PubA = DHgen () ║
╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══
│IdB, PubB, साइन (SignPrvB, (IdB, PUBA, PubB)),,
│ <──────────────────────────────────────────── PSignPrvB, साइनपब = लोड () load
│ ║ rPrvB, PubB = DHgen () ║
╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══
│ साइन (SignPrvA, (IdA, PubB, PUBA)) P P
─ ─ │ ─ ─ if> ify ify ify ify ify ify ify ify ify ify ify ify ify ify ify ify ify साइनपब, ...) ...
│ v │Key = DH (PrvA, PubB) ║
╚══ ╚══ │ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══
│ │
हालाँकि, हम अभी भी "सिद्ध" नहीं हुए हैं कि हमने इस सत्र के लिए समान सामान्य कुंजी विकसित की है। सिद्धांत रूप में, आप इस कदम के बिना कर सकते हैं - पहला परिवहन कनेक्शन अमान्य होगा, लेकिन हम चाहते हैं कि जब हैंडशेक पूरा हो जाए, तो हमें यकीन होगा कि सब कुछ वास्तव में सहमत है। फिलहाल, हमारे हाथ में ISO / IEC IS 9798-3 प्रोटोकॉल है।
हम कुंजी पर ही हस्ताक्षर कर सकते थे। यह खतरनाक है, क्योंकि यह संभव है कि उपयोग किए गए हस्ताक्षर एल्गोरिदम में लीक हो सकते हैं (बिट्स-प्रति-हस्ताक्षर, लेकिन अभी भी लीक हैं)। आप जेनरेट की की से हैश साइन कर सकते हैं, लेकिन जेनरेट की से एक हैश लीक भी जेनरेशन फंक्शन पर ब्रूट-फोर्स अटैक में वैल्यू का हो सकता है। सिग्मा एक मैक फ़ंक्शन का उपयोग करता है जो प्रेषक आईडी को प्रमाणित करता है।
┌─────┐ ┌─────┐
│PeerA│ │PeerB│
└──┬──┘ └──┬──┘
│ इडा, पूबा P P
─ ─ ─ ─ ─ ─ ─ ─ ─ ── ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ झाइयों वाले बालों वाली कटाई के समय से हटकर तैयार करें। > ║ PSignPrvA, SignPubA = लोड () ║
│ ║ │PrvA, PubA = DHgen () ║
╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══
SignIdB, PubB, साइन (SignPrvB, (PUBA, PubB)), MAC (IdB),,
─── <│ ─ ─── ─── ─── ─── ─── ─── ─── ─── ─── ─── ─│ ─│SignPrvB, SignPubB = लोड () ign
│ ║ rPrvB, PubB = DHgen () ║
╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══
╔══ ╔══ │ ╔══ ╔══ ╔══ ╔══ ╔══ ╔══ ╔══ ╔══ ╔══ ╔══
│ साइन (SignPrvA, (PubB, PUBA)), मैक (IdA) = PrKey = DH (PrvA, PubB) P
─ ─ ─ ─ ─ ─ ─ ─ ─ ── ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ झाइयों वाले बालों वाली कटाई के समय से हटकर तैयार करें। > ║ │verify (की, IdB) ║
║ B (verify (SignPubB, ...) ║
╚══ ╚══ │ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══
│ │
अनुकूलन के रूप में, कुछ अपनी अल्पकालिक कुंजियों का पुन: उपयोग करना चाहते हैं (जो निश्चित रूप से, पीएफएस के लिए बहुत ही कम है)। उदाहरण के लिए, हमने एक कुंजी जोड़ी बनाई, कनेक्ट करने की कोशिश की, लेकिन टीसीपी उपलब्ध नहीं था या प्रोटोकॉल के बीच में कहीं से टूट गया। यह एक नई जोड़ी पर खर्च किए गए एन्ट्रापी और प्रोसेसर संसाधनों को खर्च करने के लिए एक दया है। इसलिए, हम तथाकथित कुकी का परिचय देते हैं - एक छद्म यादृच्छिक मूल्य जो कि पंचांग सार्वजनिक कुंजियों का पुन: उपयोग करते समय संभावित आकस्मिक पुनरावृत्ति हमलों से बचाएगा। कुकी और अल्पकालिक सार्वजनिक कुंजी के बीच बाध्यकारी होने के कारण, विपरीत पक्ष की सार्वजनिक कुंजी को अनावश्यक के रूप में हस्ताक्षर से हटाया जा सकता है।
┌─────┐ ┌─────┐
│PeerA│ │PeerB│
└──┬──┘ └──┬──┘
│ आईडीए, पबए, कुकी ╔═════════════════════════ P
─ ─ ─ ─ ─ ─ ─ ─ ─ ── ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ झाइयों वाले बालों वाली कटाई के समय से हटकर तैयार करें। ──────────────────── >> │ P SignPrvA, SignPubA = load () │
│ ║ │PrvA, PubA = DHgen () ║
╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══
│IdB, PubB, कुकी, साइन (SignPrvB, (कुकी, कुकी, PubB)), MAC (IdB),, ═══╗
─── <│ ─ ─── ─── ─── ─── ─── ─── ─── ─── ─── ─── ──────────────────────│ ──────────────────────│SignPrvB, SignPubB = लोड () ign
│ ║ rPrvB, PubB = DHgen () ║
╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══
╔══ ╔══ │ ╔══ ╔══ ╔══ ╔══ ╔══ ╔══ ╔══ ╔══ ╔══ ╔══
│ साइन (SignPrvA, (CookieB, कुकी, PUBA)), MAC (IdA) A =Key = DH (PrvA, PubB) P
─│─ Y ify ify ify ify ify ify ify ify ify वेरीफाई करें (की, IdB) y
║ B (verify (SignPubB, ...) ║
╚══ ╚══ │ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══ ╚══
│ │
अंत में, हम एक निष्क्रिय पर्यवेक्षक से हमारे वार्ताकार पहचानकर्ताओं की गोपनीयता प्राप्त करना चाहते हैं। ऐसा करने के लिए, SIGMA पहली बार अल्पकालिक कुंजियों का आदान-प्रदान करने का सुझाव देता है, जिससे एक सामान्य कुंजी काम करती है, जिस पर प्रमाणीकरण संदेशों को प्रमाणित किया जाता है। सिग्मा दो विकल्पों का वर्णन करता है:
- सिग्मा- I - सक्रिय हमले से सर्जक की रक्षा करता है, निष्क्रिय लोगों से प्रतिक्रिया करता है: सर्जक उत्तरदाता को प्रमाणित करता है और यदि कुछ फिट नहीं होता है, तो वह अपनी पहचान नहीं देता है। यदि आप उसके साथ सक्रिय प्रोटोकॉल शुरू करते हैं तो प्रतिवादी अपनी पहचान देता है। निष्क्रिय पर्यवेक्षक को कुछ भी नहीं पता होगा;
सिग्मा-आर - सक्रिय हमलों से प्रतिक्रियाकर्ता की रक्षा करता है, सर्जक निष्क्रिय से। सब कुछ बिल्कुल विपरीत है, लेकिन इस प्रोटोकॉल में पहले से ही चार हैंडशेक संदेश प्रसारित होते हैं।
हम SIGMA-I को सामान्य सर्वर-क्लाइंट चीजों से जितनी उम्मीद करते हैं, उसके समान चुनते हैं: केवल एक प्रमाणित सर्वर क्लाइंट को पहचानता है, और हर कोई सर्वर को वैसे भी जानता है। साथ ही कम हैंडशेक संदेशों के कारण इसे लागू करना आसान है। हम सभी को प्रोटोकॉल में जोड़ते हैं संदेश भाग का एन्क्रिप्शन और पहचानकर्ता का अंतिम संदेश के एन्क्रिप्टेड भाग में स्थानांतरण होता है:
┌─────┐ ┌─────┐
│PeerA│ │PeerB│
└──┬──┘ └──┬──┘
│ पूबा, कुकी │,
─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ── ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ म सगन्सन्स के लिये ─ ─ ─ ── ─ ─ ─ ─ ─ इग्निशन इग्निशन इग्निशन इग्नोर इग्नोर इग्नोर इग्नोर, इग्नोर ,,,,, साइन इन करें साइन इन करें साइन इन करें साइन इन करें साइन लोड लोड लोड लोड लोड लोड करें (और (((((((
│ ║ │PrvA, PubA = DHgen () ║
│ │ ╚═══════════════════════════╝
│PubB, CookieB, Enc((IdB, sign(SignPrvB, (CookieA, CookieB, PubB)), MAC(IdB))) │ ╔═══════════════════════════╗
│<─────────────────────────────────────────────────────────────────────────────│ ║SignPrvB, SignPubB = load()║
│ │ ║PrvB, PubB = DHgen() ║
│ │ ╚═══════════════════════════╝
│ │ ╔═════════════════════╗
│ Enc((IdA, sign(SignPrvA, (CookieB, CookieA, PubA)), MAC(IdA))) │ ║Key = DH(PrvA, PubB) ║
│─────────────────────────────────────────────────────────────────────────────>│ ║verify(Key, IdB) ║
│ │ ║verify(SignPubB, ...)║
│ │ ╚═════════════════════╝
│ │
- 34.10-2012 256- .
- 34.10-2012 VKO.
- MAC CMAC. , 34.13-2015. — (34.12-2015).
- . -256 (34.11-2012 256 ).
. . : , , (MAC) , . , , . , , ? . , KDF (key derivation function). , - : HKDF , . , Python , hkdf . HKDF HMAC , , , -. Python Wikipedia . 34.10-2012, - -256. , :
kdf = Hkdf(None, key_session, hash=GOST34112012256) kdf.expand(b"handshake1-mac-identity") kdf.expand(b"handshake1-enc") kdf.expand(b"handshake1-mac") kdf.expand(b"handshake2-mac-identity") kdf.expand(b"handshake2-enc") kdf.expand(b"handshake2-mac") kdf.expand(b"transport-initiator-enc") kdf.expand(b"transport-initiator-mac") kdf.expand(b"transport-responder-enc") kdf.expand(b"transport-responder-mac")
/
ASN.1 :
class Msg(Choice): schema = (( ("text", MsgText()), ("handshake0", MsgHandshake0(expl=tag_ctxc(0))), ("handshake1", MsgHandshake1(expl=tag_ctxc(1))), ("handshake2", MsgHandshake2(expl=tag_ctxc(2))), )) class MsgText(Sequence): schema = (( ("payload", MsgTextPayload()), ("payloadMac", MAC()), )) class MsgTextPayload(Sequence): schema = (( ("nonce", Integer(bounds=(0, float("+inf")))), ("ciphertext", OctetString(bounds=(1, MaxTextLen))), )) class MsgHandshake0(Sequence): schema = (( ("cookieInitiator", Cookie()), ("pubKeyInitiator", PubKey()), )) class MsgHandshake1(Sequence): schema = (( ("cookieResponder", Cookie()), ("pubKeyResponder", PubKey()), ("ukm", OctetString(bounds=(8, 8))), ("ciphertext", OctetString()), ("ciphertextMac", MAC()), )) class MsgHandshake2(Sequence): schema = (( ("ciphertext", OctetString()), ("ciphertextMac", MAC()), )) class HandshakeTBE(Sequence): schema = (( ("identity", OctetString(bounds=(32, 32))), ("signature", OctetString(bounds=(64, 64))), ("identityMac", MAC()), )) class HandshakeTBS(Sequence): schema = (( ("cookieTheir", Cookie()), ("cookieOur", Cookie()), ("pubKeyOur", PubKey()), )) class Cookie(OctetString): bounds = (16, 16) class PubKey(OctetString): bounds = (64, 64) class MAC(OctetString): bounds = (16, 16)
HandshakeTBS — , (to be signed). HandshakeTBE — , (to be encrypted). ukm MsgHandshake1. 34.10 VKO, , UKM (user keying material) — .
, ( , , ).
, - . JSON :
{ "our": { "prv": "21254cf66c15e0226ef2669ceee46c87b575f37f9000272f408d0c9283355f98", "pub": "938c87da5c55b27b7f332d91b202dbef2540979d6ceaa4c35f1b5bfca6df47df0bdae0d3d82beac83cec3e353939489d9981b7eb7a3c58b71df2212d556312a1" }, "their": { "alice": "d361a59c25d2ca5a05d21f31168609deeec100570ac98f540416778c93b2c7402fd92640731a707ec67b5410a0feae5b78aeec93c4a455a17570a84f2bc21fce", "bob": "aade1207dd85ecd283272e7b69c078d5fae75b6e141f7649ad21962042d643512c28a2dbdc12c7ba40eb704af920919511180c18f4d17e07d7f5acd49787224a" } }
our — , . their — . JSON :
from pygost import gost3410 from pygost.gost34112012256 import GOST34112012256 CURVE = gost3410.GOST3410Curve( *gost3410.CURVE_PARAMS["GostR3410_2001_CryptoPro_A_ParamSet"] ) parser = argparse.ArgumentParser(description="GOSTIM") parser.add_argument( "--keys-gen", action="store_true", help="Generate JSON with our new keypair", ) parser.add_argument( "--keys", default="keys.json", required=False, help="JSON with our and their keys", ) parser.add_argument( "--bind", default="::1", help="Address to listen on", ) parser.add_argument( "--port", type=int, default=6666, help="Port to listen on", ) args = parser.parse_args() if args.keys_gen: prv_raw = urandom(32) pub = gost3410.public_key(CURVE, gost3410.prv_unmarshal(prv_raw)) pub_raw = gost3410.pub_marshal(pub) print(json.dumps({ "our": {"prv": hexenc(prv_raw), "pub": hexenc(pub_raw)}, "their": {}, })) exit(0)
34.10 — . 256- 256- . PyGOST , , (urandom(32)) , gost3410.prv_unmarshal(). , gost3410.public_key(). 34.10 — , , gost3410.pub_marshal().
JSON , , , , gost3410.pub_unmarshal(). , . -256 gost34112012256.GOST34112012256(), hashlib -.
? , : cookie (128- ), 34.10, VKO .
395 async def initiator(host, port): 396 _id = repr((host, port)) 397 logging.info("%s: dialing", _id) 398 reader, writer = await asyncio.open_connection(host, port) 399
423 logging.info("%s: got %s message", _id, msg.choice) 424 if msg.choice != "handshake1": 425 logging.warning("%s: unexpected message, disconnecting", _id) 426 writer.close() 427 return 428
UKM 64- (urandom(8)), , gost3410_vko.ukm_unmarshal(). VKO 34.10-2012 256- gost3410_vko.kek_34102012256() (KEK — key encryption key).
256- . HKDF . GOST34112012256 hashlib , Hkdf . ( Hkdf) , - . kdf.expand() 256-, .
TBE TBS :
- MAC ;
- ;
- TBE ;
- ;
- MAC ;
- TBS , cookie . .
441 try: 442 peer_name = validate_tbe( 443 msg_handshake1, 444 key_handshake1_mac_identity, 445 key_handshake1_enc, 446 key_handshake1_mac, 447 cookie_our, 448 cookie_their, 449 pub_their_raw, 450 ) 451 except ValueError as err: 452 logging.warning("%s: %s, disconnecting", _id, err) 453 writer.close() 454 return 455
, 34.13-2015 34.12-2015. , MAC-. PyGOST gost3413.mac(). ( ), , , . hardcode- ? 34.12-2015 128- , 64- — 28147-89, .
gost.3412.GOST3412Kuznechik(key) .encrypt()/.decrypt() , 34.13 . MAC : gost3413.mac(GOST3412Kuznechik(key).encrypt, KUZNECHIK_BLOCKSIZE, ciphertext). MAC- (==) , , , , BEAST TLS. Python hmac.compare_digest .
. , , . 34.13-2015 : ECB, CTR, OFB, CBC, CFB. . , ( CCM, OCB, GCM ) — MAC. (CTR): , , , ( CBC, ).
.mac(), .ctr() : ciphertext = gost3413.ctr(GOST3412Kuznechik(key).encrypt, KUZNECHIK_BLOCKSIZE, plaintext, iv). , . ( ), . handshake .
gost3410.verify() : ( GOSTIM ), ( , , ), 34.11-2012 .
, handshake2 , , : , .…
456
, ( , , ), MAC-:
499
msg_sender , TCP-. nonce, . .
async def msg_sender(peer_name: str, key_enc: bytes, key_mac: bytes, writer) -> None: nonce = 0 encrypter = GOST3412Kuznechik(key_enc).encrypt macer = GOST3412Kuznechik(key_mac).encrypt in_queue = IN_QUEUES[peer_name] while True: text = await in_queue.get() if text is None: break ciphertext = ctr( encrypter, KUZNECHIK_BLOCKSIZE, text.encode("utf-8"), long2bytes(nonce, 8), ) payload = MsgTextPayload(( ("nonce", Integer(nonce)), ("ciphertext", OctetString(ciphertext)), )) mac_tag = mac(macer, KUZNECHIK_BLOCKSIZE, payload.encode()) writer.write(Msg(("text", MsgText(( ("payload", payload), ("payloadMac", MAC(mac_tag)), )))).encode()) nonce += 1
msg_receiver, :
async def msg_receiver( msg_text: MsgText, nonce_expected: int, macer, encrypter, peer_name: str, ) -> None: payload = msg_text["payload"] if int(payload["nonce"]) != nonce_expected: raise ValueError("unexpected nonce value") mac_tag = mac(macer, KUZNECHIK_BLOCKSIZE, payload.encode()) if not compare_digest(mac_tag, bytes(msg_text["payloadMac"])): raise ValueError("invalid MAC") plaintext = ctr( encrypter, KUZNECHIK_BLOCKSIZE, bytes(payload["ciphertext"]), long2bytes(nonce_expected, 8), ) text = plaintext.decode("utf-8") await OUT_QUEUES[peer_name].put(text)
निष्कर्ष
GOSTIM ( , )! (-256 : 995bbd368c04e50a481d138c5fa2e43ec7c89bc77743ba8dbabee1fde45de120). , GoGOST , PyDERASN , NNCP , GoVPN , GOSTIM , GPLv3+ .
, , , Python/Go-, « „“ .