Learning go: menulis messenger p2p dengan enkripsi ujung ke ujung

Namun P2P Messenger lain


Membaca ulasan dan dokumentasi bahasa tidak cukup untuk mempelajari cara menulis aplikasi yang kurang lebih bermanfaat di dalamnya.


Pastikan untuk melakukan konsolidasi, Anda perlu membuat sesuatu yang menarik agar perkembangannya dapat digunakan dalam tugas-tugas lain.


Contoh UI ReactJs Chat


Artikel ini ditujukan untuk pemula yang tertarik dengan jaringan go and peer-to-peer.
Dan untuk para profesional yang dapat menawarkan ide-ide yang masuk akal atau mengkritik secara konstruktif.


Saya telah pemrograman untuk beberapa waktu dengan berbagai tingkat pencelupan di java, php, js, python.
Dan setiap bahasa pemrograman bagus di bidangnya.


Area utama untuk Go adalah penciptaan layanan terdistribusi, layanan microser.
Paling sering, layanan mikro adalah program kecil yang melakukan fungsi yang sangat terspesialisasi.


Tetapi layanan microser masih harus dapat berkomunikasi satu sama lain, sehingga alat untuk membuat layanan microser harus memungkinkan jaringan yang mudah dan tidak menyakitkan.
Untuk menguji ini, kita akan menulis sebuah aplikasi yang mengatur jaringan teman sebaya yang terdesentralisasi (Peer-To-Peer), yang paling sederhana adalah p2p messenger (omong-omong, apakah ada sinonim Rusia untuk kata ini?).


Dalam kode tersebut, saya secara aktif menciptakan sepeda dan menginjak menyapu untuk merasakan golang, mendapatkan kritik konstruktif dan saran rasional.


Apa yang kita lakukan


Peer (peer) - contoh unik dari messenger.


Utusan kami harus dapat:


  • Temukan pesta terdekat
  • Menjalin koneksi dengan rekan-rekan lain
  • Enkripsi pertukaran data dengan teman sebaya
  • Terima pesan dari pengguna
  • Tampilkan pesan ke pengguna

Untuk membuat tugas sedikit lebih menarik, mari kita buat semuanya melalui satu port jaringan.


Skema bersyarat dari pembawa pesan


Jika Anda menarik port ini melalui HTTP, kami mendapatkan aplikasi Bereaksi yang menarik port yang sama dengan membuat koneksi socket web.


Jika Anda menarik port melalui HTTP bukan dari mesin lokal, maka kami menunjukkan spanduk.


Jika peer lain terhubung ke port ini, maka koneksi permanen dibuat dengan enkripsi ujung ke ujung.


Tentukan jenis koneksi yang masuk


Pertama, buka port untuk mendengarkan dan kami akan menunggu koneksi baru.


net.ListenTCP("tcp", tcpAddr) 

Pada koneksi baru, baca 4 byte pertama.


Kami mengambil daftar kata kerja HTTP dan membandingkan 4 byte kami dengannya.


Sekarang kami menentukan apakah koneksi dibuat dari mesin lokal, dan jika tidak, kami merespons dengan spanduk dan menutup telepon.


  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")) } 

Jika koneksi bersifat lokal, maka kami merespons dengan file yang sesuai dengan permintaan.


Kemudian saya memutuskan untuk menulis sendiri prosesnya, walaupun saya bisa menggunakan handler yang tersedia di perpustakaan standar.


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

Jika jalur diminta, maka kami mencoba membuat koneksi websocket.


Karena saya mengumpulkan sepeda dalam memproses permintaan file, saya akan melakukan pemrosesan koneksi ws menggunakan perpustakaan gorilla / websocket .


Untuk melakukan ini, buat MyWriter dan terapkan metode di dalamnya untuk berhubungan dengan antarmuka http.ResponseWriter dan http.Hijacker .


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

Deteksi Sebaya


Untuk mencari rekan di jaringan lokal, kami akan menggunakan UDP multicast.


Kami akan mengirimkan paket dengan informasi tentang diri kami ke alamat IP Multicast.


  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) } } 

