تعلم الذهاب: كتابة رسول P2P مع التشفير نهاية إلى نهاية

بعد P2P رسول آخر


لا تعد قراءة المراجعات والوثائق اللغوية كافية لتعلم كيفية كتابة تطبيقات أكثر أو أقل فائدة.


تأكد من الدمج ، تحتاج إلى إنشاء شيء مثير للاهتمام حتى يمكن استخدام التطورات في مهام أخرى.


ReactJs الدردشة واجهة المستخدم سبيل المثال


هذه المقالة مخصصة للمبتدئين المهتمين بشبكات go language و نظير إلى نظير.
وللمحترفين الذين يمكنهم تقديم أفكار معقولة أو نقد بناء.


لقد تم البرمجة لبعض الوقت مع درجات متفاوتة من الانغماس في جافا ، فب ، شبيبة ، بيثون.
وكل لغة برمجة جيدة في مجالها.


المجال الرئيسي لـ Go هو إنشاء الخدمات الموزعة ، والخدمات المصغرة.
غالبًا ما يكون microservice هو برنامج صغير يؤدي وظائفه التخصصية للغاية.


ولكن يجب أن تظل الخدمات الصغيرة قادرة على التواصل مع بعضها البعض ، لذا فإن أداة إنشاء الخدمات الميكروية يجب أن تسمح بالتواصل السهل وغير المؤلم.
لاختبار ذلك ، سوف نكتب تطبيقًا ينظم شبكة لا مركزية من الأقران (Peer-To-Peer) ، والأبسط هو رسول p2p (بالمناسبة ، هل هناك مرادف روسي لهذه الكلمة؟).


في الكود ، قمت باختراع الدراجات بنشاط وخطو على أشعل النار ليشعر golang ، والحصول على النقد البناء والاقتراحات العقلانية.


ماذا نفعل


نظير (نظير) - مثيل فريد للرسول.


يجب أن يكون برنامج المراسلة لدينا قادرًا على:


  • البحث عن الأعياد القريبة
  • إنشاء اتصال مع أقرانهم الآخرين
  • تشفير تبادل البيانات مع أقرانهم
  • تلقي رسائل من المستخدم
  • إظهار الرسائل للمستخدم

لجعل المهمة أكثر إثارة للاهتمام ، دعونا نجعلها تمر عبر منفذ شبكة واحد.


المخطط الشرطي للرسول


إذا قمت بسحب هذا المنفذ عبر HTTP ، فسنحصل على تطبيق React يسحب نفس المنفذ من خلال إنشاء اتصال مقبس ويب.


إذا قمت بسحب المنفذ عبر HTTP وليس من الجهاز المحلي ، فسنقوم بعرض الشعار.


إذا كان هناك نظير آخر متصل بهذا المنفذ ، فسيتم تأسيس اتصال دائم بتشفير من طرف إلى طرف.


تحديد نوع الاتصال الوارد


أولاً ، افتح المنفذ للاستماع وسننتظر اتصالات جديدة.


net.ListenTCP("tcp", tcpAddr) 

على الاتصال الجديد ، اقرأ البايتات الأربعة الأولى.


نأخذ قائمة الأفعال HTTP ومقارنة 4 بايت لدينا معها.


الآن نحدد ما إذا كان يتم إجراء اتصال من الجهاز المحلي ، وإذا لم يكن الأمر كذلك ، فنحن نرد على شعار ونقطع الاتصال.


  buf, err := readWriter.Peek(4) /*   */ if ItIsHttp(buf) { handleHttp(readWriter, conn, p) } else { peer := proto.NewPeer(conn) p.HandleProto(readWriter, peer) } /* ... */ if !strings.EqualFold(s, "127") && !strings.EqualFold(s, "[::") { response.Body = ioutil.NopCloser(strings.NewReader("Peer To Peer Messenger. see https://github.com/easmith/p2p-messenger")) } 

إذا كان الاتصال محليًا ، فإننا نرد بالملف المقابل للطلب.


بعد ذلك قررت كتابة المعالجة بنفسي ، رغم أنني أستطيع استخدام المعالج المتاح في المكتبة القياسية.


  //   func processRequest(request *http.Request, response *http.Response) {/*    */} //     fileServer := http.FileServer(http.Dir("./front/build/")) fileServer.ServeHTTP(NewMyWriter(conn), request) 

