Si zhvillues bibliotekë (atëherë kriptografike GOST në Python të pastër), shpesh marr pyetje se si të realizoj një exchanged të sigurt mesazhesh në mënyrë të thjeshtë. Shumë mendojnë se kriptografia aplikative është një gjë e lehtë, dhe thjesht thirrja e .encrypt() do të jetë e mjaftueshme për dërgimin e sigurt nëpër një kanal komunikimi. Të tjerë mendojnë se kriptografia aplikative është një fushë e disa të paktëve, dhe është në rregull që kompanitë e pasura si Telegram të mos kenë matematicienë të olimpiadave, një protokoll të sigurt.
E gjithë kjo më shtyu të shkruaj këtë artikull, për të treguar se realizimi i protokolleve kriptografike dhe mesazheve të sigurta IM nuk është një detyrë kaq e vështirë. Megjithatë, nuk ia vlen të shpikni protokolle të vetat të autentifikimit dhe shkëmbimit të çelësave.

Në artikull do të përshkruhet , , instant messenger me IPsec IKE ), duke përdorimi ekskluzivisht algoritme kriptografike GOST nga biblioteka PyGOST dhe kodimin ASN.1 të mesazheve nga biblioteka (për të cilën tashmë ). Kushti i nevojshëm: ai duhet të jetë aq i thjeshtë, saqë të mund të shkruhet nga fillimi për një mbrëmje (ose një ditë pune), përndryshe nuk është një program i thjeshtë. Patjetër që ka gabime, komplikanime të tepruara, dhe mangësi, përveç se kjo është programi im i parë që përdor bibliotekën asyncio.
Dizajni i IM
Për të filluar, duhet të kuptojmë se si do të duket IM ynë. Për thjeshtësi, le të jetë një rrjet peer-to-peer, pa asnjë zbulim të pjesëmarrësve. Do të tregojmë vetë se në cilin adresë: port do të lidhemi për të komunikuar me biseduesin.
E kuptoj se, për momentin, supozimi i disponueshmërisë së lidhjes direkte midis dy kompjuterëve të rastësishëm është një kufizim i rëndësishëm i aplikueshmërisë së IM në praktikë. Por sa më shumë zhvillues të implementojnë ndihmesa për kalimin përmes NAT, aq më gjatë do të mbetemi në Internetin IPv4, me një probabilitet të mërzitshëm për lidhjen midis kompjuterëve të rastësishëm. Sa kohë mund ta durojmë mungesën e IPv6 në shtëpi dhe në punë?
Ne do të kemi një rrjet miqësor: të gjithë partnerët e mundshëm të bisedës duhet të jenë të njohur paraprakisht. Së pari, kjo e thjeshton shumë gjithçka: paraqituni, gjeni ose mos e gjeni emrin/çelësin, shk disconnect ose vazhdoni punën, duke e ditur partnerin. Së dyti, në përgjithësi, kjo është e sigurt dhe përjashton shumë sulme.
Interfata e IM do të jetë afër zgjidhjeve klasike , të cilat më pëlqejnë shumë për minimalizmin e tyre dhe filozofinë Unix-way. Programi IM krijon një direktori për çdo partner bisedë me tre soket të domenit Unix:
- in â nĂ« tĂ« shkruhen mesazhet e dĂ«rguara pĂ«r partnerin;
- out â nga tĂ« cilat lexohen mesazhet e pranuara nga partneri;
- status â duke lehtem, ne kuptojmĂ« nĂ«se bashkĂ«biseduesi Ă«shtĂ« aktualisht i lidhur, adresa/porta e lidhjes.
Për më tepër, krijohet një soket conn, duke regjistruar në të hostin e portës, ne iniciatojmë lidhjen me bashkëbiseduesin e largët.
|-- alice
| |-- në
| |-- jashtë
| `-- status
|-- bob
| |-- në
| |-- jashtë
| `-- status
`- conn
Ky qasje lejon krijimin e zbatimeve të pavarura të transportit IM dhe ndërfaqes së përdoruesit, sepse në shije dhe ngjyrë nuk ka shok, nuk i pëlqen të gjithëve. Duke përdorur dhe/ose , mund të merrni një ndërfaqe me shumë dritare me ndriçim sintaksor. Dhe me ndihmën e mund të merrni një varg të përputhshëm me GNU Readline për të futur mesazhe.
NĂ« tĂ« vĂ«rtetĂ«, projektet suckless pĂ«rdorin skedarĂ« FIFO. Personalish nuk arrita tĂ« kuptoj se si tĂ« punoj me skedarĂ«t nĂ« asyncio nĂ« mĂ«nyrĂ« tĂ« konkurueshme pa njĂ« mbĂ«shtetje manuale prej thjeshtuarish (pĂ«r kĂ«to gjĂ«ra kam pĂ«rdorur prej kohĂ«sh gjuhĂ«n ). Prandaj vendosa tĂ« kaloj me soketĂ«t Unix domain. FatkeqĂ«sisht, kjo heq mundĂ«sinĂ« pĂ«r tĂ« bĂ«rĂ« echo 2001:470:dead::babe 6666 > conn. E zgjodha kĂ«tĂ« problem, duke pĂ«rdorur : echo 2001:470:dead::babe 6666 | socat â UNIX-CONNECT:conn, socat READLINE UNIX-CONNECT:alice/nĂ«.
Protocoli fillestar i pasigurt
Transporti përdor TCP: ai garanton dorëzimin dhe rendin e tij. UDP nuk garanton asnjërin prej tyre (çka do të ishte e dobishme kur aplikohet kriptografia), dhe mbështetje. në Python nuk ka mbështetje nga get.
Fatkeqësisht, në TCP nuk ekziston nocioni i mesazhit, vetëm ai i rrjedhës së bajtëve. Prandaj, është e nevojshme të imagjinojmë një format për mesazhet, në mënyrë që ato të mund të ndahen në këtë rrjedhë. Mund të biem dakord të përdorim simbolin e transferimit të rreshtit. Në fillim do të mjaftojë, megjithatë, kur të fillojmë të kodojmë mesazhet tona, ky simbol mund të shfaqet kudo në tekstin e koduar. Në rrjetet, prandaj, janë popullorë protokollet që dërgojnë më parë gjatë mesazhit në bajtë. Për shembull, në Python ka xdrlib që lejon të punojë me një format të tillë. .
Ne do tĂ« punojmĂ« siç duhet dhe nĂ« mĂ«nyrĂ« efikase me leximin TCP â do ta thjeshtĂ«sojmĂ« kodin. LexojmĂ« tĂ« dhĂ«nat nga socketi nĂ« njĂ« cikĂ«l tĂ« pafund derisa tĂ« dekodojmĂ« mesazhin e plotĂ«. Si format pĂ«r kĂ«tĂ« qasje mund tĂ« pĂ«rdorim si JSON ashtu edhe XML. Por, kur tĂ« shtohet kriptografia, tĂ« dhĂ«nat do tĂ« duhet tĂ« nĂ«nshkruhen dhe tĂ« autentifikohen â dhe kjo do tĂ« kĂ«rkojĂ« njĂ« paraqitje identike byte-for-byte tĂ« objekteve, gjĂ« qĂ« JSON/XML nuk e sigurojnĂ« (rezultati i dumps mund tĂ« ndryshojĂ«).
XDR është i përshtatshëm për këtë detyrë, megjithatë unë zgjedh ASN.1 me kodimin DER dhe bibliotekën, pasi do të kemi objekte me nivele të larta me të cilat shpesh është më këndshëm dhe praktik të punojmë. Ndryshe nga schemaless , ose , ASN.1 automatikisht do të verifikojë të dhënat përballë një skeme të ngurtë të caktuar.
# 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))),
))
Mesazhi i pranuar do të jetë Msg: ose MsgText tekstual (ndërsa për tani me një fushë tekstuale), ose mesazhi i dorëzimit MsgHandshake (në të cilin dërgohet emri i bashkëbiseduesit). Tani duket e komplikuar, por kjo është një ndihmesë për të ardhmen.
âââââââ âââââââ
âPeerAâ âPeerBâ
ââââŹâââ ââââŹâââ
âMsgHandshake(IdA) â
ââââââââââââââââââ>â
â â
âMsgHandshake(IdB) â
ââ
â â
â MsgText() â
â<ââââââââââââââââââ
â â
Pa pa kriptografisë
Siç e thashë, për të gjitha operacionet me socket-et do të përdoret biblioteka asyncio. Le të shpallim atë që presim në momentin e startit:
parser = argparse.ArgumentParser(description="GOSTIM")
parser.add_argument(
"--our-name",
required=True,
help="Emri ynë i kolegut",
)
parser.add_argument(
"--their-names",
required=True,
help="Emrat e tyre të kolegëve, të ndarë me presje",
)
parser.add_argument(
"--bind",
default="::1",
help="Adresa për të dëgjuar",
)
parser.add_argument(
"--port",
type=int,
default=6666,
help="Porti për të dëgjuar",
)
args = parser.parse_args()
OUR_NAME = UTF8String(args.our_name)
THEIR_NAMES = set(args.their_names.split(","))
Vendoset emri ynĂ« (âour-name alice). TĂ« gjithĂ« biseduesit e pritur njĂ«ri pas tjetrit me presje (âtheir-names bob,eve). PĂ«r secilin nga biseduesit, krijohet njĂ« dosje me socket-e Unix, si dhe njĂ« korutinĂ« pĂ«r çdo in, out, state:
për emri i peers në THEIR_NAMES:
bëni mappat(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"))
Mesazhet që vijnë nga përdoruesi nga socketi in dërgohen në radhët IN_QUEUES:
async def unixsock_in_processor(reader, writer, in_queue: asyncio.Queue) -> None:
në mënyrë që të përherë:
tekst = await reader.read(MaxTextLen)
nëse tekst == b"":
break
await in_queue.put(tekst.decode("utf-8"))
Mesazhet që vijnë nga bashkëbiseduesit dërgohen në radhët OUT_QUEUES, nga të cilat të dhënat shkruhen në socketin out:
async def unixsock_out_processor(reader, writer, out_queue: asyncio.Queue) -> None:
në mënyrë që të përherë:
tekst = await out_queue.get()
writer.write(("[%s] %s" % (datetime.now(), tekst)).encode("utf-8"))
await writer.drain()
Kur lexon nga socketi state, programi kërkon në fjalorin PEER_ALIVE adresën e bashkëbiseduesit. Nëse lidhja me bashkëbiseduesin nuk ekziston, atëherë shkruhet një varg i zbrazët.
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()
Kur kursej adresa në soket, aktivizohet funksioni "nismëtar" i lidhjes:
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))
Le të shqyrtojmë nismëtarin. Fillimisht, ai natyrisht hap një lidhje me host-in/port-in e caktuar dhe dërgon një mesazh handshake me emrin e tij:
130 async def initiator(host, port):
131 _id = repr((host, port))
132 logging.info("%s: po dial", _id)
133 reader, writer = await asyncio.open_connection(host, port)
134 # Mesazhi i handshake-it {{{
135 writer.write(Msg(("handshake", MsgHandshake((
136 ("emri i palës", OUR_NAME),
137 )))).encode())
138 # }}}
139 await writer.drain()
Dhe më pas, pret një përgjigje nga ana e largët. Mundohet të dekodojë përgjigjen e ardhur sipas skemës Msg ASN.1. Supozomë se e gjithë mesazhi do të dërgohet me një TCP-segment dhe ne do ta marrim atë në mënyrë atomike gjatë thirrjes .read(). Kontrollojmë që kemi marrë pikërisht mesazhin e handshake-it.
Prisni mesazhin Handshake {{{
142 data = await reader.read(256)
143 nëse data == b"":
144 logging.warning("%s: nuk ka përgjigje, po shkëputet", _id)
145 writer.close()
146 kthehu
147 provo:
148 msg, _ = Msg().decode(data)
149 përjashto ASN1Error:
150 logging.warning("%s: përgjigje e paqartë, po shkëputet", _id)
151 writer.close()
152 kthehu
153 logging.info("%s: mori mesazhin %s", _id, msg.choice)
154 nëse msg.choice != "handshake":
155 logging.warning("%s: mesazh i papritur, po shkëputet", _id)
156 writer.close()
157 kthehu
158 # }}}
Po kontrollojmë nëse emri i arsimtarit është i njohur për ne. Nëse jo, atëherë po shkëputemi. Po kontrollojmë nëse kemi pasur tashmë një lidhje me të (arsimtari sërish dha komandën për t'u lidhur me ne) dhe e mbyllim atë. Në IN_QUEUES vendosen vargje Python me tekstin e mesazhit, por ka një vlerë të veçantë None, që sinjalizon korutinën msg_sender të ndalojë punën, në mënyrë që ajo të harrojë për writer-in e saj, i lidhur me lidhjen TCP të vjetër.
159 msg_handshake = msg.value
160 peer_name = str(msg_handshake["peerName"])
161 nëse emri i palës nuk është në EMRAT E TYRE:
162 logging.warning("emër palë i panjohur: %s", peer_name)
163 writer.close()
164 return
165 logging.info("%s: sesioni është krijuar: %s", _id, peer_name)
166 # Ekzekuto dërguesin e mesazheve me tekst, inicializo dekoduesin e transportit {{{
167 peer_alive = PEER_ALIVES.pop(peer_name, None)
168 nëse peer_alive është jo 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 pranon mesazhet nĂ« dalje (tĂ« vendosura nĂ« radhĂ« nga in soketi), i serializon ato nĂ« njĂ« mesazh MsgText dhe i dĂ«rgon pĂ«rmes lidhjes TCP. Ajo mund tĂ« ndĂ«rpritet nĂ« çdo moment â kĂ«tĂ« ne e kapim qartazi.
async def msg_sender(peer_name: str, writer) -> None:
in_queue = IN_QUEUES[peer_name]
while True:
text = await in_queue.get()
nëse text është 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: dërguar %d karaktere mesazh", peer_name, len(text))
Në fund, inicuesi hyn në një cikël të pafund leximi të mesazheve nga socketi. Kontrollon nëse janë mesazhe tekstuale dhe i vendos ato në radhën OUT_QUEUES, nga e cila do të dërgohen në socketin out të biseduesit përkatës. Pse nuk mund të bëjmë thjesht .read() dhe të dekodojmë mesazhin? Sepse nuk përjashtohet mundësia që disa mesazhe nga përdoruesi të jenë agreguar në tamponin e sistemit operativ dhe të dërgohen në një TCP-segment. Mund të dekodojmë të parin, ndërsa në tampon mund të mbetet një pjesë nga e dyta. Në çdo situatë anormale, ne mbyllim TCP-në lidhje dhe ndalojmë korutinën msg_sender (me dërgimin e None në radhën OUT_QUEUES).
174 buf = b""
175 # Pritini mesazhet e testit {{{
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: madhësia maksimale e tamponit është tejkaluar", _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: mesazh i papritur %s", _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: po shkëputet: %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: pranuar mesazhi me %d karaktere", peer_name, len(text))
69 await OUT_QUEUES[peer_name].put(text)
Le ta kthejmë te kodi kryesor. Pas krijimit të të gjitha korutave në momentin e nisjes së programit, ne nisim serverin TCP. Për çdo lidhje të vendosur, ai krijon një korutë përgjigjëse (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("Dëgjojmë në: %s", server.sockets[0].getsockname())
loop.run_forever()
responder është i ngjashëm me initiator dhe kryen të njëjtat veprime, por cikli i pafund i leximit të mesazheve fillon menjëherë, për thjeshtësi. Tani protokolli i dorëzimit dërgon një mesazh nga secila anë, por më vonë do të dërgojë dy nga iniciatori i lidhjes, pas të cilave mund të dërgohen menjëherë mesazhet tekstual.
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 # Lexoni derisa të marrim mesazhin Msg {{{
80 data = await reader.read(MaxMsgLen)
81 if data == b"":
82 logging.info("%s: e mbyllur lidhja", _id)
83 break
84 buf += data
85 if len(buf) > MaxMsgLen:
86 logging.warning("%s: është tejkaluar madhësia maksimale e buffers", _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: mesazh i papritur %s", _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 # Procesoni mesazhin Handshake {{{
104 elif msg_expected == "handshake":
105 logging.info("%s: mori mesazhin %s", _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("emri i panjohur i partnerit: %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: seanca e krijuar: %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: po shkëputemi", _id)
125 if msg_expected == "text":
126 IN_QUEUES[peer_name].put(None)
127 writer.close()
Protokolli i sigurtë
Ka ardhur koha pĂ«r tĂ« siguruar komunikimin tonĂ«. ĂfarĂ« do tĂ« thotĂ« siguria pĂ«r ne dhe çfarĂ« dĂ«shirojmĂ«:
- privatësia e mesazheve të dërguara;
- autenticiteti dhe integriteti i mesazheve të dërguara - ndryshimi i tyre duhet të zbulohet;
- mbrojtja nga sulmet e riprodhimit (replay attack) - fakti i humbjes apo i përsëritjes së mesazheve duhet të zbulohet (dhe ne vendosim të ndërpresim lidhjen);
- identifikimi dhe autentifikimi i biseduesve nëpërmjet çelësave publikë të futur më parë - ne tashmë kemi vendosur se krijojmë një rrjet miqësor. Vetëm pas autentifikimit do të kuptojmë me kë po komunikojmë;
- praninë karakteristikat e përmirësuara (PFS) - komprometimi i çelësit tonë të gjatë të nënshkrimit nuk duhet të çojë në mundësinë e leximin të gjithë bisedave të mëparshme. Regjistrimi i trafikut të kapur bëhet i padobishëm;
- validiteti i mesazheve (transportit dhe dorëheqjes) vetëm brenda një seance TCP. Shtimi i mesazheve të nënshkruara/autentifikuara si duhet nga një seancë tjetër (edhe me të njëjtin bisedues) nuk duhet të jetë e mundur;
- një vëzhgues pasiv nuk duhet të shohë as identifikuesit e përdoruesve, as çelësat publikë të gjatë që dërgohen, as hash-at e tyre. Njëfarë anonimiteti nga vëzhguesi pasiv.
ĂshtĂ« e habitshme, por ky minimum dĂ«shirohet nga tĂ« gjithĂ« nĂ« çdo protokoll dorĂ«heqjeje, dhe shumĂ« pak nga ato qĂ« janĂ« pĂ«rmendur pĂ«rfundimisht zbatohen pĂ«r protokollet "tĂ« shtĂ«pisĂ«". Tani, le tĂ« mos shpikim diçka tĂ« re. UnĂ« do tĂ« rekomandoja me siguri tĂ« pĂ«rdorim pĂ«r ndĂ«rtimin e protokolleve, por le tĂ« zgjidhim diçka mĂ« tĂ« thjeshtĂ«.
Dy protokollet më popullore janë:
- â njĂ« protokoll shumĂ« i komplikuar me njĂ« histori tĂ« gjatĂ« gabimesh, problemeve, dobĂ«sive, planifikimit tĂ« keq, kompleksitetit dhe mungesave (nĂ« tĂ« vĂ«rtetĂ«, kjo Ă«shtĂ« pak e lidhur me TLS 1.3). Por nuk e shqyrtojmĂ« atĂ« pĂ«r shkak tĂ« shumĂ« komplikuese.
- me â nuk kanĂ« probleme serioze kriptografike, megjithatĂ« gjithashtu nuk janĂ« tĂ« thjeshta. NĂ«se lexoni mbi IKEv1 dhe IKEv2, origjina e tyre Ă«shtĂ« , ISO/IEC IS 9798-3 dhe protokollet SIGMA (SIGn-and-MAc) â mjaft tĂ« thjeshta pĂ«r tu realizuar brenda njĂ« nate.
ĂfarĂ« e bĂ«n SIGMA, si nyja e fundit e zhvillimit tĂ« protokolleve STS/ISO, tĂ« shkĂ«lqyer? Ai plotĂ«son tĂ« gjitha kĂ«rkesat tona (pĂ«rfshirĂ« "fshehjen" e identifikuesve tĂ« biseduesve), dhe nuk ka probleme tĂ« njohura kriptografike. Ai Ă«shtĂ« minimalist â heqja e ndonjĂ« elementi nga mesazhi i protokollit do ta bĂ«jĂ« atĂ« tĂ« pasigurt.
Le të kalojmë nga protokolli më i thjeshtë i shkruar nga vetë ne deri te SIGMA. Operacioni më themelor që na intereson është : një funksion, në daljen e të cilit të dy pjesëmarrësit do të marrin njësoj vlerën, e cila mund të përdoret si një çelës simetrik. Pa hyrë në detaje: secila palë gjeneron një çift çelësh efemer (i cili përdoret vetëm brenda një seance), shkëmben çelësat publikë, dhe thërrasin funksionin e konsensusit, në të cilin kalojnë çelësin e tyre privat dhe çelësin publik të biseduesit.
âââââââ âââââââ
âPeerAâ âPeerBâ
ââââŹâââ ââââŹâââ
â IdA, PubA â ââââââââââââââââââââââ
ââââââââââââââââ>â âPrvA, PubA = DHgen()â
â â ââââââââââââââââââââââ
â IdB, PubB â ââââââââââââââââââââââ
â<ââââââââââââââââ âPrvB, PubB = DHgen()â
â â ââââââââââââââââââââââ
âââââ âââââââââ§âââââââââââââ
â âKey = DH(PrvA, PubB)â
<ââââ âââââââââ€âââââââââââââ
â â
â â
Ădo kush mund tĂ« ndĂ«rhyjĂ« dhe tĂ« zĂ«vendĂ«sojĂ« çelĂ«sat publikĂ« me tĂ« tijat â ky protokoll nuk ka autentikim tĂ« bashkĂ«biseduesve. Le tĂ« shtojmĂ« njĂ« nĂ«nshkrim me çelĂ«sa tĂ« gjatĂ«.
âââââââ âââââââ
âPeerAâ âPeerBâ
ââââŹâââ ââââŹâââ
âIdA, PubA, sign(SignPrvA, (PubA)) â âââââââââââââââââââââââââââââ
âââââââââââââââââââââââââââââââââââ¶â âSignPrvA, SignPubA = load()â
â â âPrvA, PubA = DHgen() â
â â âââââââââââââââââââââââââââââ
âIdB, PubB, sign(SignPrvB, (PubB)) â âââââââââââââââââââââââââââââ
ââââââââââââââââââââââââââââââââââââ âSignPrvB, SignPubB = load()â
â â âPrvB, PubB = DHgen() â
â â âââââââââââââââââââââââââââââ
âââââ âââââââââââââââââââââââ â
â âverify(SignPubB, ...)â â
âââââ âKey = DH(PrvA, PubB) â â
â âââââââââââââââââââââââ â
â â
Kyç kyç nuk do të jetë e përshtatshme, sepse nuk është e lidhur me një sesion specifik. Mesazhe të tilla "janë të përshtatshme" edhe për sesionet me pjesëmarrës të tjerë. Duhet të nënshkruhet i gjithë konteksti. Kjo gjithashtu detyron që të shtohet një mesazh tjetër nga A.
Për më tepër, është thelbësore të shtoni nënshkrimin dhe identifikuesin tuaj, sepse në të kundërt, ne mund të zëvendësojmë IdXXX dhe të nënshkruajmë mesazhin me çelësin e dikujt tjetër të njohur. Për të parandaluar , është e nevojshme që elementët nënshkrues të ndodhen në vende të caktuara në përputhje me kuptimin e tyre: nëse A nënshkruan (PubA, PubB), atëherë B duhet të nënshkruajë (PubB, PubA). Kjo tregon gjithashtu rëndësinë e zgjedhjes së strukturës dhe formatit të të dhënave të serializuara. Për shembull, grupet në kodimin ASN.1 DER janë të renditura: SET OF(PubA, PubB) do të jetë identike me SET OF(PubB, PubA).
âââââââ âââââââ âPeerAâ âPeerBâ ââââŹâââ ââââŹâââ â IdA, PubA â âââââââââââââââââââââââââââââ âââââââââââââââââââââââââââââââââââââââââââââ>â âSignPrvA, SignPubA = load()â â â âPrvA, PubA = DHgen() â â â âââââââââââââââââââââââââââââ âIdB, PubB, sign(SignPrvB, (IdB, PubA, PubB)) â âââââââââââââââââââââââââââââ ââ âverify(SignPubB, ...)â â â âKey = DH(PrvA, PubB) â â â âââââââââââââââââââââââ â â
MegjithatĂ«, ne ende nuk e kemi "provuara" qĂ« kemi prodhuar njĂ« çelĂ«s tĂ« pĂ«rbashkĂ«t tĂ« njĂ«jtĂ« pĂ«r kĂ«tĂ« seancĂ«. NĂ« parim, mund tĂ« bĂ«jmĂ« pa kĂ«tĂ« hap â mesazhi i parĂ« tĂ« transportit do tĂ« jetĂ« i pavlefshĂ«m, por ne duam qĂ« kur dorĂ«zimi tĂ« pĂ«rfundojĂ«, tĂ« jemi tĂ« sigurt se gjithçka Ă«shtĂ« vĂ«rtet e konfirmuar. NĂ« kĂ«tĂ« moment, kemi nĂ« duar ISO/IEC IS 9798-3 protokollin.
Ne mund të nënshkruajmë edhe çelësin e prodhuar. Kjo është e rrezikshme, pasi nuk përjashtohet që në algoritmin e përdorur të nënshkrimit mund të ketë rrjedhje (madje edhe bitet për nënshkrim, por prapë rrjedhje). Mund të nënshkruajmë hash-in e çelësit të prodhuar, por rrjedhja e madhe edhe e hash-it të çelësit të prodhuar mund të ketë vlerë gjatë sulmeve brute-force ndaj funksionit të prodhimit. SIGMA përdor funksionin MAC, i cili autentifikon identifikuesin e dërguesit.
âââââââ âââââââ âPeerAâ âPeerBâ ââââŹâââ ââââŹâââ â IdA, PubA â âââââââââââââââââââââââââââââ ââââââââââââââââââââââââââââââââââââââââââââââââââ>â âSignPrvA, SignPubA = load()â â â âPrvA, PubA = DHgen() â â â âââââââââââââââââââââââââââââ âIdB, PubB, sign(SignPrvB, (PubA, PubB)), MAC(IdB) â âââââââââââââââââââââââââââââ ââ âverify(Key, IdB) â â â âverify(SignPubB, ...)â â â âââââââââââââââââââââââ â â
Si pĂ«r optimizim, disa mund tĂ« dĂ«shirojnĂ« tĂ« ri-pĂ«rdorin çelĂ«sat e tyre efemerĂ« (çfarĂ«, sigurisht, Ă«shtĂ« e dĂ«mshme pĂ«r PFS). PĂ«r shembull, ne gjeneruam njĂ« çift çelĂ«sash, pĂ«rpiqem tĂ« lidhemi, por TCP nuk ishte i disponueshĂ«m ose u ndĂ«rpre diku nĂ« mes tĂ« protokollit. ĂshtĂ« njĂ« humbje e madhe tĂ« shpenzohet entropi dhe burime procesori pĂ«r njĂ« çift tĂ« ri. Prandaj, do tĂ« prezantojmĂ« njĂ« cookie â njĂ« vlerĂ« pseudonatyrale, e cila do tĂ« mbrojĂ« nga sulmet e mundshme tĂ« ri-pĂ«rdorimit nga rastĂ«sisht nĂ« ri-pĂ«rdorimin e çelĂ«save publikĂ« efemerĂ«. PĂ«r shkak tĂ« lidhjes mes cookie dhe çelĂ«sit publik efemer, çelĂ«si publik i palĂ«s tjetĂ«r mund tĂ« hiqet nga nĂ«nshkrimi si i panevojshĂ«m.
âââââââ âââââââ âPeerAâ âPeerBâ ââââŹâââ ââââŹâââ â IdA, PubA, CookieA â âââââââââââââââââââââââââââââ âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ>â âSignPrvA, SignPubA = load()â â â âPrvA, PubA = DHgen() â â â âââââââââââââââââââââââââââââ âIdB, PubB, CookieB, sign(SignPrvB, (CookieA, CookieB, PubB)), MAC(IdB) â âââââââââââââââââââââââââââââ ââ âverify(Key, IdB) â â â âverify(SignPubB, ...)â â â âââââââââââââââââââââââ â â
Së fundi, ne duam të sigurojmë privatësinë e identifikuesve tanë nga vëzhguesit pasivë. Për këtë, SIGMA propozon që fillimisht të shkëmbejë çelësa efemerë, të krijojë një çelës të përbashkët, mbi të cilin të kodohen mesazhet autentikues dhe identifikues. SIGMA përshkruan dy mundësi:
- SIGMA-I â mbron iniciatorin nga sulmet aktive, dhe pĂ«rgjigjĂ«sin nga ato pasive: iniciatori autentikon pĂ«rgjigjĂ«sin dhe, nĂ«se diçka nuk pĂ«rputhet, ai nuk e zbuluar identifikimin e tij. NdĂ«rsa pĂ«rgjigjĂ«si jep identifikimin e tij nĂ«se fillohet njĂ« protokoll aktiv. NjĂ« vĂ«zhgues pasiv nuk merr asnjĂ« informacion;
SIGMA-R â mbron pĂ«rgjigjĂ«sin nga sulmet aktive, dhe iniciatorin nga ato pasive. TĂ« gjitha kĂ«to pĂ«rkundrazi, por nĂ« kĂ«tĂ« protokoll dĂ«rgohen katĂ«r mesazhe dorĂ«zimi.Zgjedhim SIGMA-I si mĂ« tĂ« ngjashĂ«m me atĂ« qĂ« presim nga gjĂ«rat tradicionale klient-server: klienti e njeh vetĂ«m serverin e autentikuar, ndĂ«rsa serveri i di tĂ« gjitha. Gjithashtu, Ă«shtĂ« mĂ« i thjeshtĂ« pĂ«r t'u implementuar pĂ«r shkak tĂ« numrit mĂ« tĂ« vogĂ«l tĂ« mesazheve tĂ« dorĂ«zimit. E gjithĂ« ajo qĂ« ne e shtojmĂ« nĂ« protokoll Ă«shtĂ« enkriptimi i njĂ« pjese tĂ« mesazhit dhe transferimi i identifikuesit A nĂ« pjesĂ«n e enkriptuar tĂ« mesazhit tĂ« fundit:
âââââââ âââââââ âPeerAâ âPeerBâ ââââŹâââ ââââŹâââ â PubA, CookieA â âââââââââââââââââââââââââââââ âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ âSignPrvA, SignPubA = 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, ...)â â â âââââââââââââââââââââââ â â
- Për nënshkrim përdoret GOST R algoritmi me çelësa 256-bit.
- Për gjenerimin e çelësit të përbashkët përdoret 34.10-2012 VKO.
- Si MAC përdoret CMAC. Teknikisht, kjo është një aplikim i veçantë i funksionit të bllokimit të enkriptimit, i përshkruar në GOST R 34.13-2015. Si funksion enkriptimi për këtë mod është (34.12-2015).
- Si identifikues i biseduesit përdoret hash-i nga çelësi i tij publik. Si hash përdoret (34.11-2012 256 bit).
Pas dorĂ«zimit, do tĂ« kemi tĂ« rĂ«nĂ« dakord mbi njĂ« çelĂ«s tĂ« pĂ«rbashkĂ«t. Ky mund tĂ« pĂ«rdoret pĂ«r enkriptimin e autentikuar tĂ« mesazheve transportuese. Kjo pjesĂ« Ă«shtĂ« shumĂ« e thjeshtĂ« dhe Ă«shtĂ« e vĂ«shtirĂ« tĂ« gabosh: inkrementojmĂ« numĂ«ruesin e mesazheve, enkriptojmĂ« mesazhin, autentikojmĂ« (MAC) numĂ«ruesin dhe tekstin e enkriptuar, e dĂ«rgojmĂ«. Kur pranojmĂ« mesazhin kontrollojmĂ« qĂ« numĂ«ruesi tĂ« ketĂ« vlerĂ«n e pritur, autentikojmĂ« tekstin e enkriptuar me numĂ«ruesin, dekriptojmĂ«. ĂfarĂ« çelĂ«si duhet pĂ«rdorur pĂ«r enkriptimin e mesazheve, pĂ«r dorĂ«zimin, si duhet autentikuar? PĂ«rdorimi i njĂ« çelĂ«si tĂ« vetĂ«m pĂ«r kĂ«to detyra Ă«shtĂ« e rrezikshme dhe e paarsyeshme. Duhet gjeneruar çelĂ«sat duke pĂ«rdorur funksione tĂ« specializuara (funksioni i derivimit tĂ« çelĂ«sit). NĂ«se do tĂ« ishim gjithmonĂ« duke ndĂ«rlikuar dhe shpikur diçka: nĂ« jetĂ« e njohur prej kohĂ«sh, e studiuar mirĂ« dhe nuk ka probleme tĂ« njohura. FatkeqĂ«sisht, nĂ« bibliotekĂ«n e natyrshme Python nuk ka kĂ«tĂ« funksion, prandaj pĂ«rdorim paketĂ«n. HKDF pĂ«rdor brenda , i cili, nga ana e tij, pĂ«rdor funksionin hash. NjĂ« shembull realizimi nĂ« Python nĂ« faqen Wikipedia merr vetĂ«m disa rreshta kodi. Ashtu siç ndodh me 34.10-2012, si funksion hash do tĂ« pĂ«rdorim Strboga-256. Dalja e funksionit tonĂ« tĂ« pajtimit tĂ« çelĂ«ve do tĂ« quhet çelĂ«si sesionar, nga i cili do tĂ« prodhohen simetrikĂ«t e munguar:
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")Strukturat/skemat
Le të shqyrtojmë se cilat struktura ASN.1 kemi marrë tani për të transferuar të gjitha këto të dhëna:
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 â ajo qĂ« do tĂ« nĂ«nshkruhet (to be signed). HandshakeTBE â ajo qĂ« do tĂ« kriptohet (to be encrypted). DĂ«shiroj tĂ« tĂ«rheq vĂ«mendjen pĂ«r fushĂ«n ukm nĂ« MsgHandshake1. 34.10 VKO, pĂ«r njĂ« mĂ« shumĂ« rastĂ«sishmĂ«ri nĂ« çelĂ«sat e prodhuar, pĂ«rfshin parametrin UKM (materiali i çelĂ«sit tĂ« pĂ«rdoruesit) â thjesht njĂ« entropi shtesĂ«.
Shtimi i kriptografisë në kod
Do të shqyrtojmë vetëm ndryshimet e bëra në kodin origjinal, pasi skeleti ka mbetur i njëjtë (në të vërtetë, fillimisht është shkruar implementimi përfundimtar dhe më pas është ndarë çdo kriptografi).
Pasi autentikimi dhe identifikimi i palëve do të bëhet përmes çelësave publikë, tani duhet t'i ruajmë diku për një kohë të gjatë. Për thjeshtësi do të përdorim një JSON të tillë:
{ "our": { "prv": "21254cf66c15e0226ef2669ceee46c87b575f37f9000272f408d0c9283355f98", "pub": "938c87da5c55b27b7f332d91b202dbef2540979d6ceaa4c35f1b5bfca6df47df0bdae0d3d82beac83cec3e353939489d9981b7eb7a3c58b71df2212d556312a1" }, "their": { "alice": "d361a59c25d2ca5a05d21f31168609deeec100570ac98f540416778c93b2c7402fd92640731a707ec67b5410a0feae5b78aeec93c4a455a17570a84f2bc21fce", "bob": "aade1207dd85ecd283272e7b69c078d5fae75b6e141f7649ad21962042d643512c28a2dbdc12c7ba40eb704af920919511180c18f4d17e07d7f5acd49787224a" } }our â çifti ynĂ« i çelĂ«save, çelĂ«si privat dhe publik nĂ« formĂ«n heksadecimale. their â emrat e palĂ«ve dhe çelĂ«sat e tyre publikĂ«. Do tĂ« ndryshojmĂ« argumentet e komandĂ«s dhe do tĂ« shtojmĂ« post-pĂ«rpunimin e tĂ« dhĂ«nave 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="Generoni JSON me çiftin tonĂ« tĂ« ri tĂ« çelĂ«sit", ) parser.add_argument( "--keys", default="keys.json", required=False, help="JSON me çelĂ«sat tanĂ« dhe tĂ« tyre", ) parser.add_argument( "--bind", default="::1", help="Adresa pĂ«r tĂ« dĂ«gjuar", ) parser.add_argument( "--port", type=int, default=6666, help="Porti pĂ«r tĂ« dĂ«gjuar", ) 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) # Analizo dhe shpĂ«rndaj çelĂ«sat tanĂ« dhe tĂ« tyre {{{ 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), } # }}}ĂelĂ«si privat i algoritmit 34.10 Ă«shtĂ« njĂ« numĂ«r i rastĂ«sishĂ«m. Ka njĂ« madhĂ«si prej 256 bit pĂ«r kurvat eliptike 256-bit. PyGOST punon jo me njĂ« grup bitĂ«sh, por me , prandaj çelĂ«si ynĂ« privat (urandom(32)) duhet tĂ« konvertohet nĂ« numĂ«r, duke pĂ«rdorur gost3410.prv_unmarshal(). ĂelĂ«si publik llogaritet nĂ« mĂ«nyrĂ« deterministe nga çelĂ«si privat, duke pĂ«rdorur gost3410.public_key(). ĂelĂ«si publik 34.10 Ă«shtĂ« dy numra tĂ« mĂ«dhenj qĂ« gjithashtu duhet tĂ« kthehen nĂ« njĂ« sekuencĂ« bytesh pĂ«r ruajtje dhe transfertĂ« mĂ« tĂ« lehtĂ«, duke pĂ«rdorur gost3410.pub_marshal().
Pasi të lexohet skedari JSON, çelësat publikë duhet të kthehen përsëri, duke përdorur gost3410.pub_unmarshal(). Duke qenë se do të marrim identifikuesit e bashkëbiseduesve si hash nga çelësi publik, mund t'i llogarisim ato paraprakisht dhe t'i vendosim në një fjalor për kërkimin e shpejtë. Hashi Stribog-256 është gost34112012256.GOST34112012256(), e cila plotëson plotësisht ndërfaqen e funksioneve hash nga hashlib.
Si ka ndryshuar korutina e iniciatorit? Të gjitha, si sipas skemës së dorëzimit: gjenerojmë cookie (128-bit mjafton), çiftin efemer të çelësave 34.10, i cili do të përdoret për funksionin VKO të pajtimit të çelësave.
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 # Generoni çelësin tonë publik ephemeral dhe cookie, dërgojmë mesazhin Handshake 0 {{{ 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()- po presim përgjigjen dhe decodojmë mesazhin Msg të arritur;
- sigurohemi që morëm handshake1;
- decodojmë çelësin publik ephemeral të palës tjetër dhe llogarisim çelësin e seancës;
- krijojmë çelësat simetrikë të nevojshëm për të përpunuar pjesën TBE të mesazhit.
423 logging.info("%s: mori %s mesazh", _id, msg.choice) 424 nĂ«se msg.choice != "handshake1": 425 logging.warning("%s: mesazh befasues, duke u shkĂ«putur", _id) 426 writer.close() 427 kthehu 428 # }}} 429 msg_handshake1 = msg.value 430 # Validoni mesazhin Handshake {{{ 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 Ă«shtĂ« njĂ« numĂ«r 64-bit (urandom(8)), i cili gjithashtu kĂ«rkon deserializim nga pĂ«rfaqĂ«simi nĂ« byte, duke pĂ«rdorur gost3410_vko.ukm_unmarshal(). Funksioni VKO pĂ«r 34.10-2012 256-bit Ă«shtĂ« gost3410_vko.kek_34102012256() (KEK â çelĂ«si i enkripcioni).
ĂelĂ«si i sesionit i prodhuar tashmĂ« Ă«shtĂ« njĂ« sekuencĂ« pseudotrashe 256-bit. Prandaj, mund tĂ« pĂ«rdoret menjĂ«herĂ« nĂ« funksionin HKDF. Duke qenĂ« se GOST34112012256 pĂ«rputhet me ndĂ«rfaqen hashlib, mund tĂ« pĂ«rdoret menjĂ«herĂ« nĂ« klasĂ«n Hkdf. Ne nuk e specifikojmĂ« kripĂ«n (argumenti i parĂ« nĂ« Hkdf) sepse çelĂ«si i prodhuar pĂ«r shkak tĂ« efemeritetit tĂ« çifteve tĂ« çelĂ«save do tĂ« jetĂ« i ndryshĂ«m pĂ«r çdo sesion dhe ai tashmĂ« ka mjaft entropi. kdf.expand() pĂ«r default jep tashmĂ« çelĂ«sa me gjatĂ«si 256-bit, tĂ« nevojshme pĂ«r Kuzuçin nĂ« vazhdim.
Më pas kontrollohen pjesët TBE dhe TBS të mesazhit të pranuar:
- llogaritet dhe kontrollohet MAC mbi tekstin e enkriptuar të pranuar;
- tekstin e enkriptuar dekriptohet;
- struktura TBE dekodifikohet;
- nga ajo merret identifikuesi i palës biseduese dhe kontrollohet a na është i njohur;
- llogaritet dhe kontrollohet MAC mbi këtë identifikues;
- kontrollohet nënshkrimi mbi strukturën TBS, e cila përfshin skedat e të dy palëve dhe çelësin publik efemer të palës së kundërt. Nënshkrimi kontrollohet me çelësin e nënshkrimit afatgjatë të palës biseduese.
441 provo: 442 emri_peer = valide_tbe( 443 mesazhi_handshake1, 444 çelĂ«si_handshake1_mac_identity, 445 çelĂ«si_handshake1_enc, 446 çelĂ«si_handshake1_mac, 447 cookie_yne, 448 cookie_tyre, 449 pub_tyre_raw, 450 ) 451 pĂ«rjashto ValueError si gabim: 452 logging.warning("%s: %s, duke u shkĂ«putur", _id, gabim) 453 shkruesi.mbyll() 454 kthe 455 # }}} 128 def valide_tbe( 129 mesazhi_handshake: Union[MsgHandshake1, MsgHandshake2], 130 çelĂ«si_mac_identity: bytes, 131 çelĂ«si_enc: bytes, 132 çelĂ«si_mac: bytes, 133 cookie_tyre: Cookie, 134 cookie_yne: Cookie, 135 pub_çelĂ«si_yne: PubKey, 136 ) -> str: 137 ciphertext = bytes(mesazhi_handshake["ciphertext"]) 138 mac_tag = mac(GOST3412Kuznechik(çelĂ«si_mac).encrypt, KUZNECHIK_BLOCKSIZE, ciphertext) 139 nĂ«se jo krahaso_digest(mac_tag, bytes(mesazhi_handshake["ciphertextMac"])): 140 ngrit_valueError("MAC i pavlefshĂ«m") 141 plaintext = ctr( 142 GOST3412Kuznechik(çelĂ«si_enc).encrypt, 143 KUZNECHIK_BLOCKSIZE, 144 ciphertext, 145 8 * b"x00", 146 ) 147 provo: 148 tbe, _ = HandshakeTBE().decode(plaintext) 149 pĂ«rjashto ASN1Error: 150 ngrit_valueError("nuk mund tĂ« dekodoj TBE") 151 çelĂ«si_nĂ«nshkrim_pub_hash = bytes(tbe["identity"]) 152 peer = KEYSET.get(çelĂ«si_nĂ«nshkrim_pub_hash) 153 nĂ«se peer Ă«shtĂ« None: 154 ngrit_valueError("identitet i panjohur") 155 mac_tag = mac( 156 GOST3412Kuznechik(çelĂ«si_mac_identity).encrypt, 157 KUZNECHIK_BLOCKSIZE, 158 çelĂ«si_nĂ«nshkrim_pub_hash, 159 ) 160 nĂ«se jo krahaso_digest(mac_tag, bytes(tbe["identityMac"])): 161 ngrit_valueError("MAC i identitetit i pavlefshĂ«m") 162 tbs = HandshakeTBS(( 163 ("cookieTyre", cookie_tyre), 164 ("cookieYne", cookie_yne), 165 ("pubĂelĂ«siYne", pub_çelĂ«si_yne), 166 )) 167 nĂ«se jo gost3410.verify( 168 CURVE, 169 peer["pub"], 170 GOST34112012256(tbs.encode()).digest(), 171 bytes(tbe["signature"]), 172 ): 173 ngrit_valueError("nĂ«nshkrim i pavlefshĂ«m") 174 kthe peer["emri"]Siç e pĂ«rmenda mĂ« lart, 34.13-2015 pĂ«rshkruan variacione tĂ« ndryshme nga 34.12-2015. Mes tyre ka njĂ« mĂ«nyrĂ« pĂ«r prodhimin e imitoshkallĂ«s, llogaritjes sĂ« MAC-it. NĂ« PyGOST kjo Ă«shtĂ« gost3413.mac(). Kjo mĂ«nyrĂ« kĂ«rkon kalimin e funksionit tĂ« enkriptimit (qĂ« merr dhe kthen njĂ« bllok tĂ« vetĂ«m tĂ« dhĂ«nash), madhĂ«sinĂ« e bllokut tĂ« enkriptimit dhe, nĂ« vetvete, tĂ« dhĂ«nat. Pse nuk mund tĂ« kodosh me fort madhĂ«sinĂ« e bllokut tĂ« enkriptimit? 34.12-2015 pĂ«rshkruan jo vetĂ«m enkriptimin 128-bit Kyznechik, por gjithashtu edhe 64-bitin â njĂ« version pak tĂ« modifikuar i GOST 28147-89, i krijuar qĂ« nga KGB dhe ende ka njĂ« nga pragjet mĂ« tĂ« larta tĂ« sigurisĂ«.
Kuznechik inicializohet duke thirrur gost.3412.GOST3412Kuznechik(key) dhe kthen një objekt me metodat .encrypt()/.decrypt() të përshtatshme për kalim në funksionin 34.13. MAC-u llogaritet si më poshtë: gost3413.mac(GOST3412Kuznechik(key).encrypt, KUZNECHIK_BLOCKSIZE, ciphertext). Për të krahasuar MAC-in e llogaritur me atë të ardhur nuk mund të përdoret krahasimi i zakonshëm (==) i string-ëve të bajtëve, pasi kjo operacion jep rrjedhje të kohës së krahasimit, e cila, në rastin më të përgjithshëm, mund të çojë në dobësi fatale si sulmet ndaj TLS. Në Python ka një funksion të veçantë hmac.compare_digest për këtë.
Funksioni i algoritmit tĂ« enkriptimit mund tĂ« enkriptojĂ« vetĂ«m njĂ« bllok tĂ« dhĂ«nash. PĂ«r mĂ« shumĂ« se njĂ« bllok, dhe pĂ«r gjatĂ«si qĂ« nuk janĂ« shumĂ«fish, Ă«shtĂ« e nevojshme tĂ« pĂ«rdoret njĂ« mod i enkriptimit. NĂ« 34.13-2015 pĂ«rshkruhen: ECB, CTR, OFB, CBC, CFB. Secili ka fushat e veta tĂ« lejueshme tĂ« pĂ«rdorimit dhe karakteristikat. FatkeqĂ«sisht, akoma nuk kemi standardizuar (si CCM, OCB, GCM dhe tĂ« ngjashme) â jemi tĂ« detyruar tĂ« shtojmĂ« sĂ« paku MAC. UnĂ« zgjedh (CTR): ai nuk kĂ«rkon plotĂ«sim deri nĂ« madhĂ«sinĂ« e bllokut, mund tĂ« ekzekutohet paralelisht, pĂ«rdor vetĂ«m funksionin e enkriptimit, mund tĂ« pĂ«rdoret nĂ« mĂ«nyrĂ« tĂ« sigurt pĂ«r enkriptimin e njĂ« numri tĂ« madh mesazhesh (ndryshe nga CBC, ku relativisht shpejt fillojnĂ« kolizionet).
Si .mac(), .ctr() merr të dhëna të ngjashme si input: ciphertext = gost3413.ctr(GOST3412Kuznechik(key).encrypt, KUZNECHIK_BLOCKSIZE, plaintext, iv). Kërkohet caktimi i vektorit të inicializimit, i gjatë saktësisht sa gjysma e bllokut të enkriptimit. Nëse çelësi ynë i enkriptimit përdoret vetëm për të encryptuar një mesazh (ndonjëherë edhe për disa blloqe), është e sigurt të caktohet një vektor inicializimi zero. Për enkriptimin e mesazheve handshake përdorim çdo herë një çelës të veçantë.
Kontrolli i nënshkrimit gost3410.verify() është trivial: dërgojmë kurbën eliptike brenda së cilës punojmë (cila thjesht e fixojmë në protokollin tonë GOSTIM), çelësin publik të nënshkruesit (mos harroni se kjo duhet të jetë një tuple e dy numrave të mëdhenj, dhe jo një string bajtësh), hash 34.11-2012 dhe nënshkrimin që ka ardhur.
Më pas, në iniciator ne përgatisim dhe dërgojmë mesazhin handshake2 të dorëzimit, duke kryer të njëjtat veprime që bëmë gjatë kontrollit, por në mënyrë simetrike: nënshkrimi me çelësat tanë në vend të kontrollit, etj...
456 # Përgatit dhe dërgo Mesazhin Handshake 2 {{{ 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: sesioni u krijua: %s", _id, peer_name)Kur sesioni është krijuar, prodhohen çelësat e transportit (një çelës i veçuar për enkriptimin, për autentifikimin, për secilën nga palët), inicializohet Kuznechik për dekriptimin dhe verifikimin e MAC-it:
499 # Funksioni i dĂ«rguesit tĂ« mesazheve, inicializo dekoderin e transportit {{{ 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 # Prit pĂ«r mesazhet testuese {{{ 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 korutina tani enkripton mesazhet para dĂ«rgimit nĂ« lidhjen TCP. Ădo mesazh ka njĂ« nonce qĂ« rritet monotonikisht, gjithashtu duke shĂ«rbyer si vektor inicializimi gjatĂ« enkriptimit nĂ« modalitetin e numĂ«ruesit. Vlerat e numĂ«ruesit nĂ« çdo mesazh dhe bllok mesazhi do tĂ« garantohet tĂ« kenĂ« ndryshime.
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 += 1Mesazhet që vijnë përpunohen nga korutina msg_receiver, e cila merret me autentifikimin dhe dekriptimin:
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("vlera e papritur e nonce") mac_tag = mac(macer, KUZNECHIK_BLOCKSIZE, payload.encode()) if not compare_digest(mac_tag, bytes(msg_text["payloadMac"])): raise ValueError("MAC e pavlefshme") plaintext = ctr( encrypter, KUZNECHIK_BLOCKSIZE, bytes(payload["ciphertext"]), long2bytes(nonce_expected, 8), ) text = plaintext.decode("utf-8") await OUT_QUEUES[peer_name].put(text)Përfundimi
GOSTIM parashikohet të përdoret ekskluzivisht për qëllime edukative (pasi nuk është testuar, si minimum)! Kodi burimor i programit mund të shkarkohet (Stribog-256 hash: 995bbd368c04e50a481d138c5fa2e43ec7c89bc77743ba8dbabee1fde45de120). Si sihtë të gjitha projektet e mia, lloji , , , , GOSTIM është plotësisht , i përhapur sipas kushteve .
, , anëtar i , zhvillues Python/Go, specialist kryesor .
Burimi: habr.com