Dan dengarkan secara terpisah dari Multicast IP untuk semua paket 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) } 

Dengan demikian, kita mendeklarasikan diri kita sendiri dan belajar tentang penampilan pesta-pesta lainnya.


Mungkin untuk mengatur ini di tingkat IP, dan bahkan dalam dokumentasi resmi paket IPv4, hanya paket data multicast yang diberikan sebagai contoh kode.


Protokol Interaksi Teman


Kami akan mengemas semua komunikasi antar rekan dalam amplop (Amplop).


Pada setiap amplop selalu ada pengirim dan penerima, untuk ini kami akan menambahkan perintah (yang ia bawa bersamanya), pengidentifikasi (sejauh ini adalah nomor acak, tetapi dapat dilakukan sebagai hash konten), panjang isi dan isi amplop itu sendiri - pesan atau parameter perintah.


Byte amplop


Perintah, (atau jenis konten) berhasil ditempatkan di bagian paling awal amplop dan kami menetapkan daftar perintah 4 byte yang tidak bersinggungan dengan nama-nama kata kerja HTTP.


Seluruh amplop selama transmisi serial ke dalam array byte.


Jabat tangan


Ketika koneksi dibuat, pesta segera meraih jabat tangan, memberikan namanya, kunci publik dan kunci publik sesaat untuk menghasilkan kunci sesi bersama.


Sebagai tanggapan, rekan menerima kumpulan data yang serupa, mendaftarkan rekan yang ditemukan dalam daftar dan menghitung (CalcSharedSecret) kunci sesi umum.


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

Pertukaran pesta


Setelah jabat tangan, rekan-rekan bertukar daftar rekan mereka =)


Untuk melakukan ini, sebuah amplop dengan perintah LIST dikirim, dan daftar teman sebaya JSON ditempatkan di isinya.
Sebagai tanggapan, kami mendapatkan amplop yang serupa.


Kami menemukan dalam daftar yang baru dan dengan masing-masing kami mencoba untuk terhubung, berjabat tangan, bertukar pesta dan seterusnya ...


Olahpesan pengguna


Pesan khusus adalah nilai terbesar bagi kami, jadi kami akan mengenkripsi dan menandatangani setiap koneksi.


Tentang enkripsi


Dalam pustaka golang (google) standar dari paket crypto, banyak algoritma yang berbeda diimplementasikan (tidak ada standar GOST).


Yang paling nyaman untuk tanda tangan saya pikir adalah kurva Ed25519. Kami akan menggunakan perpustakaan ed25519 untuk menandatangani pesan.


Pada awalnya, saya berpikir tentang menggunakan pasangan kunci yang diperoleh dari ed25519 tidak hanya untuk penandatanganan, tetapi juga untuk menghasilkan kunci sesi.


Namun, kunci untuk penandatanganan tidak berlaku untuk menghitung kunci bersama - Anda masih perlu menyulapnya:


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

Oleh karena itu, diputuskan untuk menghasilkan kunci sementara, dan secara umum, ini adalah pendekatan yang tepat yang tidak memberikan kesempatan bagi penyerang untuk mengambil kunci bersama.


Untuk pecinta matematika, berikut adalah tautan wiki:
Protokol Diffie - Hellman_ pada Kurva Elips
Tanda Tangan Digital EdDSA


Pembuatan kunci bersama cukup standar: pertama, untuk koneksi baru, kami menghasilkan kunci sementara, kami mengirim amplop dengan kunci publik ke soket.


Sisi yang berlawanan melakukan hal yang sama, tetapi dalam urutan yang berbeda: ia menerima amplop dengan kunci publik, menghasilkan pasangannya sendiri dan mengirimkan kunci publik ke soket.


Sekarang setiap peserta memiliki kunci singkat publik dan pribadi orang lain.


Mengalikannya, kita mendapatkan kunci yang sama untuk keduanya, yang akan kita gunakan untuk mengenkripsi pesan.


 //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 } 