إذا تم طلب المسار /ws ، فإننا نحاول تأسيس اتصال websocket.


منذ أن قمت بتجميع الدراجة في معالجة طلبات الملفات ، سأقوم بمعالجة اتصال ws باستخدام مكتبة gorilla / websocket .


للقيام بذلك ، قم بإنشاء MyWriter وتطبيق الأساليب فيه لتتوافق مع الواجهات http.ResponseWriter و http.Hijacker .


  // w - MyWriter func handleWs(w http.ResponseWriter, r *http.Request, p *proto.Proto) { c, err := upgrader.Upgrade(w, r, w.Header()) /*          */ } 

كشف الأقران


للبحث عن أقرانهم في شبكة محلية ، سنستخدم الإرسال المتعدد UDP.


سنرسل حزم مع معلومات عن أنفسنا إلى عنوان البث المتعدد IP.


  func startMeow(address string, p *proto.Proto) { conn, err := net.DialUDP("udp", nil, addr) /* ... */ for { _, err := conn.Write([]byte(fmt.Sprintf("meow:%v:%v", hex.EncodeToString(p.PubKey), p.Port))) /* ... */ time.Sleep(1 * time.Second) } } 

واستمع بشكل منفصل من البث المتعدد IP لجميع حزم UDP.


  func listenMeow(address string, p *proto.Proto, handler func(p *proto.Proto, peerAddress string)) { /* ... */ conn, err := net.ListenMulticastUDP("udp", nil, addr) /* ... */ _, src, err := conn.ReadFromUDP(buffer) /* ... */ // connectToPeer handler(p, peerAddress) } 

هكذا نعلن أنفسنا ونتعرف على مظهر الأعياد الأخرى.


قد يكون من الممكن تنظيم ذلك على مستوى IP ، وحتى في الوثائق الرسمية لحزمة IPv4 ، يتم تقديم حزمة بيانات البث المتعدد فقط كمثال على الكود.


نظير التفاعل بروتوكول


سنقوم بتعبئة جميع الاتصالات بين الأقران في مظروف (مظروف).


يوجد دائمًا على أي مغلف مرسل ومستلم ، وسنضيف إلى هذا الأمر أمرًا (يحمله معه) ، معرفًا (حتى الآن هذا رقم عشوائي ، ولكن يمكن القيام به كتجزئة للمحتويات) ، وطول محتويات ومحتويات المغلف نفسه - رسالة أو معلمات أمر.


مغلف بايت


يتم وضع الأمر (أو نوع المحتوى) بنجاح في بداية الظرف ونحدد قائمة بأوامر من 4 بايت لا تتقاطع مع أسماء أفعال HTTP.


يتم إجراء تسلسل المغلف بأكمله أثناء الإرسال في مجموعة من وحدات البايت.


المصافحة


عند إنشاء الاتصال ، تتواصل العيد على الفور للحصول على مصافحة ، مع إعطاء اسمها والمفتاح العام والمفتاح العمومي المؤقت لإنشاء مفتاح جلسة مشتركة.


استجابة لذلك ، يتلقى النظير مجموعة مماثلة من البيانات ، ويسجل النظير الموجود في قائمته ويحسب (CalcSharedSecret) مفتاح الجلسة العامة.


  func handShake(p *proto.Proto, conn net.Conn) *proto.Peer { /* ... */ peer := proto.NewPeer(conn) /*     */ p.SendName(peer) /*     */ envelope, err := proto.ReadEnvelope(bufio.NewReader(conn)) /* ... */ } 

تبادل العيد


بعد المصافحة ، يتبادل الأقران قوائم نظرائهم =)


للقيام بذلك ، يتم إرسال مغلف مع أمر LIST ، ويتم وضع قائمة JSON من أقرانه في محتوياته.
ردا على ذلك ، حصلنا على مغلف مماثل.


نجد في قوائم جديدة ومع كل منها نحاول الاتصال ، مصافحة ، تبادل الأعياد وهلم جرا ...


رسائل المستخدم


تعد الرسائل المخصصة ذات القيمة الأكبر بالنسبة لنا ، لذلك سنقوم بتشفير كل اتصال وتوقيعه.


