ProHoster > Blog > Pentadbiran > GOSTIM: P2P F2F E2EE IM dalam satu petang dengan kriptografi GOST
GOSTIM: P2P F2F E2EE IM dalam satu petang dengan kriptografi GOST
Menjadi pemaju PyGOST perpustakaan (primitif kriptografi GOST dalam Python tulen), saya sering menerima soalan tentang cara melaksanakan pemesejan selamat yang paling mudah pada lutut. Ramai orang menganggap kriptografi gunaan agak mudah, dan memanggil .encrypt() pada sifir blok sudah cukup untuk menghantarnya dengan selamat melalui saluran komunikasi. Yang lain percaya bahawa kriptografi gunaan adalah nasib segelintir orang, dan boleh diterima bahawa syarikat kaya seperti Telegram dengan ahli matematik olimpiade tidak boleh melaksanakan protokol selamat.
Semua ini mendorong saya untuk menulis artikel ini untuk menunjukkan bahawa melaksanakan protokol kriptografi dan IM selamat bukanlah tugas yang sukar. Walau bagaimanapun, ia tidak berbaloi untuk mencipta pengesahan anda sendiri dan protokol perjanjian utama.
Artikel akan menulis peer-to-peer, kawan ke kawan, disulitkan hujung ke hujung utusan segera dengan SIGMA-I pengesahan dan protokol perjanjian utama (berdasarkan ia dilaksanakan IPsec IKE), menggunakan algoritma kriptografi GOST secara eksklusif perpustakaan PyGOST dan perpustakaan pengekodan mesej ASN.1 PyDERASN (tentang saya sudah menulis sebelum ini). Prasyarat: ia mestilah sangat mudah sehingga ia boleh ditulis dari awal dalam satu petang (atau hari kerja), jika tidak, ia bukan lagi program yang mudah. Ia mungkin mempunyai ralat, komplikasi yang tidak perlu, kekurangan, dan ini adalah program pertama saya menggunakan perpustakaan asyncio.
Reka bentuk IM
Pertama, kita perlu memahami bagaimana rupa IM kita. Untuk kesederhanaan, biarkan ia menjadi rangkaian peer-to-peer, tanpa sebarang penemuan peserta. Kami secara peribadi akan menunjukkan alamat mana: port untuk disambungkan untuk berkomunikasi dengan lawan bicara.
Saya faham bahawa, pada masa ini, andaian bahawa komunikasi langsung tersedia antara dua komputer sewenang-wenangnya adalah had ketara ke atas kebolehgunaan IM dalam amalan. Tetapi semakin banyak pembangun melaksanakan semua jenis tongkat traversal NAT, semakin lama kita akan kekal di Internet IPv4, dengan kebarangkalian komunikasi yang menyedihkan antara komputer sewenang-wenangnya. Berapa lama anda boleh bertolak ansur dengan kekurangan IPv6 di rumah dan di tempat kerja?
Kami akan mempunyai rangkaian rakan ke rakan: semua rakan bicara yang mungkin mesti diketahui lebih awal. Pertama, ini sangat memudahkan segala-galanya: kami memperkenalkan diri kami, menjumpai atau tidak menjumpai nama/kunci, terputus sambungan atau terus bekerja, mengetahui lawan bicara. Kedua, secara umum, ia selamat dan menghapuskan banyak serangan.
Antara muka IM akan hampir dengan penyelesaian klasik projek yang tidak berguna, yang saya sangat suka kerana minimalisme dan falsafah Unix-way mereka. Program IM mencipta direktori dengan tiga soket domain Unix untuk setiap lawan bicara:
dalamβmesej yang dihantar kepada lawan bicara direkodkan di dalamnya;
keluar - mesej yang diterima daripada lawan bicara dibaca daripadanya;
negeri - dengan membaca daripadanya, kita mengetahui sama ada lawan bicara sedang disambungkan, alamat/port sambungan.
Di samping itu, soket sambung dicipta, dengan menulis port hos di mana kami memulakan sambungan kepada lawan bicara jauh.
|-- alice
| |-- in
| |-- out
| `-- state
|-- bob
| |-- in
| |-- out
| `-- state
`- conn
Pendekatan ini membolehkan anda membuat pelaksanaan bebas pengangkutan IM dan antara muka pengguna, kerana tiada rakan, anda tidak boleh menggembirakan semua orang. menggunakan tmux dan / atau berbilang ekor, anda boleh mendapatkan antara muka berbilang tetingkap dengan penyerlahan sintaks. Dan dengan bantuan rlwrap anda boleh mendapatkan baris input mesej serasi GNU Readline.
Malah, projek tidak berguna menggunakan fail FIFO. Secara peribadi, saya tidak dapat memahami cara bekerja dengan fail secara kompetitif dalam asyncio tanpa latar belakang tulisan tangan daripada benang khusus (saya telah menggunakan bahasa untuk perkara sedemikian untuk masa yang lama Go). Oleh itu, saya memutuskan untuk membuat kaitan dengan soket domain Unix. Malangnya, ini menjadikannya mustahil untuk melakukan echo 2001:470:dead::babe 6666 > conn. Saya menyelesaikan masalah ini menggunakan socat: echo 2001:470:dead::babe 6666 | socat - UNIX-CONNECT:conn, socat READLINE UNIX-CONNECT:alice/in.
Protokol tidak selamat asal
TCP digunakan sebagai pengangkutan: ia menjamin penghantaran dan pesanannya. UDP tidak menjamin kedua-duanya (yang akan berguna apabila kriptografi digunakan), tetapi menyokong SCTP Python tidak keluar dari kotak.
Malangnya, dalam TCP tidak ada konsep mesej, hanya aliran bait. Oleh itu, adalah perlu untuk menghasilkan format untuk mesej supaya mereka boleh dikongsi sesama mereka dalam urutan ini. Kami boleh bersetuju untuk menggunakan aksara suapan baris. Tidak mengapa untuk permulaan, tetapi sebaik sahaja kami mula menyulitkan mesej kami, watak ini mungkin muncul di mana-mana dalam teks sifir. Dalam rangkaian, oleh itu, protokol popular adalah yang mula-mula menghantar panjang mesej dalam bait. Sebagai contoh, di luar kotak Python mempunyai xdrlib, yang membolehkan anda bekerja dengan format yang serupa XDR.
Kami tidak akan berfungsi dengan betul dan cekap dengan bacaan TCP - kami akan memudahkan kod. Kami membaca data dari soket dalam gelung yang tidak berkesudahan sehingga kami menyahkod mesej lengkap. JSON dengan XML juga boleh digunakan sebagai format untuk pendekatan ini. Tetapi apabila kriptografi ditambah, data perlu ditandatangani dan disahkan - dan ini memerlukan perwakilan yang sama bait demi bait bagi objek, yang tidak disediakan oleh JSON/XML (hasil pembuangan mungkin berbeza-beza).
XDR sesuai untuk tugasan ini, namun saya memilih ASN.1 dengan pengekodan DER dan PyDERASN perpustakaan, kerana kita akan mempunyai objek peringkat tinggi di tangan yang selalunya lebih menyenangkan dan mudah untuk digunakan. Tidak seperti tanpa skema bencode, Pek Mesej atau CBOR, ASN.1 akan menyemak data secara automatik terhadap skema berkod keras.
Mesej yang diterima ialah Msg: sama ada MsgText teks (dengan satu medan teks buat masa ini) atau mesej jabat tangan MsgHandshake (yang mengandungi nama lawan bicara). Sekarang ia kelihatan terlalu rumit, tetapi ini adalah asas untuk masa depan.
Seperti yang telah saya katakan, perpustakaan asyncio akan digunakan untuk semua operasi soket. Mari umumkan perkara yang kami jangkakan semasa pelancaran:
Tetapkan nama anda sendiri (--nama-kami alice). Semua lawan bicara yang dijangkakan disenaraikan dipisahkan dengan koma (βnama mereka bob,eve). Untuk setiap lawan bicara, direktori dengan soket Unix dibuat, serta coroutine untuk setiap masuk, keluar, nyatakan:
Apabila membaca dari soket keadaan, program mencari alamat lawan bicara dalam kamus PEER_ALIVE. Jika belum ada sambungan dengan lawan bicara, maka baris kosong 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()
Apabila menulis alamat ke soket sambungan, fungsi "pemula" sambungan dilancarkan:
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))
Mari kita pertimbangkan pemula. Mula-mula ia jelas membuka sambungan ke hos/port yang ditentukan dan menghantar mesej jabat tangan dengan namanya:
Kemudian, ia menunggu jawapan daripada pihak jauh. Cuba menyahkod respons masuk menggunakan skema Msg ASN.1. Kami menganggap bahawa keseluruhan mesej akan dihantar dalam satu segmen TCP dan kami akan menerimanya secara atom apabila memanggil .read(). Kami menyemak sama ada kami menerima mesej berjabat tangan.
Kami menyemak sama ada nama yang diterima daripada lawan bicara diketahui oleh kami. Jika tidak, maka kita memutuskan sambungan. Kami menyemak sama ada kami telah menjalinkan hubungan dengannya (pembicara sekali lagi memberi arahan untuk menyambung kepada kami) dan menutupnya. Baris gilir IN_QUEUES memegang rentetan Python dengan teks mesej, tetapi mempunyai nilai istimewa None yang memberi isyarat kepada coroutine msg_sender untuk berhenti berfungsi supaya ia melupakan penulisnya yang dikaitkan dengan sambungan TCP warisan.
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 mesej keluar (dibaris gilir dari soket dalam), mensirikannya ke dalam mesej MsgText dan menghantarnya melalui sambungan TCP. Ia boleh pecah pada bila-bila masa - kami memintas dengan jelas.
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 penghujungnya, pemula memasuki gelung tak terhingga untuk membaca mesej daripada soket. Menyemak sama ada mesej ini ialah mesej teks dan meletakkannya dalam baris gilir OUT_QUEUES, dari mana ia akan dihantar ke soket keluar rakan bicara yang sepadan. Mengapa anda tidak boleh melakukan .read() dan menyahkod mesej? Kerana ada kemungkinan beberapa mesej daripada pengguna akan diagregatkan dalam penimbal sistem pengendalian dan dihantar dalam satu segmen TCP. Kita boleh menyahkod yang pertama, dan kemudian sebahagian daripada yang berikutnya mungkin kekal dalam penimbal. Sekiranya berlaku sebarang situasi yang tidak normal, kami menutup sambungan TCP dan menghentikan coroutine msg_sender (dengan menghantar Tiada ke baris gilir 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)
Mari kembali ke kod utama. Selepas mencipta semua coroutine pada masa program bermula, kami memulakan pelayan TCP. Untuk setiap sambungan yang ditubuhkan, ia mencipta coroutine responder.
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()
responder adalah serupa dengan pemula dan mencerminkan semua tindakan yang sama, tetapi gelung tak terhingga untuk membaca mesej bermula serta-merta, untuk kesederhanaan. Pada masa ini, protokol jabat tangan menghantar satu mesej dari setiap sisi, tetapi pada masa hadapan akan ada dua daripada pemula sambungan, selepas itu mesej teks boleh dihantar serta-merta.
Sudah tiba masanya untuk menjamin komunikasi kita. Apakah yang kami maksudkan dengan keselamatan dan apa yang kami mahu:
kerahsiaan mesej yang dihantar;
keaslian dan integriti mesej yang dihantar - perubahannya mesti dikesan;
perlindungan terhadap serangan ulang - fakta mesej yang hilang atau berulang mesti dikesan (dan kami memutuskan untuk menamatkan sambungan);
pengenalpastian dan pengesahan lawan bicara menggunakan kunci awam yang telah dimasukkan sebelumnya - kami telah memutuskan sebelum ini bahawa kami membuat rangkaian rakan ke rakan. Hanya selepas pengesahan kami akan memahami dengan siapa kami berkomunikasi;
ketersediaan kerahsiaan hadapan yang sempurna hartanah (PFS) - menjejaskan kunci tandatangan jangka panjang kami tidak seharusnya membawa kepada keupayaan untuk membaca semua surat-menyurat sebelumnya. Merakam trafik yang dipintas menjadi tidak berguna;
kesahihan/kesahan mesej (pengangkutan dan jabat tangan) hanya dalam satu sesi TCP. Memasukkan mesej yang ditandatangani/disahkan dengan betul daripada sesi lain (walaupun dengan lawan bicara yang sama) tidak boleh dilakukan;
pemerhati pasif tidak seharusnya melihat sama ada pengecam pengguna, menghantar kunci awam tahan lama atau cincang daripadanya. Tanpa nama tertentu daripada pemerhati pasif.
Anehnya, hampir semua orang mahu mempunyai minimum ini dalam mana-mana protokol jabat tangan, dan sangat sedikit perkara di atas akhirnya dipenuhi untuk protokol "tanah sendiri". Sekarang kami tidak akan mencipta sesuatu yang baharu. Saya pasti akan mengesyorkan menggunakan Rangka kerja bunyi untuk membina protokol, tetapi mari kita pilih sesuatu yang lebih mudah.
Dua protokol yang paling popular ialah:
TLS - protokol yang sangat kompleks dengan sejarah panjang pepijat, jambs, kelemahan, pemikiran yang buruk, kerumitan dan kekurangan (namun, ini tidak ada kaitan dengan TLS 1.3). Tetapi kami tidak menganggapnya kerana ia terlalu rumit.
IPsec Ρ IKE β tidak mempunyai masalah kriptografi yang serius, walaupun ia juga tidak mudah. Jika anda membaca tentang IKEv1 dan IKEv2, maka sumber mereka adalah STS, protokol ISO/IEC IS 9798-3 dan SIGMA (SIGn-and-MAc) - cukup mudah untuk dilaksanakan dalam satu petang.
Apakah kebaikan SIGMA, sebagai pautan terkini dalam pembangunan protokol STS/ISO? Ia memenuhi semua keperluan kami (termasuk "menyembunyikan" pengecam interlocutor) dan tidak mempunyai masalah kriptografi yang diketahui. Ia minimalis - mengalih keluar sekurang-kurangnya satu elemen daripada mesej protokol akan membawa kepada ketidakamanannya.
Mari kita beralih daripada protokol yang paling mudah ditanam di rumah kepada SIGMA. Operasi paling asas yang kami minati ialah perjanjian kunci: Fungsi yang mengeluarkan kedua-dua peserta nilai yang sama, yang boleh digunakan sebagai kunci simetri. Tanpa pergi ke butiran: setiap pihak menjana pasangan kunci sementara (hanya digunakan dalam satu sesi) (kunci awam dan peribadi), menukar kunci awam, memanggil fungsi perjanjian, kepada input yang mereka luluskan kunci peribadi mereka dan orang awam kunci lawan bicara.
Sesiapa sahaja boleh melompat di tengah dan menggantikan kunci awam dengan kunci mereka sendiri - tiada pengesahan rakan bicara dalam protokol ini. Mari tambahkan tandatangan dengan kunci tahan lama.
Tandatangan sedemikian tidak akan berfungsi, kerana ia tidak terikat pada sesi tertentu. Mesej sedemikian juga "sesuai" untuk sesi dengan peserta lain. Seluruh konteks mesti melanggan. Ini memaksa kami untuk menambah satu lagi mesej daripada A.
Di samping itu, adalah penting untuk menambah pengecam anda sendiri di bawah tandatangan, kerana jika tidak, kami boleh menggantikan IdXXX dan menandatangani semula mesej dengan kunci rakan bicara lain yang dikenali. Untuk mengelakkan serangan refleksi, adalah perlu bahawa elemen di bawah tandatangan berada di tempat yang ditakrifkan dengan jelas mengikut maksudnya: jika tanda A (PubA, PubB), maka B mesti menandatangani (PubB, PubA). Ini juga bercakap tentang kepentingan memilih struktur dan format data bersiri. Contohnya, set dalam pengekodan ASN.1 DER diisih: SET OF(PubA, PubB) akan sama dengan SET OF(PubB, PubA).
Walau bagaimanapun, kami masih belum "membuktikan" bahawa kami telah menjana kunci kongsi yang sama untuk sesi ini. Pada dasarnya, kita boleh lakukan tanpa langkah ini - sambungan pengangkutan pertama akan menjadi tidak sah, tetapi kami mahu apabila jabat tangan selesai, kami akan memastikan bahawa semuanya benar-benar dipersetujui. Pada masa ini kami mempunyai protokol ISO/IEC IS 9798-3.
Kami boleh menandatangani kunci yang dijana itu sendiri. Ini berbahaya, kerana mungkin terdapat kebocoran dalam algoritma tandatangan yang digunakan (walaupun bit-per-tandatangan, tetapi masih bocor). Anda boleh menandatangani cincang kunci terbitan, tetapi cincangan kunci terbitan yang bocor sekalipun boleh menjadi berharga dalam serangan kekerasan terhadap fungsi terbitan. SIGMA menggunakan fungsi MAC yang mengesahkan ID pengirim.
Sebagai pengoptimuman, sesetengah orang mungkin mahu menggunakan semula kunci sementara mereka (yang, sudah tentu, malang untuk PFS). Sebagai contoh, kami menjana pasangan kunci, cuba menyambung, tetapi TCP tidak tersedia atau terganggu di suatu tempat di tengah-tengah protokol. Sungguh memalukan untuk membazirkan sumber entropi dan pemproses pada pasangan baharu. Oleh itu, kami akan memperkenalkan apa yang dipanggil kuki - nilai rawak pseudo yang akan melindungi daripada kemungkinan serangan ulang tayang rawak apabila menggunakan semula kunci awam yang tidak lama. Disebabkan pengikatan antara kuki dan kunci awam yang tidak lama, kunci awam pihak bertentangan boleh dialih keluar daripada tandatangan sebagai tidak perlu.
Akhir sekali, kami ingin mendapatkan privasi rakan perbualan kami daripada pemerhati pasif. Untuk melakukan ini, SIGMA bercadang untuk menukar kunci sementara dan membangunkan kunci biasa untuk menyulitkan pengesahan dan pengecaman mesej. SIGMA menerangkan dua pilihan:
SIGMA-I - melindungi pemula daripada serangan aktif, responden daripada serangan pasif: pemula mengesahkan responden dan jika sesuatu tidak sepadan, maka ia tidak memberikan pengenalannya. Defendan memberikan pengenalannya jika protokol aktif dimulakan dengannya. Pemerhati pasif tidak belajar apa-apa;
SIGMA-R - melindungi responden daripada serangan aktif, pemula daripada serangan pasif. Semuanya betul-betul bertentangan, tetapi dalam protokol ini empat mesej jabat tangan sudah dihantar.
Kami memilih SIGMA-I kerana ia lebih serupa dengan apa yang kami harapkan daripada perkara biasa pelayan klien: klien hanya dikenali oleh pelayan yang disahkan, dan semua orang sudah mengetahui pelayan. Selain itu, ia lebih mudah untuk dilaksanakan kerana lebih sedikit mesej jabat tangan. Apa yang kami tambahkan pada protokol adalah untuk menyulitkan sebahagian daripada mesej dan memindahkan pengecam A ke bahagian yang disulitkan pada mesej terakhir:
GOST R digunakan untuk tandatangan 34.10-2012 algoritma dengan kekunci 256-bit.
Untuk menjana kunci awam, 34.10/2012/XNUMX VKO digunakan.
CMAC digunakan sebagai MAC. Secara teknikal, ini adalah mod operasi khas sifir blok, yang diterangkan dalam GOST R 34.13-2015. Sebagai fungsi penyulitan untuk mod ini β Belalang (34.12-2015).
Cincang kunci awamnya digunakan sebagai pengecam lawan bicara. Digunakan sebagai hash Stribog-256 (34.11/2012/256 XNUMX bit).
Selepas berjabat tangan, kami akan bersetuju dengan kunci yang dikongsi. Kami boleh menggunakannya untuk penyulitan yang disahkan bagi mesej pengangkutan. Bahagian ini sangat mudah dan sukar untuk membuat kesilapan: kami menambah pembilang mesej, menyulitkan mesej, mengesahkan (MAC) pembilang dan teks sifir, menghantar. Apabila menerima mesej, kami menyemak sama ada kaunter mempunyai nilai yang dijangkakan, mengesahkan teks sifir dengan kaunter dan menyahsulitnya. Apakah kunci yang harus saya gunakan untuk menyulitkan mesej jabat tangan, mengangkut mesej dan cara untuk mengesahkannya? Menggunakan satu kunci untuk semua tugas ini adalah berbahaya dan tidak bijak. Ia adalah perlu untuk menjana kunci menggunakan fungsi khusus KDF (fungsi terbitan kunci). Sekali lagi, janganlah kita membelah rambut dan mencipta sesuatu: HKDF telah lama diketahui, diteliti dengan baik dan tidak mempunyai masalah yang diketahui. Malangnya, perpustakaan Python asli tidak mempunyai fungsi ini, jadi kami gunakan hkdf beg plastik. HKDF digunakan secara dalaman HMAC, yang seterusnya menggunakan fungsi cincang. Contoh pelaksanaan dalam Python pada halaman Wikipedia hanya memerlukan beberapa baris kod. Seperti dalam kes 34.10/2012/256, kami akan menggunakan Stribog-XNUMX sebagai fungsi cincang. Output fungsi perjanjian utama kami akan dipanggil kunci sesi, yang daripadanya yang simetri yang hilang akan dijana:
HandshakeTBS adalah perkara yang akan ditandatangani. Jabat TanganTBE - perkara yang akan disulitkan. Saya menarik perhatian anda kepada bidang ukm dalam MsgHandshake1. 34.10 VKO, untuk rawak yang lebih besar bagi kunci yang dijana, termasuk parameter UKM (bahan kunci pengguna) - hanya entropi tambahan.
Menambah Kriptografi pada Kod
Mari kita pertimbangkan hanya perubahan yang dibuat pada kod asal, kerana rangka kerja tetap sama (sebenarnya, pelaksanaan terakhir ditulis terlebih dahulu, dan kemudian semua kriptografi dipotong daripadanya).
Memandangkan pengesahan dan pengenalpastian rakan bicara akan dijalankan menggunakan kunci awam, mereka kini perlu disimpan di suatu tempat untuk masa yang lama. Untuk kesederhanaan, kami menggunakan JSON seperti ini:
kami - pasangan kunci kami, kunci persendirian dan awam perenambelasan. mereka β nama rakan bicara dan kunci awam mereka. Mari kita ubah argumen baris arahan dan tambahkan pasca pemprosesan data 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),
}
# }}}
Kunci persendirian algoritma 34.10 ialah nombor rawak. Saiz 256-bit untuk lengkung elips 256-bit. PyGOST tidak berfungsi dengan set bait, tetapi dengan bilangan yang besar, jadi kunci peribadi kami (urandom(32)) perlu ditukar kepada nombor menggunakan gost3410.prv_unmarshal(). Kunci awam ditentukan secara deterministik daripada kunci persendirian menggunakan gost3410.public_key(). Kunci awam 34.10 ialah dua nombor besar yang juga perlu ditukar kepada jujukan bait untuk memudahkan penyimpanan dan penghantaran menggunakan gost3410.pub_marshal().
Selepas membaca fail JSON, kunci awam sewajarnya perlu ditukar kembali menggunakan gost3410.pub_unmarshal(). Memandangkan kami akan menerima pengecam interlocutor dalam bentuk cincangan daripada kunci awam, mereka boleh dikira dengan segera dan diletakkan dalam kamus untuk carian pantas. Cincang Stribog-256 ialah gost34112012256.GOST34112012256(), yang memenuhi sepenuhnya antara muka hashlib bagi fungsi cincang.
Bagaimanakah coroutine pemula telah berubah? Segala-galanya adalah mengikut skema jabat tangan: kami menjana kuki (128-bit sudah banyak), pasangan kekunci fana 34.10, yang akan digunakan untuk fungsi perjanjian kunci VKO.
UKM ialah nombor 64-bit (urandom(8)), yang juga memerlukan penyahserikatan daripada perwakilan baitnya menggunakan gost3410_vko.ukm_unmarshal(). Fungsi VKO untuk 34.10/2012/256 3410-bit ialah gost34102012256_vko.kek_XNUMX() (KEK - kunci penyulitan).
Kunci sesi yang dijana sudah pun menjadi jujukan bait pseudo-rawak 256-bit. Oleh itu, ia boleh digunakan dengan segera dalam fungsi HKDF. Memandangkan GOST34112012256 memenuhi antara muka hashlib, ia boleh digunakan serta-merta dalam kelas Hkdf. Kami tidak menentukan garam (hujah pertama Hkdf), kerana kunci yang dijana, disebabkan oleh kefanaan pasangan kunci yang mengambil bahagian, akan berbeza untuk setiap sesi dan sudah mengandungi entropi yang mencukupi. kdf.expand() secara lalai sudah menghasilkan kunci 256-bit yang diperlukan untuk Grasshopper nanti.
Seterusnya, bahagian TBE dan TBS bagi mesej masuk disemak:
MAC ke atas teks sifir masuk dikira dan disemak;
teks sifir dinyahsulit;
Struktur TBE dinyahkod;
pengecam lawan bicara diambil daripadanya dan ia diperiksa sama ada dia diketahui oleh kami sama sekali;
MAC atas pengecam ini dikira dan disemak;
tandatangan di atas struktur TBS disahkan, yang termasuk kuki kedua-dua pihak dan kunci sementara awam pihak bertentangan. Tandatangan itu disahkan oleh kunci tandatangan tahan lama lawan bicara.
Seperti yang saya tulis di atas, 34.13/2015/XNUMX menerangkan pelbagai mod operasi sifir blok mulai 34.12/2015/3413. Antaranya terdapat mod untuk menjana sisipan tiruan dan pengiraan MAC. Dalam PyGOST ini ialah gost34.12.mac(). Mod ini memerlukan lulus fungsi penyulitan (menerima dan memulangkan satu blok data), saiz blok penyulitan dan, sebenarnya, data itu sendiri. Mengapa anda tidak boleh mengekod keras saiz blok penyulitan? 2015/128/64 menerangkan bukan sahaja sifir Grasshopper XNUMX-bit, tetapi juga XNUMX-bit Magma - GOST 28147-89 yang diubah suai sedikit, dicipta semula dalam KGB dan masih mempunyai salah satu ambang keselamatan tertinggi.
Kuznechik dimulakan dengan memanggil gost.3412.GOST3412Kuznechik(kunci) dan mengembalikan objek dengan kaedah .encrypt()/.decrypt() yang sesuai untuk menghantar kepada 34.13 fungsi. MAC dikira seperti berikut: gost3413.mac(GOST3412Kuznechik(kunci).encrypt, KUZNECHIK_BLOCKSIZE, ciphertext). Untuk membandingkan MAC yang dikira dan diterima, anda tidak boleh menggunakan perbandingan biasa (==) rentetan bait, kerana operasi ini membocorkan masa perbandingan, yang, dalam kes umum, boleh membawa kepada kelemahan yang membawa maut seperti BEAST serangan terhadap TLS. Python mempunyai fungsi khas, hmac.compare_digest, untuk ini.
Fungsi sifir blok hanya boleh menyulitkan satu blok data. Untuk nombor yang lebih besar, malah bukan gandaan panjang, adalah perlu untuk menggunakan mod penyulitan. 34.13-2015 menerangkan perkara berikut: ECB, CTR, OFB, CBC, CFB. Masing-masing mempunyai bidang aplikasi dan ciri yang boleh diterima sendiri. Malangnya, kami masih tidak mempunyai piawaian mod penyulitan yang disahkan (seperti CCM, OCB, GCM dan seumpamanya) - kami terpaksa sekurang-kurangnya menambah MAC sendiri. saya pilih mod kaunter (CTR): ia tidak memerlukan padding kepada saiz blok, boleh diselarikan, hanya menggunakan fungsi penyulitan, boleh digunakan dengan selamat untuk menyulitkan sejumlah besar mesej (tidak seperti CBC, yang mempunyai perlanggaran dengan agak cepat).
Seperti .mac(), .ctr() mengambil input yang serupa: ciphertext = gost3413.ctr(GOST3412Kuznechik(key).encrypt, KUZNECHIK_BLOCKSIZE, plaintext, iv). Ia diperlukan untuk menentukan vektor permulaan yang betul-betul separuh panjang blok penyulitan. Jika kunci penyulitan kami hanya digunakan untuk menyulitkan satu mesej (walaupun dari beberapa blok), maka adalah selamat untuk menetapkan vektor permulaan sifar. Untuk menyulitkan mesej jabat tangan, kami menggunakan kunci berasingan setiap kali.
Mengesahkan tandatangan gost3410.verify() adalah remeh: kami melepasi lengkung eliptik di mana kami sedang bekerja (kami hanya merekodkannya dalam protokol GOSTIM kami), kunci awam penandatangan (jangan lupa bahawa ini harus menjadi dua tuple nombor besar, dan bukan rentetan bait), cincangan 34.11/2012/XNUMX dan tandatangan itu sendiri.
Seterusnya, dalam pemula kami menyediakan dan menghantar mesej jabat tangan ke jabat tangan2, melakukan tindakan yang sama seperti yang kami lakukan semasa pengesahan, hanya secara simetri: menandatangani kunci kami dan bukannya menyemak, dsb...
Apabila sesi diwujudkan, kunci pengangkutan dijana (kunci berasingan untuk penyulitan, untuk pengesahan, untuk setiap pihak), dan Grasshopper dimulakan untuk menyahsulit dan menyemak MAC:
Coroutine msg_sender kini menyulitkan mesej sebelum menghantarnya pada sambungan TCP. Setiap mesej mempunyai nonce yang meningkat secara monoton, yang juga merupakan vektor permulaan apabila disulitkan dalam mod kaunter. Setiap mesej dan blok mesej dijamin mempunyai nilai kaunter yang berbeza.
GOSTIM bertujuan untuk digunakan secara eksklusif untuk tujuan pendidikan (kerana ia tidak dilindungi oleh ujian, sekurang-kurangnya)! Kod sumber program boleh dimuat turun di sini (Π‘ΡΡΠΈΠ±ΠΎΠ³-256 Ρ ΡΡ: 995bbd368c04e50a481d138c5fa2e43ec7c89bc77743ba8dbabee1fde45de120). ΠΠ°ΠΊ ΠΈ Π²ΡΠ΅ ΠΌΠΎΠΈ ΠΏΡΠΎΠ΅ΠΊΡΡ, ΡΠΈΠΏΠ° GoGOST, PyDERASN, NCCP, GoVPN, GOSTIM sepenuhnya perisian percuma, diedarkan di bawah terma GPLv3 +.