GOSTIM: P2P F2F E2EE IM dalam satu malam dengan kriptografi GOST

Sebagai pengembang perpustakaan PyGOST (primitif GOST kriptografis dengan Python murni), saya sering mendapatkan pertanyaan tentang bagaimana menerapkan pesan aman paling sederhana di lutut saya. Banyak yang menganggap kriptografi terapan sebagai hal yang cukup sederhana, dan panggilan .encrypt () ke block cipher akan cukup untuk mengirim dengan aman melalui saluran komunikasi. Yang lain percaya bahwa kriptografi terapan adalah takdir beberapa orang, dan dapat diterima bahwa perusahaan-perusahaan kaya seperti Telegram dengan olimpiade matematika tidak dapat menerapkan protokol yang aman.

Semua ini mendorong saya untuk menulis artikel ini untuk menunjukkan bahwa implementasi protokol kriptografi dan mengamankan IM bukanlah tugas yang sulit. Namun, menciptakan otentikasi Anda sendiri dan protokol perjanjian kunci tidak sepadan.

Menghirup

Artikel ini akan ditulis messenger instan terenkripsi peer-to-peer , teman-ke-teman , end-to-end dengan otentikasi SIGMA-I dan protokol perjanjian kunci (berdasarkan pada mana IPsec IKE diimplementasikan) menggunakan algoritma kriptografi GOST secara eksklusif GOST kriptografi algoritma dan perpustakaan PyGOST perpustakaan dan ASN.1 penyandian pesan dengan pustaka PyDERASN (tentang yang sudah saya tulis sebelumnya ). Prasyarat: itu harus sangat sederhana sehingga dapat ditulis dari awal dalam satu malam (atau hari kerja), kalau tidak itu bukan lagi program sederhana. Mungkin ada kesalahan, kesulitan yang tidak perlu, kekurangan, ditambah ini adalah program pertama saya menggunakan perpustakaan asyncio.

Desain IM


Untuk memulai, Anda perlu memahami bagaimana tampilan IM kami. Untuk kesederhanaan, biarkan itu menjadi jaringan peer-to-peer, tanpa ada penemuan peserta. Kami akan secara pribadi menunjukkan ke alamat mana: port untuk terhubung untuk berkomunikasi dengan lawan bicara.

Saya mengerti bahwa pada saat ini, asumsi ketersediaan komunikasi langsung antara dua komputer yang berubah-ubah adalah batasan signifikan dari penerapan IM dalam praktek. Tetapi semakin banyak pengembang akan menerapkan semua jenis kruk NAT-traversal, semakin lama kita akan tetap di Internet IPv4, dengan kemungkinan komunikasi yang menyedihkan antara komputer yang sewenang-wenang. Nah, seberapa banyak Anda bisa menanggung kekurangan IPv6 di rumah dan di tempat kerja?

Kami akan memiliki jaringan teman-ke-teman: semua lawan bicara yang mungkin harus diketahui sebelumnya. Pertama, sangat menyederhanakan segalanya: memperkenalkan diri, menemukan atau tidak menemukan nama / kunci, terputus atau terus bekerja, mengetahui lawan bicara. Kedua, dalam kasus umum, aman dan tidak termasuk banyak serangan.

Antarmuka IM akan dekat dengan solusi klasik dari proyek tanpa payah , yang saya sangat suka untuk minimalis dan filosofi Unix-way mereka. Program IM untuk setiap lawan bicara membuat direktori dengan tiga soket domain Unix:

  • dalam - pesan yang dikirim ke lawan bicara direkam di dalamnya;
  • pesan keluar yang diterima dari teman bicara dibaca darinya;
  • negara - membaca dari itu, kita akan mengetahui apakah teman bicara terhubung sekarang, alamat koneksi / port.