حول التشفير


في مكتبات golang القياسية (google) من حزمة التشفير ، يتم تنفيذ الكثير من الخوارزميات المختلفة (لا توجد معايير GOST).


الأكثر ملاءمة للتوقيعات أعتقد أن منحنى Ed25519. سوف نستخدم مكتبة ed25519 لتوقيع الرسائل.


في البداية ، فكرت في استخدام زوج مفاتيح تم الحصول عليه من ed25519 ليس فقط للتوقيع ، ولكن أيضًا لإنشاء مفتاح جلسة.


ومع ذلك ، فإن مفاتيح التوقيع لا تنطبق على حساب المفتاح المشترك - لا تزال بحاجة إلى استحضارها:


 func CreateKeyExchangePair() (publicKey [32]byte, privateKey [32]byte) { pub, priv, err := ed25519.GenerateKey(nil) /* ... */ copy(publicKey[:], pub[:]) copy(privateKey[:], priv[:]) curve25519.ScalarBaseMult(&publicKey, &privateKey) /* ... */ } 

لذلك ، تقرر إنشاء مفاتيح سريعة الزوال ، وبصفة عامة ، هذا هو النهج الصحيح الذي لا يترك للمهاجمين فرصة لالتقاط مفتاح مشترك.


لمحبي الرياضيات ، فيما يلي روابط الويكي:
Diffie Protocol - Hellman_ on منحنيات بيضاوية
التوقيع الرقمي EdDSA


يعد إنشاء مفتاح مشترك أمرًا قياسيًا: أولاً ، بالنسبة للاتصال الجديد ، نقوم بإنشاء مفاتيح سريعة الزوال ، ونرسل مظروفًا بمفتاح عمومي إلى المقبس.


يفعل الجانب الآخر نفس الشيء ، لكن بترتيب مختلف: يتلقى مغلفًا بمفتاح عام ، ويقوم بإنشاء زوج خاص به ويرسل المفتاح العام إلى المقبس.


الآن لدى كل مشارك مفاتيح سريعة وعامة لشخص آخر.


بضربهم ، نحصل على نفس المفتاح لكليهما ، والذي سنستخدمه لتشفير الرسائل.


 //CalcSharedSecret Calculate shared secret func CalcSharedSecret(publicKey []byte, privateKey []byte) (secret [32]byte) { var pubKey [32]byte var privKey [32]byte copy(pubKey[:], publicKey[:]) copy(privKey[:], privateKey[:]) curve25519.ScalarMult(&secret, &privKey, &pubKey) return } 

سنقوم بتشفير الرسائل من خلال خوارزمية AES الراسخة في وضع اقتران الكتلة (CBC).


كل هذه التطبيقات يمكن العثور عليها بسهولة في وثائق golang.


التنقيح الوحيد هو ملء الرسالة تلقائيًا مع صفر بايت من أجل تعدد طولها إلى طول كتلة التشفير (16 بايت).


  //Encrypt the message func Encrypt(content []byte, key []byte) []byte { padding := len(content) % aes.BlockSize if padding != 0 { repeat := bytes.Repeat([]byte("\x00"), aes.BlockSize-(padding)) content = append(content, repeat...) } /* ... */ } //Decrypt encrypted message func Decrypt(encrypted []byte, key []byte) []byte { /* ... */ encrypted = bytes.Trim(encrypted, string([]byte("\x00"))) return encrypted } 

في عام 2013 ، قام بتطبيق AES (مع وضع مماثل لـ CBC) لتشفير الرسائل في Telegram كجزء من مسابقة من Pavel Durov.


في ذلك الوقت ، تم استخدام بروتوكول Diffie-Hellman الأكثر شيوعًا في البرقيات لإنشاء مفتاح سريع الزوال.


ومن أجل استبعاد الحمل من الاتصالات المزيفة ، قبل كل عملية تبادل مفاتيح ، حل العملاء مشكلة التعمير.


واجهة المستخدم الرسومية


نحتاج إلى عرض قائمة من الأقران وقائمة بالرسائل معهم ، وكذلك الرد على الرسائل الجديدة عن طريق زيادة العداد بجوار اسم النظير.