Kami akan mengenkripsi pesan dengan algoritma AES yang telah lama ada dalam mode kopling blok (CBC).


Semua implementasi ini mudah ditemukan dalam dokumentasi golang.


Satu-satunya perbaikan adalah pengisian otomatis pesan dengan nol byte untuk banyaknya panjangnya hingga panjang blok enkripsi (16 byte).


  //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 } 

Kembali pada tahun 2013, ia menerapkan AES (dengan mode yang mirip dengan CBC) untuk mengenkripsi pesan di Telegram sebagai bagian dari kontes dari Pavel Durov.


Pada saat itu, protokol Diffie-Hellman yang paling umum digunakan dalam telegram untuk menghasilkan kunci fana.


Dan untuk mengecualikan beban dari koneksi palsu, sebelum setiap pertukaran kunci, klien memecahkan masalah faktorisasi.


GUI


Kita perlu menunjukkan daftar teman dan daftar pesan dengannya, dan juga menanggapi pesan baru dengan menambah penghitung di sebelah nama rekan.


Di sini tanpa masalah - ReactJS + websocket.


Pesan soket web pada dasarnya adalah amplop unik, hanya saja tidak mengandung cipherteks.


Semua dari mereka adalah "ahli waris" dari tipe WsCmd dan diserialisasi dalam JSON pada saat transfer.


  //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) } /* ... */ 

Jadi, permintaan HTTP datang ke root ("/"), sekarang untuk menampilkan bagian depan, lihat di direktori "front / build" dan berikan index.html


Nah antarmuka dibuat, sekarang pilihan untuk pengguna adalah: jalankan di browser atau di jendela yang terpisah - WebView.


Untuk opsi terakhir digunakan zserge / webview


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

Untuk membangun aplikasi dengannya, Anda perlu menginstal sistem lain


  sudo apt install libwebkit2gtk-4.0-dev 

Dalam proses berpikir tentang GUI, saya menemukan banyak perpustakaan untuk GTK, QT, dan antarmuka konsol akan terlihat sangat culun - https://github.com/jroimartin/gocui - menurut saya ide yang sangat menarik.


Peluncuran Messenger


Instalasi golang


Tentu saja, Anda harus menginstal go terlebih dahulu.
Untuk melakukan ini, saya sangat merekomendasikan menggunakan instruksi golang.org/doc/install .


Instruksi sederhana untuk bash script


Unduh aplikasi di GOPATH


Sudah diatur sedemikian rupa sehingga semua perpustakaan dan bahkan proyek Anda harus dalam apa yang disebut GOPATH.


Secara default, ini adalah $ HOME / go. Go memungkinkan Anda untuk menarik sumber dari repositori publik dengan perintah sederhana:


  go get github.com/easmith/p2p-messenger 

Sekarang, di $HOME/go/src/github.com/easmith/p2p-messenger sumber dari cabang master akan muncul


Instalasi NPM dan perakitan depan


Seperti yang saya tulis di atas, GUI kami adalah aplikasi web dengan front di ReactJs, jadi front masih perlu dirakit.


Nodejs + npm - di sini seperti biasa.


Untuk jaga-jaga, berikut adalah instruksi untuk ubuntu


Sekarang kita mulai perakitan depan sebagai standar


 cd front npm update npm run build 

Bagian depan siap!


Luncurkan


Mari kita kembali ke root dan meluncurkan pesta utusan kita.


Saat memulai, kita dapat menentukan nama rekan, port, file dengan alamat rekan-rekan lain dan bendera yang menunjukkan apakah akan meluncurkan WebView.


Secara default, $USER@$HOSTNAME digunakan sebagai nama $USER@$HOSTNAME dan port 35035.


Jadi, kami memulai dan mengobrol dengan teman-teman di jaringan lokal.


  go run app.go -name Snowden 