Selain itu, soket konek dibuat, menulis ke port host mana, kami memulai koneksi ke lawan bicara jarak jauh.

 | - alice
 |  | - dalam
 |  | - keluar
 |  `- nyatakan
 | - bob
 |  | - dalam
 |  | - keluar
 |  `- nyatakan
 `- samb

Pendekatan ini memungkinkan Anda untuk membuat implementasi independen transport IM dan antarmuka pengguna, karena tidak ada teman untuk rasa dan warna, Anda tidak akan menyenangkan semua orang. Dengan menggunakan tmux dan / atau multitail , Anda bisa mendapatkan antarmuka multi-jendela dengan penyorotan sintaksis. Dan dengan rlwrap, Anda bisa mendapatkan string yang kompatibel dengan GNU Readline untuk memasukkan pesan.

Bahkan, proyek tanpa pengisap menggunakan file FIFO. Secara pribadi, saya tidak bisa mengerti bagaimana dalam asyncio bekerja dengan file secara kompetitif tanpa substrat buatan tangan dari utas yang dipilih (saya telah menggunakan bahasa Go untuk hal-hal seperti itu untuk waktu yang lama). Oleh karena itu, saya memutuskan untuk bertahan dengan soket domain Unix. Sayangnya, ini membuat tidak mungkin untuk melakukan echo 2001: 470: dead :: babe 6666> conn. Saya memecahkan masalah ini menggunakan socat : echo 2001: 470: dead :: babe 6666 | socat - UNIX-CONNECT: samb, socat READLINE UNIX-CONNECT: alice / in

Protokol tidak aman awal


TCP digunakan sebagai transportasi: ia menjamin pengiriman dan pesanannya. UDP tidak menjamin yang satu atau yang lain (yang akan berguna ketika kriptografi diterapkan), dan dukungan SCTP dengan Python berada di luar kotak.

Sayangnya, dalam TCP tidak ada konsep pesan, tetapi hanya aliran byte. Oleh karena itu, perlu untuk datang dengan format untuk pesan sehingga mereka dapat dibagikan di antara mereka sendiri dalam aliran ini. Kami dapat menyetujui untuk menggunakan karakter umpan baris. Sebagai permulaan, ini cocok, namun, ketika kami mulai mengenkripsi pesan kami, simbol ini dapat muncul di mana saja di ciphertext. Oleh karena itu, protokol populer di jaringan, pertama-tama mengirim panjang pesan dalam byte. Misalnya, dalam Python, di luar kotak ada xdrlib, yang memungkinkan Anda untuk bekerja dengan format XDR serupa.

Kami tidak akan bekerja dengan benar dan efisien dengan pembacaan TCP - kami menyederhanakan kodenya. Kami membaca data dari soket dalam loop tanpa akhir hingga kami mendekode pesan lengkap. Anda juga dapat menggunakan JSON dengan XML sebagai format untuk pendekatan ini. Tetapi ketika kriptografi ditambahkan, maka data harus ditandatangani dan diautentikasi - dan ini akan membutuhkan representasi objek identik byte-by-byte, yang tidak disediakan JSON / XML (dumps mungkin berbeda-beda).

XDR cocok untuk tugas seperti itu, namun, saya memilih ASN.1 dengan penyandian DER dan pustaka PyDERASN , karena kita akan memiliki objek tingkat tinggi di tangan, yang seringkali lebih menyenangkan dan nyaman untuk dikerjakan. Tidak seperti schemaless bencode , MessagePack, atau CBOR , ASN.1 akan secara otomatis memvalidasi data terhadap skema kode-keras.

# Msg ::= CHOICE { # text MsgText, # handshake [0] EXPLICIT MsgHandshake } class Msg(Choice): schema = (( ("text", MsgText()), ("handshake", MsgHandshake(expl=tag_ctxc(0))), )) # MsgText ::= SEQUENCE { # text UTF8String (SIZE(1..MaxTextLen))} class MsgText(Sequence): schema = (( ("text", UTF8String(bounds=(1, MaxTextLen))), )) # MsgHandshake ::= SEQUENCE { # peerName UTF8String (SIZE(1..256)) } class MsgHandshake(Sequence): schema = (( ("peerName", UTF8String(bounds=(1, 256))), )) 

Pesan yang diterima adalah Msg: salah satu teks MsgText (dengan satu bidang teks sejauh ini) atau pesan handshake MsgHandshake (di mana nama teman bicara ditransmisikan). Sekarang kelihatannya terlalu rumit, tetapi ini merupakan tantangan untuk masa depan.

      β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”
      β”‚PeerAβ”‚ β”‚PeerBβ”‚
      β””β”€β”€β”¬β”€β”€β”˜ β””β”€β”€β”¬β”€β”€β”˜
         β”‚MsgHandshake (IdA) β”‚
         │──────────────── >> >>
         β”‚ β”‚
         β”‚MsgHandshake (IdB) β”‚
         β”‚ <────────────────
         β”‚ β”‚
         β”‚ MsgText () β”‚
         │───────────────── >> β”‚
         β”‚ β”‚
         β”‚ MsgText () β”‚
         β”‚ <────────────────│
         β”‚ β”‚


IM tanpa kriptografi


Seperti yang saya katakan, untuk semua operasi dengan soket pustaka asyncio akan digunakan. Nyatakan apa yang kami harapkan saat peluncuran:

 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(",")) 

Tetapkan nama Anda sendiri (- alice-nama kami). Koma mencantumkan semua lawan bicara yang diharapkan (--their-names bob, eve). Untuk masing-masing lawan bicara, direktori dengan soket Unix dibuat, serta coroutine untuk masing-masing in, out, state:

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

Pesan dari soket masuk dari pengguna dikirim ke antrian 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")) 

Pesan dari lawan bicara dikirim ke antrian OUT_QUEUES, dari mana data ditulis ke soket keluar:

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

Saat membaca dari soket negara, program mencari di kamus PEER_ALIVE untuk alamat lawan bicara. Jika belum ada koneksi ke teman bicara, baris kosong akan ditulis.

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

Ketika sebuah alamat ditulis ke soket koneksi, fungsi "inisiator" koneksi diluncurkan:

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

Pertimbangkan penggagasnya. Pertama, dia jelas membuka koneksi ke host / port yang ditentukan dan mengirim pesan jabat tangan dengan namanya:

  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 # Handshake message {{{ 135 writer.write(Msg(("handshake", MsgHandshake(( 136 ("peerName", OUR_NAME), 137 )))).encode()) 138 # }}} 139 await writer.drain() 

Kemudian menunggu respons dari sisi jarak jauh. Mencoba memecahkan kode respons yang diterima sesuai dengan skema ASN.1 Msg. Kami berasumsi bahwa seluruh pesan akan dikirim oleh satu segmen TCP dan kami akan menerimanya secara atomik ketika .read () dipanggil. Kami memverifikasi bahwa kami menerima pesan jabat tangan persis.

  141 # Wait for Handshake message {{{ 142 data = await reader.read(256) 143 if data == b"": 144 logging.warning("%s: no answer, disconnecting", _id) 145 writer.close() 146 return 147 try: 148 msg, _ = Msg().decode(data) 149 except ASN1Error: 150 logging.warning("%s: undecodable answer, disconnecting", _id) 151 writer.close() 152 return 153 logging.info("%s: got %s message", _id, msg.choice) 154 if msg.choice != "handshake": 155 logging.warning("%s: unexpected message, disconnecting", _id) 156 writer.close() 157 return 158 # }}} 

Kami memverifikasi bahwa nama orang yang kami ajak bicara dikenal oleh kami. Jika tidak, maka putuskan koneksi. Kami memeriksa apakah kami telah membuat koneksi dengannya (lawan bicara lagi memberi perintah untuk terhubung dengan kami) dan menutupnya. String python dengan teks pesan ditempatkan dalam antrian IN_QUEUES, tetapi ada nilai khusus Tidak ada, yang memberi sinyal msg_sender ke coroutine untuk berhenti bekerja sehingga dia akan melupakan penulisnya, yang terhubung ke koneksi TCP yang ketinggalan zaman.

  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 # Run text message sender, initialize transport decoder {{{ 167 peer_alive = PEER_ALIVES.pop(peer_name, None) 168 if peer_alive is not None: 169 peer_alive.close() 170 await IN_QUEUES[peer_name].put(None) 171 PEER_ALIVES[peer_name] = writer 172 asyncio.ensure_future(msg_sender(peer_name, writer)) 173 # }}} 

msg_sender menerima pesan keluar (antri dari soket masuk), membuat serialisasi pesan itu menjadi pesan MsgText, dan mengirimkannya melalui koneksi TCP. Itu bisa putus kapan saja - kami jelas mencegatnya.

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

Pada akhirnya, inisiator memasuki siklus membaca pesan tanpa henti dari soket. Memeriksa apakah ini adalah pesan teks, dan menempatkan dalam OUT_QUEUES antrian dari mana mereka akan dikirim ke soket keluar dari lawan bicara yang sesuai. Mengapa Anda tidak bisa melakukan .read () saja dan mendekode pesannya? Karena ada kemungkinan bahwa beberapa pesan dari pengguna akan dikumpulkan dalam buffer sistem operasi dan dikirim oleh satu segmen TCP. Kita dapat memecahkan kode yang pertama, dan kemudian bagian dari yang berikutnya mungkin tetap dalam buffer. Dalam keadaan darurat apa pun, kami menutup koneksi TCP dan menghentikan msg_sender coroutine (dengan mengirim None ke antrian OUT_QUEUES).

  174 buf = b"" 175 # Wait for test messages {{{ 176 while True: 177 data = await reader.read(MaxMsgLen) 178 if data == b"": 179 break 180 buf += data 181 if len(buf) > MaxMsgLen: 182 logging.warning("%s: max buffer size exceeded", _id) 183 break 184 try: 185 msg, tail = Msg().decode(buf) 186 except ASN1Error: 187 continue 188 buf = tail 189 if msg.choice != "text": 190 logging.warning("%s: unexpected %s message", _id, msg.choice) 191 break 192 try: 193 await msg_receiver(msg.value, peer_name) 194 except ValueError as err: 195 logging.warning("%s: %s", err) 196 break 197 # }}} 198 logging.info("%s: disconnecting: %s", _id, peer_name) 199 IN_QUEUES[peer_name].put(None) 200 writer.close() 66 async def msg_receiver(msg_text: MsgText, peer_name: str) -> None: 67 text = str(msg_text["text"]) 68 logging.info("%s: received %d characters message", peer_name, len(text)) 69 await OUT_QUEUES[peer_name].put(text) 

Ayo kembali ke kode utama. Setelah membuat semua coroutine, pada saat memulai program, kami memulai server TCP. Untuk setiap koneksi yang ada, ia membuat coroutine responden.

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

Responden mirip dengan penggagas dan mencerminkan semua tindakan yang sama, tetapi loop tak berujung dari pesan pembacaan segera dimulai, untuk kesederhanaan. Sekarang protokol jabat tangan mengirim satu pesan dari setiap sisi, tetapi di masa depan, akan ada dua dari penggagas koneksi, setelah itu pesan teks dapat segera dikirim.

  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 # Read until we get Msg message {{{ 80 data = await reader.read(MaxMsgLen) 81 if data == b"": 82 logging.info("%s: closed connection", _id) 83 break 84 buf += data 85 if len(buf) > MaxMsgLen: 86 logging.warning("%s: max buffer size exceeded", _id) 87 break 88 try: 89 msg, tail = Msg().decode(buf) 90 except ASN1Error: 91 continue 92 buf = tail 93 # }}} 94 if msg.choice != msg_expected: 95 logging.warning("%s: unexpected %s message", _id, msg.choice) 96 break 97 if msg_expected == "text": 98 try: 99 await msg_receiver(msg.value, peer_name) 100 except ValueError as err: 101 logging.warning("%s: %s", err) 102 break 103 # Process Handshake message {{{ 104 elif msg_expected == "handshake": 105 logging.info("%s: got %s message", _id, msg_expected) 106 msg_handshake = msg.value 107 peer_name = str(msg_handshake["peerName"]) 108 if peer_name not in THEIR_NAMES: 109 logging.warning("unknown peer name: %s", peer_name) 110 break 111 writer.write(Msg(("handshake", MsgHandshake(( 112 ("peerName", OUR_NAME), 113 )))).encode()) 114 await writer.drain() 115 logging.info("%s: session established: %s", _id, peer_name) 116 peer_alive = PEER_ALIVES.pop(peer_name, None) 117 if peer_alive is not None: 118 peer_alive.close() 119 await IN_QUEUES[peer_name].put(None) 120 PEER_ALIVES[peer_name] = writer 121 asyncio.ensure_future(msg_sender(peer_name, writer)) 122 msg_expected = "text" 123 # }}} 124 logging.info("%s: disconnecting", _id) 125 if msg_expected == "text": 126 IN_QUEUES[peer_name].put(None) 127 writer.close() 

Protokol aman


Waktunya telah tiba untuk mengamankan komunikasi kita. Apa yang kami maksud dengan keamanan dan apa yang kami inginkan:

  • kerahasiaan pesan yang dikirimkan;
  • keaslian dan integritas pesan yang dikirim - perubahannya harus dideteksi;
  • perlindungan terhadap serangan replay - fakta bahwa pesan telah hilang atau coba lagi harus dideteksi (dan kami memutuskan untuk memutuskan sambungan);
  • identifikasi dan otentikasi lawan bicara dengan kunci publik pra-didorong - kami telah memutuskan sebelumnya bahwa kami membuat jaringan teman-ke-teman. Hanya setelah otentikasi akan kami pahami dengan siapa kami berkomunikasi;
  • kehadiran properti kerahasiaan ke depan yang sempurna (PFS) - kompromi dari kunci tanda tangan kami yang sudah berumur panjang seharusnya tidak mengarah pada kemungkinan membaca semua korespondensi sebelumnya. Merekam lalu lintas yang dicegat menjadi tidak berguna;
  • validitas / validitas pesan (transport dan jabat tangan) hanya dalam sesi TCP yang sama. Penyisipan pesan yang ditandatangani / diautentikasi dengan benar dari sesi lain (bahkan dengan teman bicara yang sama) tidak dimungkinkan;
  • pengamat pasif seharusnya tidak melihat pengidentifikasi pengguna, mentransmisikan kunci publik berumur panjang, atau hash dari mereka. Semacam anonimitas dari pengamat pasif.

Anehnya, hampir semua orang ingin memiliki minimum ini dalam protokol jabat tangan mana pun, dan sangat sedikit di atas yang pada akhirnya dilakukan untuk protokol yang dibuat sendiri. Jadi sekarang kita tidak akan menemukan hal-hal baru. Saya pasti akan merekomendasikan menggunakan kerangka kerja Noise untuk membangun protokol, tetapi mari kita pilih sesuatu yang lebih sederhana.

Yang paling populer adalah dua protokol:

  • TLS adalah protokol yang kompleks dengan sejarah panjang bug, sekolah, kerentanan, pemikiran yang buruk, kompleksitas dan kekurangan (namun, ini tidak berlaku banyak untuk TLS 1.3). Tetapi kami tidak menganggapnya karena kerumitannya.
  • IPsec dengan IKE - tidak memiliki masalah kriptografi yang serius, meskipun mereka juga tidak sederhana. Jika Anda membaca tentang IKEv1 dan IKEv2, sumbernya adalah protokol STS , ISO / IEC IS 9798-3 dan SIGMA (SIGn-and-MAc) - cukup sederhana untuk diimplementasikan dalam satu malam.

Bagaimana SIGMA, sebagai tautan terakhir dalam pengembangan protokol STS / ISO, bagus? Itu memenuhi semua persyaratan kami (termasuk "menyembunyikan" pengidentifikasi lawan bicara), tidak memiliki masalah kriptografi yang diketahui. Itu minimalis - menghapus setidaknya satu elemen dari pesan protokol akan menyebabkan rasa tidak amannya.

Mari kita beralih dari protokol homegrown yang paling sederhana ke SIGMA. Operasi paling mendasar yang kami minati adalah pencocokan kunci : suatu fungsi pada output yang mana kedua partisipan akan menerima nilai yang sama yang dapat digunakan sebagai kunci simetris. Tanpa merinci: masing-masing pihak menghasilkan pasangan kunci sesaat (hanya digunakan dalam sesi yang sama) (kunci publik dan pribadi), bertukar kunci publik, memanggil fungsi rekonsiliasi, ke input yang mereka kirimkan kunci pribadi mereka dan kunci publik dari lawan bicara.

 β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”
 β”‚PeerAβ”‚ β”‚PeerBβ”‚
 β””β”€β”€β”¬β”€β”€β”˜ β””β”€β”€β”¬β”€β”€β”˜
    β”‚ IdA, PubA β”‚ ╔════════════════════╗
    │────────────── >> β”‚ rPrvA, PubA = DHgen () β•‘
    β”‚ β”‚ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
    β”‚ IdB, PubB β”‚ ╔════════════════════╗
    β”‚ <──────────────│ β•‘PrvB, PubB = DHgen () β•‘
    β”‚ β”‚ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
    ────┐ ╔═══════════════════╗
        β”‚ β•‘Key = DH (PrvA, PubB) β•‘
    <β”€β”€β”€β”˜ β•šβ•β•β•β•β•β•β•β•€β•β•β•β•β•β•β•β•β•β•β•β•
    β”‚ β”‚
    β”‚ β”‚


Siapa pun dapat melakukan intervensi di tengah dan mengganti kunci publik dengan milik mereka - dalam protokol ini tidak ada otentikasi dari lawan bicara. Tambahkan tanda tangan dengan kunci berumur panjang.

 β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”
 β”‚PeerAβ”‚ β”‚PeerBβ”‚
 β””β”€β”€β”¬β”€β”€β”˜ β””β”€β”€β”¬β”€β”€β”˜
    β”‚IdA, PubA, tanda (SignPrvA, (PubA)) β”‚ ╔═══════════════════════╗
    β”‚ ─ ─ ─ ─ Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign, ,,,,,,,, SignPubA = load () β•‘
    β”‚ β”‚ β•‘PrvA, PubA = DHgen () β•‘
    β”‚ β”‚ β•šβ•β• ═ ═ ═ ═ ╝ ╝ ╝ ╝ ╝ ╝ ╝ ╝ ╝
    β”‚IdB, PubB, tanda (SignPrvB, (PubB)) β”‚ ╔═══════════════════════╗
    β”‚ <──────────────────────────────—
    β”‚ β”‚ β•‘PrvB, PubB = DHgen () β•‘
    β”‚ β”‚ β•šβ•β• ═ ═ ═ ═ ╝ ╝ ╝ ╝ ╝ ╝ ╝ ╝ ╝
    ────┐ ╔═════════════════════│ β”‚
        β”‚ β•‘verifikasikan (SignPubB, ...) β•‘ β”‚
    <β”€β”€β”€β”˜ β•‘Key = DH (PrvA, PubB) β•‘ β”‚
    β”‚ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• β”‚
    β”‚ β”‚


Tanda tangan seperti itu tidak akan berfungsi, karena tidak terikat pada sesi tertentu. Pesan semacam itu juga cocok untuk sesi dengan peserta lain. Seluruh konteks harus berlangganan. Ini juga memaksa penambahan pesan lain dari A.

Selain itu, sangat penting untuk menambahkan pengenal Anda sendiri sebagai tanda tangan, karena, jika tidak, kami dapat mengganti IdXXX dan menandatangani ulang pesan dengan kunci lawan bicara terkenal lainnya. Untuk mencegah serangan refleksi , elemen-elemen di bawah tanda tangan perlu berada di tempat-tempat yang jelas dalam artinya: jika tanda A (PubA, PubB), maka B harus masuk (PubB, PubA). Ini juga menunjukkan pentingnya memilih struktur dan format data serial. Sebagai contoh, set di ASN.1 DER encoding diurutkan: SET OF (PubA, PubB) akan identik dengan SET OF (PubB, PubA).

 β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”
 β”‚PeerAβ”‚ β”‚PeerBβ”‚
 β””β”€β”€β”¬β”€β”€β”˜ β””β”€β”€β”¬β”€β”€β”˜
    β”‚ IdA, PubA β”‚ ╔══════════════════════════
    β”‚ ─ ─ ─ ─ Ign ign ─ Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign Ign SignPubA = load () β•‘
    β”‚ β”‚ β•‘PrvA, PubA = DHgen () β•‘
    β”‚ β”‚ β•šβ•β• ═ ═ ═ ═ ╝ ╝ ╝ ╝ ╝ ╝ ╝ ╝ ╝
    β”‚IdB, PubB, tanda (SignPrvB, (IdB, PubA, PubB)) β”‚ ╔════════════════════╗
    β”‚ <───────────────────────────────────────── β•‘ignPrvB SignPubB = load () β•‘
    β”‚ β”‚ β•‘PrvB, PubB = DHgen () β•‘
    β”‚ β”‚ β•šβ•β• ═ ═ ═ ═ ╝ ╝ ╝ ╝ ╝ ╝ ╝ ╝ ╝
    β”‚ tanda (SignPrvA, (IdA, PubB, PubA)) β”‚ ╔═══════════════════╗
    β”‚ ─ ─ ─ ─ ─> Ify Ify Ify Ify Ify Ify Ify Ify Ify Ify Ify Ify Ify Ify Ify Ify Ify Ify Ify Ify Ify SignPubB, ...) β•‘
    β”‚ β”‚ β•‘Key = DH (PrvA, PubB) β•‘
    β”‚ β”‚ β•šβ•β• ═ ═ ═ ╝ ╝ ╝ ╝ ═ ╝ ╝ ╝
    β”‚ β”‚


Namun, kami masih belum "membuktikan" bahwa kami telah mengembangkan kunci bersama yang sama untuk sesi ini. Pada prinsipnya, Anda dapat melakukannya tanpa langkah ini - koneksi transportasi pertama tidak valid, tetapi kami ingin bahwa ketika jabat tangan selesai, kami akan yakin bahwa semuanya benar-benar disepakati. Saat ini, kami memiliki protokol ISO / IEC IS 9798-3.

Kita bisa menandatangani kunci itu sendiri. Ini berbahaya, karena ada kemungkinan bahwa ada kebocoran dalam algoritma tanda tangan yang digunakan (biarkan bit per tanda tangan, tetapi masih bocor). Anda dapat menandatangani hash dari kunci yang dihasilkan, tetapi bahkan kebocoran hash dari kunci yang dihasilkan dapat bernilai dalam serangan brute-force pada fungsi generasi. SIGMA menggunakan fungsi MAC yang mengotentikasi ID pengirim.

 β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”
 β”‚PeerAβ”‚ β”‚PeerBβ”‚
 β””β”€β”€β”¬β”€β”€β”˜ β””β”€β”€β”¬β”€β”€β”˜
    β”‚ IdA, PubA β”‚ ╔══════════════════════════
    β”‚ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ β”‚ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ > β”‚ β•‘SignPrvA, SignPubA = load () β•‘
    β”‚ β”‚ β•‘PrvA, PubA = DHgen () β•‘
    β”‚ β”‚ β•šβ•β• ═ ═ ═ ═ ╝ ╝ ╝ ╝ ╝ ╝ ╝ ╝ ╝
    β”‚IdB, PubB, tanda (SignPrvB, (PubA, PubB)), MAC (IdB) β”‚ ╔═════════════════╗
    β”‚ <─── ─ ─ ─ ─ ─ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ ─│ β•‘SignPrvB, SignPubB = load () β•‘
    β”‚ β”‚ β•‘PrvB, PubB = DHgen () β•‘
    β”‚ β”‚ β•šβ•β• ═ ═ ═ ═ ╝ ╝ ╝ ╝ ╝ ╝ ╝ ╝ ╝
    β”‚ β”‚ ╔══ ═ ═ ═ β•— β•— β•— β•— ═ β•— β•— β•—
    β”‚ tanda (SignPrvA, (PubB, PubA)), MAC (IdA) β”‚ β•‘Key = DH (PrvA, PubB) β•‘
    β”‚ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ β”‚ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ > β”‚ β•‘verifikasikan (Kunci, IdB) β•‘
    β”‚ β”‚ β•‘verifikasikan (SignPubB, ...) β•‘
    β”‚ β”‚ β•šβ•β• ═ ═ ═ ╝ ╝ ╝ ╝ ═ ╝ ╝ ╝
    β”‚ β”‚


Sebagai optimasi, beberapa mungkin ingin menggunakan kembali kunci sementara mereka (yang, tentu saja, menyedihkan untuk PFS). Sebagai contoh, kami membuat pasangan kunci, mencoba terhubung, tetapi TCP tidak tersedia atau terputus di suatu tempat di tengah protokol. Sangat disayangkan menghabiskan entropi dan sumber daya prosesor yang dihabiskan untuk pasangan baru. Oleh karena itu, kami memperkenalkan cookie yang disebut - nilai pseudo-acak yang akan melindungi terhadap kemungkinan serangan replay yang tidak disengaja ketika menggunakan kembali kunci publik sesaat. Karena pengikatan antara cookie dan kunci publik singkat, kunci publik dari pihak yang berlawanan dapat dihapus dari tanda tangan sebagai tidak perlu.

 β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”
 β”‚PeerAβ”‚ β”‚PeerBβ”‚
 β””β”€β”€β”¬β”€β”€β”˜ β””β”€β”€β”¬β”€β”€β”˜
    β”‚ IdA, PubA, CookieA β”‚ ╔═════════════════════════
    β”‚ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ β”‚ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─────────────────── >> β”‚ β”‚ SignPrvA, SignPubA = load () β•‘
    β”‚ β”‚ β•‘PrvA, PubA = DHgen () β•‘
    β”‚ β”‚ β•šβ•β• ═ ═ ═ ═ ╝ ╝ ╝ ╝ ╝ ╝ ╝ ╝ ╝
    β”‚IdB, PubB, CookieB, tanda (SignPrvB, (CookieA, CookieB, PubB)), MAC (IdB) β”‚ ╔═══════════════════════ ═══╗
    β”‚ <─── ─ ─ ─ ─ ─ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ ─────────────────────│ ignSignPrvB, SignPubB = load () β•‘
    β”‚ β”‚ β•‘PrvB, PubB = DHgen () β•‘
    β”‚ β”‚ β•šβ•β• ═ ═ ═ ═ ╝ ╝ ╝ ╝ ╝ ╝ ╝ ╝ ╝
    β”‚ β”‚ ╔══ ═ ═ ═ β•— β•— β•— β•— ═ β•— β•— β•—
    β”‚ tanda (SignPrvA, (CookieB, CookieA, PubA)), MAC (IdA) β”‚ β•‘Key = DH (PrvA, PubB) β•‘
    β”‚ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ β”‚ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ Ify Ify Ify Ify Ify Ify Ify Ify Ify Ify Ify Ify Verifikasi (Kunci, IdB) β•‘
    β”‚ β”‚ β•‘verifikasikan (SignPubB, ...) β•‘
    β”‚ β”‚ β•šβ•β• ═ ═ ═ ╝ ╝ ╝ ╝ ═ ╝ ╝ ╝
    β”‚ β”‚


Akhirnya, kami ingin mendapatkan privasi pengidentifikasi lawan bicara dari pengamat pasif. Untuk melakukan ini, SIGMA menyarankan pertama kali bertukar kunci sementara, membuat kunci umum yang digunakan untuk mengotentikasi pesan otentikasi. SIGMA menjelaskan dua opsi:

  • SIGMA-I - melindungi inisiator dari serangan aktif, responden dari yang pasif: inisiator mengotentikasi responden dan jika ada sesuatu yang tidak cocok, maka itu tidak memberikan identifikasi. Terdakwa memberikan identitasnya jika Anda memulai protokol aktif dengannya. Pengamat pasif tidak akan tahu apa-apa;
    SIGMA-R - melindungi responden dari serangan aktif, inisiator dari pasif. Semuanya justru sebaliknya, tetapi dalam protokol ini sudah empat pesan jabat tangan dikirimkan.


    Kami memilih SIGMA-I sebagai lebih mirip dengan apa yang kami harapkan dari hal-hal yang biasa terjadi pada klien-server: hanya server yang diautentikasi yang mengenali klien, dan semua orang tahu server itu. Plus lebih mudah untuk diterapkan karena lebih sedikit pesan jabat tangan. Yang kami tambahkan ke protokol adalah enkripsi bagian pesan dan transfer pengidentifikasi A ke bagian terenkripsi dari pesan terakhir:

     β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”
     β”‚PeerAβ”‚ β”‚PeerBβ”‚
     β””β”€β”€β”¬β”€β”€β”˜ β””β”€β”€β”¬β”€β”€β”˜
        β”‚ PubA, CookieA β”‚ ╔══════════════════════════╗
        β”‚ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ β”‚ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ Ign Ign Ign Ign Ign Ign Ign Ign Ign β•‘ Ign ,,,,,, Sign Sign Sign Sign Sign Sign Sign Sign Sign Sign Sign Load Sign Load Load Load Load Load Load Load ((((((((((((
        β”‚ β”‚ β•‘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) # Parse and unmarshal our and their keys {{{ with open(args.keys, "rb") as fd: _keys = json.loads(fd.read().decode("utf-8")) KEY_OUR_SIGN_PRV = gost3410.prv_unmarshal(hexdec(_keys["our"]["prv"])) _pub = hexdec(_keys["our"]["pub"]) KEY_OUR_SIGN_PUB = gost3410.pub_unmarshal(_pub) KEY_OUR_SIGN_PUB_HASH = OctetString(GOST34112012256(_pub).digest()) for peer_name, pub_raw in _keys["their"].items(): _pub = hexdec(pub_raw) KEYS[GOST34112012256(_pub).digest()] = { "name": peer_name, "pub": gost3410.pub_unmarshal(_pub), } # }}} 

    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 # Generate our ephemeral public key and cookie, send Handshake 0 message {{{ 400 cookie_our = Cookie(urandom(16)) 401 prv = gost3410.prv_unmarshal(urandom(32)) 402 pub_our = gost3410.public_key(CURVE, prv) 403 pub_our_raw = PubKey(gost3410.pub_marshal(pub_our)) 404 writer.write(Msg(("handshake0", MsgHandshake0(( 405 ("cookieInitiator", cookie_our), 406 ("pubKeyInitiator", pub_our_raw), 407 )))).encode()) 408 # }}} 409 await writer.drain() 

    • Msg ;
    • handshake1;
    • ;
    • TBE .

      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 # }}} 429 msg_handshake1 = msg.value 430 # Validate Handshake message {{{ 431 cookie_their = msg_handshake1["cookieResponder"] 432 pub_their_raw = msg_handshake1["pubKeyResponder"] 433 pub_their = gost3410.pub_unmarshal(bytes(pub_their_raw)) 434 ukm_raw = bytes(msg_handshake1["ukm"]) 435 ukm = ukm_unmarshal(ukm_raw) 436 key_session = kek_34102012256(CURVE, prv, pub_their, ukm, mode=2001) 437 kdf = Hkdf(None, key_session, hash=GOST34112012256) 438 key_handshake1_mac_identity = kdf.expand(b"handshake1-mac-identity") 439 key_handshake1_enc = kdf.expand(b"handshake1-enc") 440 key_handshake1_mac = kdf.expand(b"handshake1-mac") 

    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 # }}} 128 def validate_tbe( 129 msg_handshake: Union[MsgHandshake1, MsgHandshake2], 130 key_mac_identity: bytes, 131 key_enc: bytes, 132 key_mac: bytes, 133 cookie_their: Cookie, 134 cookie_our: Cookie, 135 pub_key_our: PubKey, 136 ) -> str: 137 ciphertext = bytes(msg_handshake["ciphertext"]) 138 mac_tag = mac(GOST3412Kuznechik(key_mac).encrypt, KUZNECHIK_BLOCKSIZE, ciphertext) 139 if not compare_digest(mac_tag, bytes(msg_handshake["ciphertextMac"])): 140 raise ValueError("invalid MAC") 141 plaintext = ctr( 142 GOST3412Kuznechik(key_enc).encrypt, 143 KUZNECHIK_BLOCKSIZE, 144 ciphertext, 145 8 * b"\x00", 146 ) 147 try: 148 tbe, _ = HandshakeTBE().decode(plaintext) 149 except ASN1Error: 150 raise ValueError("can not decode TBE") 151 key_sign_pub_hash = bytes(tbe["identity"]) 152 peer = KEYS.get(key_sign_pub_hash) 153 if peer is None: 154 raise ValueError("unknown identity") 155 mac_tag = mac( 156 GOST3412Kuznechik(key_mac_identity).encrypt, 157 KUZNECHIK_BLOCKSIZE, 158 key_sign_pub_hash, 159 ) 160 if not compare_digest(mac_tag, bytes(tbe["identityMac"])): 161 raise ValueError("invalid identity MAC") 162 tbs = HandshakeTBS(( 163 ("cookieTheir", cookie_their), 164 ("cookieOur", cookie_our), 165 ("pubKeyOur", pub_key_our), 166 )) 167 if not gost3410.verify( 168 CURVE, 169 peer["pub"], 170 GOST34112012256(tbs.encode()).digest(), 171 bytes(tbe["signature"]), 172 ): 173 raise ValueError("invalid signature") 174 return peer["name"] 

    , 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 # Prepare and send Handshake 2 message {{{ 457 tbs = HandshakeTBS(( 458 ("cookieTheir", cookie_their), 459 ("cookieOur", cookie_our), 460 ("pubKeyOur", pub_our_raw), 461 )) 462 signature = gost3410.sign( 463 CURVE, 464 KEY_OUR_SIGN_PRV, 465 GOST34112012256(tbs.encode()).digest(), 466 ) 467 key_handshake2_mac_identity = kdf.expand(b"handshake2-mac-identity") 468 mac_tag = mac( 469 GOST3412Kuznechik(key_handshake2_mac_identity).encrypt, 470 KUZNECHIK_BLOCKSIZE, 471 bytes(KEY_OUR_SIGN_PUB_HASH), 472 ) 473 tbe = HandshakeTBE(( 474 ("identity", KEY_OUR_SIGN_PUB_HASH), 475 ("signature", OctetString(signature)), 476 ("identityMac", MAC(mac_tag)), 477 )) 478 tbe_raw = tbe.encode() 479 key_handshake2_enc = kdf.expand(b"handshake2-enc") 480 key_handshake2_mac = kdf.expand(b"handshake2-mac") 481 ciphertext = ctr( 482 GOST3412Kuznechik(key_handshake2_enc).encrypt, 483 KUZNECHIK_BLOCKSIZE, 484 tbe_raw, 485 8 * b"\x00", 486 ) 487 mac_tag = mac( 488 GOST3412Kuznechik(key_handshake2_mac).encrypt, 489 KUZNECHIK_BLOCKSIZE, 490 ciphertext, 491 ) 492 writer.write(Msg(("handshake2", MsgHandshake2(( 493 ("ciphertext", OctetString(ciphertext)), 494 ("ciphertextMac", MAC(mac_tag)), 495 )))).encode()) 496 # }}} 497 await writer.drain() 498 logging.info("%s: session established: %s", _id, peer_name) 

    , ( , , ), MAC-:

      499 # Run text message sender, initialize transport decoder {{{ 500 key_initiator_enc = kdf.expand(b"transport-initiator-enc") 501 key_initiator_mac = kdf.expand(b"transport-initiator-mac") 502 key_responder_enc = kdf.expand(b"transport-responder-enc") 503 key_responder_mac = kdf.expand(b"transport-responder-mac") ... 509 asyncio.ensure_future(msg_sender( 510 peer_name, 511 key_initiator_enc, 512 key_initiator_mac, 513 writer, 514 )) 515 encrypter = GOST3412Kuznechik(key_responder_enc).encrypt 516 macer = GOST3412Kuznechik(key_responder_mac).encrypt 517 # }}} 519 nonce_expected = 0 520 # Wait for test messages {{{ 521 while True: 522 data = await reader.read(MaxMsgLen) ... 530 msg, tail = Msg().decode(buf) ... 537 try: 538 await msg_receiver( 539 msg.value, 540 nonce_expected, 541 macer, 542 encrypter, 543 peer_name, 544 ) 545 except ValueError as err: 546 logging.warning("%s: %s", err) 547 break 548 nonce_expected += 1 549 # }}} 

    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) 

    Kesimpulan


    GOSTIM ( , )! (-256 : 995bbd368c04e50a481d138c5fa2e43ec7c89bc77743ba8dbabee1fde45de120). , GoGOST , PyDERASN , NNCP , GoVPN , GOSTIM , GPLv3+ .

    , , , Python/Go-, Β« β€žβ€œ .

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


All Articles