هنا دون مشاكل - ReactJS + websocket.


رسائل مأخذ توصيل الويب عبارة عن مظروف فريد من نوعه ، إلا أنها لا تحتوي على نصوص مشفرة.


كلهم "ورثة" من نوع WsCmd ويتم إجراء تسلسل في JSON عند النقل.


  //Serializable interface to detect that can to serialised to json type Serializable interface { ToJson() []byte } func toJson(v interface{}) []byte { json, err := json.Marshal(v) /*  err */ return json } /* ... */ //WsCmd WebSocket command type WsCmd struct { Cmd string `json:"cmd"` } //WsMessage WebSocket command: new Message type WsMessage struct { WsCmd From string `json:"from"` To string `json:"to"` Content string `json:"content"` } //ToJson convert to JSON bytes func (v WsMessage) ToJson() []byte { return toJson(v) } /* ... */ 

لذلك ، يأتي طلب HTTP إلى الجذر ("/") ، الآن لعرض المقدمة ، والبحث في دليل "front / build" وإعطاء index.html


حسنًا ، تم تكوين الواجهة ، والآن أصبح الخيار للمستخدمين: تشغيلها في مستعرض أو في نافذة منفصلة - WebView.


بالنسبة للخيار الأخير ، استخدم zserge / webview


  e := webview.Open("Peer To Peer Messenger", fmt.Sprintf("http://localhost:%v", initParams.Port), 800, 600, false) 

لإنشاء تطبيق به ، تحتاج إلى تثبيت نظام آخر


  sudo apt install libwebkit2gtk-4.0-dev 

في سياق التفكير في واجهة المستخدم الرسومية ، وجدت العديد من المكتبات الخاصة بـ GTK و QT وستبدو واجهة وحدة التحكم من العبقري غريب الأطوار - https://github.com/jroimartin/gocui - في رأيي فكرة مثيرة للاهتمام للغاية.


رسول إطلاق


تركيب Golang


بالطبع ، تحتاج أولا إلى تثبيت الذهاب.
للقيام بذلك ، أوصي بشدة باستخدام تعليمة golang.org/doc/install .


تعليمات مبسطة لباش النصي


تنزيل تطبيق في GOPATH


إنه مرتب لدرجة أن جميع المكتبات وحتى مشاريعك يجب أن تكون في ما يسمى GOPATH.


بشكل افتراضي ، هذا هو $ HOME / go. يسمح لك Go بسحب المصدر من المستودع العام باستخدام أمر بسيط:


  go get github.com/easmith/p2p-messenger 

الآن ، في دليل $HOME/go/src/github.com/easmith/p2p-messenger سيظهر المصدر من الفرع الرئيسي


تركيب npm والتجمع الأمامي


كما كتبت أعلاه ، فإن واجهة المستخدم الرسومية الخاصة بنا هي تطبيق ويب له واجهة على ReactJs ، لذلك لا يزال يتعين تجميع الواجهة.


Nodejs + npm - هنا كالمعتاد.


فقط في حالة ، وهنا هو تعليمات لأوبونتو


الآن نبدأ التجمع الأمامي كمعيار


 cd front npm update npm run build 

الجبهة جاهزة!


إطلاق


دعنا نعود إلى الجذر وإطلاق عيد رسولنا.


عند بدء التشغيل ، يمكننا تحديد اسم نظيرنا ومنفذنا وملفنا مع عناوين نظرائنا الآخرين وعلامة تشير إلى ما إذا كان سيتم تشغيل WebView أم لا.


بشكل افتراضي ، يتم استخدام $USER@$HOSTNAME كاسم $USER@$HOSTNAME والمنفذ 35035.


لذلك ، نبدأ ونتحدث مع الأصدقاء على الشبكة المحلية.


  go run app.go -name Snowden 