Umpan balik tentang pemrograman golang


  • Hal yang paling penting yang ingin saya catat: on go, ternyata segera mengimplementasikan apa yang saya maksudkan .
    Hampir semua yang Anda butuhkan ada di perpustakaan standar.
  • Namun, ada kesulitan ketika saya memulai proyek di direktori selain GOPATH.
    Saya menggunakan GoLand untuk menulis kode. Dan pada awalnya memalukan untuk memformat kode secara otomatis dengan pustaka impor-otomatis.
  • Ada banyak generator kode di IDE , yang memungkinkan kami untuk fokus pada pengembangan daripada pada kumpulan kode.
  • Anda dengan cepat terbiasa dengan penanganan kesalahan yang sering, tetapi wajah-tangan terjadi ketika Anda menyadari bahwa untuk pergi situasi yang normal adalah ketika esensi kesalahan dianalisis menurut perwakilan stringnya.
     err != io.EOF 
  • Hal-hal sedikit lebih baik dengan perpustakaan os. Konstruksi semacam itu membantu memahami esensi masalah.
     if os.IsNotExist(err) { /* ... */ } 
  • Di luar kotak, pergilah ajari kami untuk mendokumentasikan kode dan menulis tes dengan benar.
    Dan ada beberapa tapi. Kami telah menggambarkan antarmuka dengan metode ToJson() .
    Jadi, pembuat dokumentasi tidak mewarisi deskripsi metode ini ke metode yang mengimplementasikannya, jadi untuk menghapus peringatan yang tidak perlu, Anda harus menyalin dokumentasi ke setiap metode yang diterapkan (proto / mtypes.go).
  • Baru-baru ini saya terbiasa dengan kekuatan log4j di java, jadi tidak ada cukup logger yang baik di mana saja.
    Mungkin layak melihat luasnya github logging yang indah dengan appenders dan formatters.
  • Pekerjaan yang tidak biasa dengan array.
    Misalnya, rangkaian terjadi melalui fungsi append , dan konversi array dengan panjang sewenang-wenang menjadi array dengan panjang tetap melalui copy .
  • switch-case berfungsi seperti if-elseif-else - tetapi ini adalah pendekatan yang menarik, tetapi sekali lagi dengan wajah:
    jika kita menginginkan perilaku switch-case biasa, kita perlu membuat fallthrough pada setiap case.
    Anda juga bisa menggunakan goto , tapi jangan, tolong!
  • Tidak ada operator ternary dan seringkali ini tidak nyaman.

Apa selanjutnya


Jadi messenger Peer-To-Peer yang paling sederhana diterapkan.


Kerucut dijejalkan, lebih jauh Anda dapat meningkatkan fungsionalitas pengguna: mengirim file, gambar, audio, emotikon, dll., Dll.


Dan Anda tidak dapat menemukan protokol Anda, dan menggunakan Bufer Protokol Google,
Hubungkan blockchain dan lindungi diri Anda dari spam menggunakan kontrak pintar Ethereum.


Pada kontrak pintar, atur obrolan grup, saluran, sistem nama, avatar, dan profil pengguna.


Sangat penting untuk menjalankan seed peer, mengimplementasikan bypass NAT, dan mengirim pesan dari peer to peer.


Akibatnya, Anda mendapatkan telegram / telepon pengganti yang baik, Anda hanya perlu mentransfer semua teman Anda di sana =)


Kegunaan


Beberapa tautan

Dalam proses pengerjaan messenger, saya menemukan halaman yang menarik untuk pengembang pemula.
Saya membaginya dengan Anda:


golang.org/doc/ - dokumentasi bahasa, semuanya sederhana, jelas dan dengan contoh. Dokumentasi yang sama dapat dijalankan secara lokal dengan perintah


 godoc -HTTP=:6060 

gobyexample.com - kumpulan contoh sederhana


golang-book.ru - buku bagus dalam bahasa Rusia


github.com/dariubs/GoBooks adalah kumpulan buku tentang Go.


awesome-go.com - Daftar pustaka, kerangka kerja dan aplikasi menarik saat bepergian. Kategorisasi lebih atau kurang, tetapi deskripsi banyak dari mereka sangat langka, yang tidak membantu pencarian dengan Ctrl + F

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


All Articles