ردود الفعل على برمجة golang


  • أهم شيء أود أن أشير إليه: في الحال ، اتضح على الفور أن تنفيذ ما كنت أقصده .
    كل ما تحتاجه تقريبًا موجود في المكتبة القياسية.
  • ومع ذلك ، كانت هناك صعوبة عندما بدأت المشروع في دليل غير GOPATH.
    اعتدت GoLand لكتابة التعليمات البرمجية. في البداية ، كان من المحرج تهيئة الشفرة تلقائيًا مع مكتبات الاستيراد التلقائي.
  • هناك الكثير من مولدات الشفرات في IDE ، مما سمح لنا بالتركيز على التطوير بدلاً من التركيز على مجموعة الرموز.
  • تعتاد بسرعة على التعامل مع الأخطاء بشكل متكرر ، ولكن يحدث وجهاً لوجه عندما تدرك أن الوضع الطبيعي يحدث عندما يتم تحليل جوهر الخطأ وفقًا لتمثيل السلسلة.
     err != io.EOF 
  • الأمور أفضل قليلاً مع مكتبة نظام التشغيل. تساعد هذه الإنشاءات على فهم جوهر المشكلة.
     if os.IsNotExist(err) { /* ... */ } 
  • من خارج الصندوق ، يعلمنا go أن نوثق الشفرة وكتابة الاختبارات بشكل صحيح.
    وهناك بعض ولكن. لقد وصفنا الواجهة ToJson() .
    لذلك ، لا يرث منشئ الوثائق وصف هذه الطريقة للطرق التي تنفذها ، لذلك لإزالة التحذيرات غير الضرورية ، يجب عليك نسخ الوثائق في كل طريقة تم تنفيذها (proto / mtypes.go).
  • لقد اعتدت مؤخرًا على قوة log4j في java ، لذلك لا يوجد ما يكفي من المسجل الجيد.
    ربما يستحق النظر إلى اتساع تسجيل جيثب الجميل مع الملاحين والمنسقين.
  • عمل غير عادي مع المصفوفات.
    على سبيل المثال ، يحدث التسلسل من خلال وظيفة append ، وتحويل مجموعة من الطول التعسفي إلى صفيف ذي طول ثابت خلال copy .
  • switch-case تعمل مثل if-elseif-else - ولكن هذا أسلوب مثير للاهتمام ، ولكن مرة أخرى وجهاً لوجه:
    إذا كنا نريد سلوك switch-case المعتاد ، فنحن نحتاج إلى وضع fallthrough في كل حالة.
    يمكنك أيضًا استخدام goto ، لكن دعونا لا نرجوك!
  • لا يوجد مشغل ثلاثي ، وغالبًا ما يكون هذا غير مناسب.

ما التالي؟


حتى يتم تنفيذ أبسط رسول الند للند.


المخاريط مكتظة ، كما يمكنك تحسين وظائف المستخدم: إرسال الملفات والصور والصوت والرموز وما إلى ذلك ، إلخ.


ولا يمكنك اختراع البروتوكول الخاص بك ، واستخدام مخازن بروتوكول Google ،
ربط blockchain وحماية نفسك من البريد المزعج باستخدام Ethereum العقود الذكية.


في العقود الذكية ، قم بتنظيم محادثات جماعية وقنوات ونظام أسماء وأفاتار وملفات تعريف المستخدمين.


من الضروري أيضًا تشغيل نظراء البذور ، وتنفيذ تجاوز NAT ، وإرسال رسائل من نظير إلى نظير.


نتيجة لذلك ، تحصل على برقية / هاتف بديل جيد ، ما عليك سوى نقل جميع أصدقائك إلى هناك =)


فائدة


بعض الروابط

في سياق العمل على برنامج messenger ، وجدت صفحات مثيرة للاهتمام لمطور برامج التطوير للمبتدئين.
أشاركهم معك:


golang.org/doc/ - التوثيق اللغوي ، كل شيء بسيط وواضح ومع أمثلة. يمكن تشغيل نفس الوثائق محليًا باستخدام الأمر


 godoc -HTTP=:6060 

gobyexample.com - مجموعة من الأمثلة البسيطة


golang-book.ru - كتاب جيد باللغة الروسية


github.com/dariubs/GoBooks عبارة عن مجموعة من الكتب حول Go.


awesome-go.com - قائمة المكتبات والأطر والتطبيقات مثيرة للاهتمام على الذهاب. التصنيف أكثر أو أقل ، لكن وصف العديد منهم نادر جدًا ، وهو لا يساعد في البحث بواسطة Ctrl + F

Source: https://habr.com/ru/post/ar437686/


All Articles