GOSTIM: P2P F2F E2EE IM mewn un noson gyda cryptograffeg GOST

Bod yn ddatblygwr PyGOST llyfrgelloedd (primitives cryptograffig GOST mewn Python pur), rwy'n aml yn derbyn cwestiynau am sut i weithredu'r negeseuon diogel symlaf ar y pen-glin. Mae llawer o bobl yn ystyried bod cryptograffeg gymhwysol yn eithaf syml, a bydd galw .encrypt() ar seiffr bloc yn ddigon i'w anfon yn ddiogel dros sianel gyfathrebu. Mae eraill yn credu mai cryptograffeg gymhwysol yw tynged yr ychydig, ac mae'n dderbyniol bod cwmnΓ―au cyfoethog fel Telegram ag olympiad-fathemategwyr methu gweithredu protocol diogel.

Fe wnaeth hyn oll fy ysgogi i ysgrifennu'r erthygl hon i ddangos nad yw gweithredu protocolau cryptograffig a diogel IM yn dasg mor anodd. Fodd bynnag, nid yw'n werth dyfeisio eich protocolau dilysu a chytundeb allweddol eich hun.

GOSTIM: P2P F2F E2EE IM mewn un noson gyda cryptograffeg GOST
Bydd yr erthygl yn ysgrifennu cyfoedion-i-cyfoedion, ffrind-i-ffrind, pen-i-ben wedi'i amgryptio negesydd gwib gyda SIGMA-I protocol dilysu a chytundeb allweddol (y mae'n cael ei weithredu ar y sail honno IKE IPsec), gan ddefnyddio algorithmau cryptograffig GOST yn unig llyfrgell PyGOST a llyfrgell amgodio neges ASN.1 PyDERASN (am yr hwn yr wyf eisoes ysgrifennodd o'r blaen). Rhagofyniad: rhaid iddo fod mor syml fel y gellir ei ysgrifennu o'r dechrau mewn un noson (neu ddiwrnod gwaith), neu fel arall nid yw'n rhaglen syml mwyach. Mae'n debyg bod ganddo wallau, cymhlethdodau diangen, diffygion, a dyma fy rhaglen gyntaf gan ddefnyddio'r llyfrgell asyncio.

dylunio IM

Yn gyntaf, mae angen inni ddeall sut olwg fydd ar ein IM. Er mwyn symlrwydd, gadewch iddo fod yn rhwydwaith cyfoedion i gyfoedion, heb unrhyw gyfranogwyr wedi'u darganfod. Byddwn yn nodi'n bersonol pa gyfeiriad: porthladd i gysylltu ag ef i gyfathrebu Γ’'r cydgysylltydd.

Deallaf, ar hyn o bryd, fod y dybiaeth bod cyfathrebu uniongyrchol ar gael rhwng dau gyfrifiadur mympwyol yn gyfyngiad sylweddol ar gymhwysedd IM yn ymarferol. Ond po fwyaf y bydd datblygwyr yn gweithredu pob math o faglau croesi NAT, yr hiraf y byddwn yn aros ar y Rhyngrwyd IPv4, gyda thebygolrwydd digalon o gyfathrebu rhwng cyfrifiaduron mympwyol. Pa mor hir allwch chi oddef diffyg IPv6 gartref ac yn y gwaith?

Bydd gennym rwydwaith ffrind-i-ffrind: mae'n rhaid bod yn hysbys o flaen llaw pob cydweithiwr posibl. Yn gyntaf, mae hyn yn symleiddio popeth yn fawr: fe wnaethom gyflwyno ein hunain, dod o hyd i'r enw/allwedd neu fethu Γ’ dod o hyd iddo, wedi'i ddatgysylltu neu barhau i weithio, gan adnabod y cydgysylltydd. Yn ail, yn gyffredinol, mae'n ddiogel ac yn dileu llawer o ymosodiadau.

Bydd y rhyngwyneb IM yn agos at atebion clasurol prosiectau di-sugno, yr wyf yn ei hoffi'n fawr am eu minimaliaeth a'u hathroniaeth Unix-way. Mae'r rhaglen IM yn creu cyfeiriadur gyda thair soced parth Unix ar gyfer pob interlocutor:

  • i mewn - mae negeseuon a anfonir at y cydgysylltydd yn cael eu cofnodi ynddo;
  • out - darllenir negeseuon a dderbynnir oddi wrth yr interlocutor ohoni;
  • datgan - trwy ddarllen ohono, rydym yn darganfod a yw'r cydgysylltydd wedi'i gysylltu ar hyn o bryd, y cyfeiriad cysylltiad/porthladd.

Yn ogystal, mae soced conn yn cael ei greu, trwy ysgrifennu'r porthladd gwesteiwr yr ydym yn cychwyn cysylltiad Γ’'r interlocutor o bell iddo.

|-- alice
|   |-- in
|   |-- out
|   `-- state
|-- bob
|   |-- in
|   |-- out
|   `-- state
`- conn

Mae'r dull hwn yn caniatΓ‘u ichi wneud gweithrediadau annibynnol o gludiant IM a rhyngwyneb defnyddiwr, oherwydd nad oes ffrind, ni allwch blesio pawb. Defnyddio tmux a / neu amlgynffon, gallwch gael rhyngwyneb aml-ffenestr gydag amlygu cystrawen. A chyda chymorth rlwrap gallwch gael llinell fewnbynnu neges sy'n gydnaws Γ’ GNU Readline.

Mewn gwirionedd, mae prosiectau di-sugno'n defnyddio ffeiliau FIFO. Yn bersonol, ni allwn ddeall sut i weithio gyda ffeiliau yn gystadleuol mewn asyncio heb gefndir wedi'i ysgrifennu Γ’ llaw o edafedd pwrpasol (Rwyf wedi bod yn defnyddio'r iaith ar gyfer pethau o'r fath ers amser maith Go). Felly, penderfynais wneud y tro gyda socedi parth Unix. Yn anffodus, mae hyn yn ei gwneud hi'n amhosib gwneud adlais 2001:470:dead::babe 6666 > conn. Rwy'n datrys y broblem hon gan ddefnyddio socat: adlais 2001:470:marw::babe 6666 | socat - UNIX-CONNECT:conn, socat DARLLENWCH UNIX-CONNECT:alice/in.

Y protocol ansicr gwreiddiol

Defnyddir TCP fel cludiant: mae'n gwarantu danfoniad a'i archeb. Nid yw CDU yn gwarantu'r naill na'r llall (a fyddai'n ddefnyddiol pan ddefnyddir cryptograffeg), ond cefnogaeth SCTP Nid yw Python yn dod allan o'r bocs.

Yn anffodus, yn TCP nid oes cysyniad o neges, dim ond ffrwd o beit. Felly, mae angen llunio fformat ar gyfer negeseuon fel y gellir eu rhannu ymhlith ei gilydd yn yr edefyn hwn. Gallwn gytuno i ddefnyddio'r cymeriad porthiant llinell. Mae'n iawn i ddechreuwyr, ond ar Γ΄l i ni ddechrau amgryptio ein negeseuon, gall y nod hwn ymddangos yn unrhyw le yn y testun cipher. Mewn rhwydweithiau, felly, mae protocolau sy'n anfon hyd y neges yn gyntaf mewn beit yn boblogaidd. Er enghraifft, allan o'r bocs mae gan Python xdrlib, sy'n eich galluogi i weithio gyda fformat tebyg XDR.

Ni fyddwn yn gweithio'n gywir ac yn effeithlon gyda darllen TCP - byddwn yn symleiddio'r cod. Rydyn ni'n darllen data o'r soced mewn dolen ddiddiwedd nes i ni ddadgodio'r neges gyflawn. Gellir defnyddio JSON gyda XML hefyd fel fformat ar gyfer y dull hwn. Ond pan fydd cryptograffeg yn cael ei ychwanegu, bydd yn rhaid i'r data gael ei lofnodi a'i ddilysu - a bydd hyn yn gofyn am gynrychioliad beit-am-beit union yr un fath o wrthrychau, nad yw JSON/XML yn ei ddarparu (gall canlyniadau dympiau amrywio).

Mae XDR yn addas ar gyfer y dasg hon, fodd bynnag rwy'n dewis ASN.1 gydag amgodio DER a PyDERASN llyfrgell, gan y bydd gennym wrthrychau lefel uchel wrth law y bydd yn aml yn fwy dymunol a chyfleus i weithio gyda nhw. Yn wahanol i schemaless bengod, Pecyn Neges neu CBOR, Bydd ASN.1 yn gwirio'r data yn awtomatig yn erbyn sgema Γ’ chod caled.

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

Y neges a dderbynnir fydd Msg: naill ai neges destun MsgText (gydag un maes testun am y tro) neu neges ysgwyd llaw MsgHandshake (sy'n cynnwys enw'r cydgysylltydd). Nawr mae'n edrych yn or-gymhleth, ond dyma sylfaen ar gyfer y dyfodol.

     β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚ PeerAβ”‚ β”‚PeerBβ”‚ β””β”€β”€β”¬β”€β”€β”˜ └───────────── dA) β”‚ │───────── ────────>β”‚ β”‚ β”‚ β”‚Msg ysgwyd llaw(IdB) β”‚ β”‚<───────────————─ β”‚ β”‚ MsgText() β”‚ │──── MsgText() β”‚ β”‚ β”‚

IM heb cryptograffeg

Fel y dywedais eisoes, bydd y llyfrgell asyncio yn cael ei ddefnyddio ar gyfer pob gweithrediad soced. Gadewch i ni gyhoeddi'r hyn yr ydym yn ei ddisgwyl yn y lansiad:

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

Gosodwch eich enw eich hun (--ein-enw alice). Rhestrir yr holl gydryngwyr disgwyliedig wedi'u gwahanu gan atalnodau (β€”eu henwau bob, noson). Ar gyfer pob un o'r interlocutors, crΓ«ir cyfeiriadur gyda socedi Unix, yn ogystal Γ’ coroutine ar gyfer pob un i mewn, allan, datgan:

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

Mae negeseuon sy'n dod gan y defnyddiwr o'r soced fewnol yn cael eu hanfon i'r ciw 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"))

Mae negeseuon sy'n dod o interlocutors yn cael eu hanfon i OUT_QUEUES ciwiau, y mae data yn cael ei ysgrifennu i'r soced allan:

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

Wrth ddarllen o soced cyflwr, mae'r rhaglen yn edrych am gyfeiriad y cydgysylltydd yn y geiriadur PEER_ALIVE. Os nad oes cysylltiad Γ’'r cydgysylltydd eto, yna ysgrifennir llinell wag.

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

Wrth ysgrifennu cyfeiriad i soced conn, mae'r swyddogaeth β€œcychwynnwr” cysylltu yn cael ei lansio:

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

Gadewch i ni ystyried y cychwynnwr. Yn gyntaf mae'n amlwg yn agor cysylltiad Γ’'r gwesteiwr / porthladd penodedig ac yn anfon neges ysgwyd llaw gyda'i enw:

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

Yna, mae'n aros am ymateb gan y parti anghysbell. Yn ceisio dadgodio'r ymateb sy'n dod i mewn gan ddefnyddio cynllun Msg ASN.1. Tybiwn y bydd y neges gyfan yn cael ei hanfon mewn un segment TCP a byddwn yn ei derbyn yn atomig wrth ffonio .read(). Rydym yn gwirio ein bod wedi derbyn y neges ysgwyd llaw.

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

Rydym yn gwirio ein bod yn gwybod enw'r cydgysylltydd a dderbyniwyd. Os na, yna rydym yn torri'r cysylltiad. Rydym yn gwirio a ydym eisoes wedi sefydlu cysylltiad ag ef (rhoddodd yr interlocutor y gorchymyn i gysylltu Γ’ ni eto) a'i gau. Mae'r ciw IN_QUEUES yn dal llinynnau Python gyda thestun y neges, ond mae ganddo werth arbennig o Dim sy'n arwydd i'r coroutine msg_sender roi'r gorau i weithio fel ei fod yn anghofio am ei ysgrifennwr sy'n gysylltiedig Γ’'r cysylltiad TCP etifeddiaeth.

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

Mae msg_sender yn derbyn negeseuon sy'n mynd allan (wedi'u ciwio o soced mewn), yn eu cyfresoli i neges MsgText ac yn eu hanfon dros gysylltiad TCP. Gall dorri ar unrhyw adeg - rydym yn amlwg yn rhyng-gipio hyn.

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

Ar y diwedd, mae'r cychwynnwr yn mynd i mewn i ddolen ddiddiwedd o negeseuon darllen o'r soced. Yn gwirio a yw'r negeseuon hyn yn negeseuon testun ac yn eu gosod yn y ciw OUT_QUEUES, y byddant yn cael eu hanfon ohono i soced allan y cydgysylltydd cyfatebol. Pam na allwch chi wneud .read() a dadgodio'r neges? Oherwydd ei bod yn bosibl y bydd nifer o negeseuon gan y defnyddiwr yn cael eu hagregu yn byffer y system weithredu a'u hanfon mewn un segment TCP. Gallwn ddadgodio'r un cyntaf, ac yna gall rhan o'r un dilynol aros yn y byffer. Yn achos unrhyw sefyllfa annormal, rydym yn cau'r cysylltiad TCP ac yn atal y coroutine msg_sender (trwy anfon Dim i'r ciw 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)

Gadewch i ni ddychwelyd at y prif god. Ar Γ΄l creu'r holl goroutines pan fydd y rhaglen yn dechrau, rydyn ni'n cychwyn y gweinydd TCP. Ar gyfer pob cysylltiad sefydledig, mae'n creu coroutine ymatebwr.

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

mae'r ymatebwr yn debyg i'r cychwynnwr ac yn adlewyrchu'r un gweithredoedd, ond mae'r ddolen ddiddiwedd o negeseuon darllen yn cychwyn ar unwaith, er mwyn symlrwydd. Ar hyn o bryd, mae'r protocol ysgwyd llaw yn anfon un neges o bob ochr, ond yn y dyfodol bydd dwy gan y cychwynnwr cysylltiad, ac ar Γ΄l hynny gellir anfon negeseuon testun ar unwaith.

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

Protocol diogel

Mae'n bryd sicrhau ein cyfathrebiadau. Beth rydym yn ei olygu wrth ddiogelwch a beth rydym ei eisiau:

  • cyfrinachedd negeseuon a drosglwyddir;
  • dilysrwydd a chywirdeb negeseuon a drosglwyddir - rhaid canfod eu newidiadau;
  • amddiffyniad rhag ymosodiadau ailchwarae - rhaid canfod y ffaith bod negeseuon ar goll neu'n cael eu hailadrodd (a byddwn yn penderfynu terfynu'r cysylltiad);
  • adnabod a dilysu interlocutors gan ddefnyddio allweddi cyhoeddus a gofnodwyd ymlaen llaw - rydym eisoes wedi penderfynu yn gynharach ein bod yn gwneud rhwydwaith ffrind-i-ffrind. Dim ond ar Γ΄l dilysu y byddwn yn deall gyda phwy yr ydym yn cyfathrebu;
  • argaeledd cyfrinachedd perffaith ymlaen eiddo (PFS) - ni ddylai peryglu ein hallwedd arwyddo hirhoedlog arwain at y gallu i ddarllen pob gohebiaeth flaenorol. Mae cofnodi traffig rhyng-gipio yn mynd yn ddiwerth;
  • dilysrwydd/dilysrwydd negeseuon (cludiant ac ysgwyd llaw) o fewn un sesiwn TCP yn unig. Ni ddylai fod yn bosibl mewnosod negeseuon wedi'u harwyddo/dilysu'n gywir o sesiwn arall (hyd yn oed gyda'r un cydgysylltydd);
  • ni ddylai sylwedydd goddefol weld naill ai dynodwyr defnyddiwr, allweddi cyhoeddus hirhoedlog a drosglwyddir, na hashes oddi wrthynt. Rhywfaint o anhysbysrwydd gan arsylwr goddefol.

Yn syndod, mae bron pawb eisiau cael yr isafswm hwn mewn unrhyw brotocol ysgwyd llaw, ac ychydig iawn o'r uchod sy'n cael ei fodloni yn y pen draw ar gyfer protocolau β€œcynnyrch cartref”. Nawr ni fyddwn yn dyfeisio dim byd newydd. Byddwn yn bendant yn argymell defnyddio Fframwaith sΕ΅n ar gyfer adeiladu protocolau, ond gadewch i ni ddewis rhywbeth symlach.

Y ddau brotocol mwyaf poblogaidd yw:

  • TLS - protocol cymhleth iawn gyda hanes hir o chwilod, tagfeydd, gwendidau, meddwl gwael, cymhlethdod a diffygion (fodd bynnag, nid oes a wnelo hyn fawr ddim Γ’ TLS 1.3). Ond nid ydym yn ei ystyried oherwydd ei fod yn or-gymhleth.
  • IPsec с IKE β€” nid oes ganddynt broblemau cryptograffig difrifol, er nad ydynt yn syml ychwaith. Os darllenwch am IKEv1 ac IKEv2, yna eu ffynhonnell yw STS, protocolau ISO/IEC IS 9798-3 a SIGMA (SIGn-and-MAc) - digon syml i'w gweithredu mewn un noson.

Beth sy'n dda am SIGMA, fel y cyswllt diweddaraf yn natblygiad protocolau STS/ISO? Mae'n cwrdd Γ’'n holl ofynion (gan gynnwys β€œcuddio” dynodwyr interlocutor) ac nid oes ganddo unrhyw broblemau cryptograffig hysbys. Mae'n finimalaidd - bydd tynnu o leiaf un elfen o'r neges protocol yn arwain at ei ansicrwydd.

Gadewch i ni fynd o'r protocol cartref symlaf i SIGMA. Y gweithrediad mwyaf sylfaenol y mae gennym ddiddordeb ynddo yw cytundeb allweddol: Swyddogaeth sy'n allbynnu'r ddau gyfranogwr yr un gwerth, y gellir ei ddefnyddio fel allwedd cymesur. Heb fynd i fanylion: mae pob un o'r partΓ―on yn cynhyrchu pΓ’r allwedd byrhoedlog (a ddefnyddir o fewn un sesiwn yn unig) (allweddi cyhoeddus a phreifat), cyfnewid allweddi cyhoeddus, galw'r swyddogaeth cytundeb, y maent yn trosglwyddo eu hallwedd breifat a'r cyhoedd i'r mewnbwn. allwedd y interlocutor.

β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚ PeerAβ”‚ β”‚ PeerBβ”‚ β””β”€β”€β”€β”€β”€β”˜ └────── β”‚ ╔══════════ ══════════╗ │──────────────>β”‚ β•‘PrvA, PrvA ─ ─ ═ ════════ ═══════════╝ β”‚ idb, pubb β”‚ ╔════════════════════╗ β”‚ <──────── Oes ──────│ β•‘ PrvB, PubB = DHgen() β•‘ β”‚ β”‚ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β• ␕ ― ───┐ ╔════ ═══╧════════════╗ β”‚ β•‘ Allwedd = DH(PrvA, PubB)β•‘ <β”€β”€β”€β”˜ ═║ ═══════ ════╝ β”‚ β”‚ β”‚ β”‚

Gall unrhyw un neidio yn y canol a disodli allweddi cyhoeddus gyda'u rhai eu hunain - nid oes unrhyw ddilysiad o interlocutors yn y protocol hwn. Gadewch i ni ychwanegu llofnod gydag allweddi hirhoedlog.

β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚ PeerAβ”‚ β”‚ PeerBβ”‚ β””β”€β”€β”€β”€β”€β”˜ └─ ─ ─ ─ ─ ─ β”‚ ─ ─ └── arwydd(SignPrvA, (PubA)) β”‚ ╔═ │──────────── ────────────────────────────────────────────────── PubA = llwyth() β•‘ β”‚ β”‚ β•‘ PrvA, PubA = DHgen() β•‘ β”‚ β”‚ β•šβ•β•β•β•β•β•β• ═══════ ════════════════════␂ , arwydd(SignPrvB, (PubB)) β”‚ ╔══════════════ ═══════ ════― ═‗——————————― ─────────── ───────────── ─ ─│ β•‘ SignPrvB, SignPubB = llwyth( )β•‘ β”‚ β”‚ β•‘ PrvB, PubB β”• β”‚ β”‚ β•‘ PrvB, PubB = β•‘ ═════════ ══════════════ ══╝ ────┐ β•” ══════␕␕══␕═ ═ ═ ═ ═ ═ β•— β”‚ β”‚ β•‘ gwirio( SignPubB,...)β•‘ β”‚ <β”€β”€β”€β”˜ β•‘ Allwedd = DH(Pr vA, PubB) β•‘ β”‚ β”‚ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• ═╝ β”‚ β”‚ β”‚

Ni fydd llofnod o'r fath yn gweithio, gan nad yw'n gysylltiedig Γ’ sesiwn benodol. Mae negeseuon o'r fath hefyd yn β€œaddas” ar gyfer sesiynau gyda chyfranogwyr eraill. Rhaid i'r cyd-destun cyfan danysgrifio. Mae hyn yn ein gorfodi i ychwanegu neges arall gan A.

Yn ogystal, mae'n hanfodol ychwanegu eich dynodwr eich hun o dan y llofnod, oherwydd fel arall gallwn ddisodli IdXXX ac ail-lofnodi'r neges gydag allwedd cydgysylltydd hysbys arall. Er mwyn atal ymosodiadau myfyrio, mae'n angenrheidiol bod yr elfennau o dan y llofnod mewn mannau wedi'u diffinio'n glir yn Γ΄l eu hystyr: os yw A yn arwyddo (PubA, PubB), yna mae'n rhaid i B lofnodi (PubB, PubA). Mae hyn hefyd yn sΓ΄n am bwysigrwydd dewis strwythur a fformat data cyfresol. Er enghraifft, mae setiau mewn amgodio ASN.1 DER yn cael eu didoli: bydd SET OF(PubA, PubB) yn union yr un fath Γ’ SET OF(PubB, PubA).

β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚ PeerAβ”‚ β”‚ PeerBβ”‚ β””β”€β”€β”€β”€β”€β”˜ └────── β”‚ ╔══════════ ═════════════════╗ │─────────—————————————————————─ ────────── ─────────────>β”‚ β•‘ SignPrvA, SignPubA = llwyth()β•‘ β”‚ β”‚ β•‘ PrvA, PubA = DHgen() β•‘ β•‘ ═ ═══════ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ╝ β”‚ IdB, PubB, arwydd(ArwyddPrvB, (IdB, PubA, PubB)) β”‚ β•” ␕␕ β”‚ ═␕␕ ═ ═════ ════════════╗ β”‚<────────────────——————─ ────────── ─────────│ β•‘ SignPrvB, SignPubB = llwyth() β•‘ β”‚ β”‚ β•‘ PrvB, PubB = DHgen() β•‘ β”‚ β”‚ β•š β•‘ ═ ════════ ══════════╝ β”‚ arwydd(SignPrvA, (IDA, PubB, PubA)) β”‚ ╔══════════════ ════╗ │─ ────────────────────────────────────────────── ───>β”‚ β•‘ gwirio(SignPubB, ...) β•‘ β”‚ β”‚ β•‘key = dh (prva, PUBB) β•‘ β”‚ β”‚ β”‚

Fodd bynnag, nid ydym wedi β€œprofi” o hyd ein bod wedi cynhyrchu'r un allwedd a rennir ar gyfer y sesiwn hon. Mewn egwyddor, gallwn wneud heb y cam hwn - bydd y cysylltiad trafnidiaeth cyntaf un yn annilys, ond rydym am, pan fydd yr ysgwyd llaw wedi'i gwblhau, y byddem yn sicr y cytunir ar bopeth mewn gwirionedd. Ar hyn o bryd mae gennym y protocol ISO/IEC IS 9798-3 wrth law.

Gallem lofnodi'r allwedd a gynhyrchir ei hun. Mae hyn yn beryglus, gan ei bod yn bosibl y bydd yr algorithm llofnod a ddefnyddir yn gollwng (er ei fod yn ddarnau fesul llofnod, ond yn dal i ollwng). Mae'n bosibl arwyddo hash o'r allwedd tarddiad, ond gall gollwng hyd yn oed stwnsh yr allwedd ddeilliadol fod yn werthfawr mewn ymosodiad 'n Ysgrublaidd ar y swyddogaeth tarddiad. Mae SIGMA yn defnyddio swyddogaeth MAC sy'n dilysu ID yr anfonwr.

β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚ PeerAβ”‚ β”‚ PeerBβ”‚ β””β”€β”€β”€β”€β”€β”˜ └────── β”‚ ╔══════════ ═════════════════╗ │─────────—————————————————————─ ────────── ──────────eiddiadurten ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ╝ β”‚ IdB, PubB, arwydd(SignPrvB, (PubA, PubB) β•”) β•‘ ═══ β”‚<───────────────── ─────────────────────────── ────────── ─│ β•‘signprvb, Signpubb = llwyth () β•‘ β”‚ β”‚ β•‘prvb, pubb = dhGen () β•‘ β”‚ β”‚ β”‚ β•šβ•β•β•β• ═════════════════════ ═════════════════════ ══╝ β”‚ β”‚ ╔════════════ ╔════════════ ═════════╗ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ Prva, pubb) β•‘ │──────────’s’s’s’s ond aelodau ─────>β”‚ β•‘ gwirio(Allwedd, IdB) β•‘ β”‚ β”‚ β•‘ gwirio(SignPubB,...) β•‘ β”‚ β”‚ β•šβ•β•β•β•β• ␕ ══ β•• ═════ ═╝ β”‚ β”‚

Fel optimeiddio, efallai y bydd rhai am ailddefnyddio eu bysellau byrhoedlog (sydd, wrth gwrs, yn anffodus i PFS). Er enghraifft, fe wnaethom gynhyrchu pΓ’r allweddol, ceisio cysylltu, ond nid oedd TCP ar gael neu amharwyd arno rhywle yng nghanol y protocol. Mae'n drueni gwastraffu adnoddau entropi a phrosesydd ar bΓ’r newydd. Felly, byddwn yn cyflwyno'r cwci fel y'i gelwir - gwerth ffug-hap a fydd yn amddiffyn rhag ymosodiadau ailchwarae ar hap posibl wrth ailddefnyddio allweddi cyhoeddus byrhoedlog. Oherwydd y rhwymiad rhwng y cwci a'r allwedd gyhoeddus dros dro, gellir tynnu allwedd gyhoeddus y parti arall o'r llofnod fel rhywbeth diangen.

β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚ PeerAβ”‚ β”‚ PeerBβ”‚ β””β”€β”€β”€β”€β”€β”€β””β”€β”€β”˜ └─── CwciA β”‚ ╔════════ ═══════════════════╗ │───────—————————————————————————————─ ────────── ────────────────────────────────────────────── ─>β”‚ β•‘SignPrvA, SignPubA = llwyth( ) β•‘ β”‚ β”‚ β•‘ PrvA, PubA = DHgen() β•‘ β”‚ β”‚ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β‘ ══╝ β”‚IdB, PubB, CookieB , arwydd(SignPrvB, (CookieA, CookieB, PubB)), MAC(IdB) β”‚ ╔═════════════════␕␕␕␕␕ β•— β”‚< ────────────────────────────────────────────── ────────── ────────────────────────│ β•‘ SignPrvB, SignPubB = llwyth() β•‘ β”‚ β”‚ ─ ────────│ β•‘ SignPrvB, SignPubB = llwyth() β•‘ β”‚ β”‚ β”‚ B β”‚ β”‚ B β”‚ β”‚ β”‚ B β”‚ β”‚ β”‚ β”‚ β”‚ B β”‚ β”‚ β”‚ β”‚ β•šβ•β•β•β•β•β• ═════════════════════╝ β”‚ β”‚ ╔═════════║ ═══════╗ β”‚ arwydd( Signprva, (CookieB, Cookiea, Puba)), Mac (IDA) β”‚ β•‘Key = DH (PRVA, PUBB) β•‘ │─────────’s’s’s’s’s’s aelodPINP ────────────────────────────────────────────── ───────>β”‚ β•‘ dilysu(Allwedd, IdB) β•‘ β”‚ β”‚ β•‘ gwirio(SignPubB, ...) β•‘ β”‚ β”‚ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• β”‚

Yn olaf, rydym am gael preifatrwydd ein partneriaid sgwrsio gan arsylwr goddefol. I wneud hyn, mae SIGMA yn cynnig cyfnewid allweddi byrhoedlog yn gyntaf a datblygu allwedd gyffredin i amgryptio dilysu ac adnabod negeseuon arni. Mae SIGMA yn disgrifio dau opsiwn:

  • SIGMA-I - yn amddiffyn y cychwynnwr rhag ymosodiadau gweithredol, yr ymatebydd rhag rhai goddefol: mae'r cychwynnwr yn dilysu'r ymatebydd ac os nad yw rhywbeth yn cyfateb, yna nid yw'n rhoi ei hunaniaeth. Mae'r diffynnydd yn rhoi ei brawf adnabod os cychwynnir protocol gweithredol gydag ef. Nid yw'r sylwedydd goddefol yn dysgu dim;
    SIGMA-R - yn amddiffyn yr ymatebydd rhag ymosodiadau gweithredol, y cychwynnwr rhag rhai goddefol. Mae popeth yn union i'r gwrthwyneb, ond yn y protocol hwn mae pedair neges ysgwyd llaw eisoes yn cael eu trosglwyddo.

    Rydyn ni'n dewis SIGMA-I gan ei fod yn debycach i'r hyn rydyn ni'n ei ddisgwyl gan bethau cyfarwydd cleient-gweinydd: dim ond y gweinydd dilys sy'n cydnabod y cleient, ac mae pawb eisoes yn adnabod y gweinydd. Hefyd mae'n haws ei weithredu oherwydd llai o negeseuon ysgwyd llaw. Y cyfan rydyn ni'n ei ychwanegu at y protocol yw amgryptio rhan o'r neges a throsglwyddo'r dynodwr A i'r rhan o'r neges olaf sydd wedi'i hamgryptio:

    PubA, CookieA β”‚ ╔══════════ ═════════════― ― ― ― ― ― ― ― ― ― ― ― ― ― ― ────────── ───── ────────’s ────────── wyl ond────’s’s’s’s ond dir mae'r ───── ──────>β”‚ β•‘ SignPrvA , SignPubA = llwyth()β•‘ β”‚ β”‚ β•‘ PrvA, PubA = DHgen() βš‘ β”• β”‚ ═════════ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ╝ β”‚ PubB, CookieB, Amg ((IdB, arwydd(SignPrvB, (CookieA, CookieB, PubB)), MAC(IdB) ␔ ␔) β•” ═══════════════ ═══════╗ β”‚<──────—————————————————————————' ────────── ───── ────────── β•‘ SignP rvB, SignPubB = llwyth()β•‘ β”‚ β”‚ β•‘ PrvB, PubB = β”š β”• β”• β”‚ β”‚ β”• β”‚ β”‚ β”• ════════ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ╝ β”‚ β”‚ ╔════════ ═␕═ ═␕ ═ ═ β•— β”‚ Amg((IdA, arwydd( SignPrvA, (CookieB, CookieA, PubA), MAC(IdA))) β”‚ β•‘ Allwedd = DH(PrvA, PubB) β•‘ │────────────────── ─ ────────────────── ───────── ( SignPubB, ...) ─ ──── ═══════ ══════════╝ β”‚ β”‚
    
    • Defnyddir GOST R ar gyfer llofnod 34.10-2012 algorithm gydag allweddi 256-did.
    • I gynhyrchu'r allwedd gyhoeddus, defnyddir 34.10/2012/XNUMX VKO.
    • Defnyddir CMAC fel y MAC. Yn dechnegol, mae hwn yn ddull gweithredu arbennig o seiffr bloc, a ddisgrifir yn GOST R 34.13-2015. Fel swyddogaeth amgryptio ar gyfer y modd hwn - Troellwr gwair (34.12-2015).
    • Mae stwnsh ei allwedd gyhoeddus yn cael ei ddefnyddio fel dynodwr yr interlocutor. Wedi'i ddefnyddio fel hash Stribog- 256 (34.11/2012/256 XNUMX did).

    Ar Γ΄l yr ysgwyd llaw, byddwn yn cytuno ar allwedd a rennir. Gallwn ei ddefnyddio ar gyfer amgryptio dilys o negeseuon trafnidiaeth. Mae'r rhan hon yn syml iawn ac yn anodd gwneud camgymeriad: rydym yn cynyddu'r cownter neges, yn amgryptio'r neges, yn dilysu (MAC) y cownter a ciphertext, yn anfon. Wrth dderbyn neges, rydyn ni'n gwirio bod gan y cownter y gwerth disgwyliedig, yn dilysu'r testun cipher gyda'r cownter, ac yn ei ddadgryptio. Pa allwedd ddylwn i ei defnyddio i amgryptio negeseuon ysgwyd llaw, cludo negeseuon, a sut i'w dilysu? Mae defnyddio un allwedd ar gyfer yr holl dasgau hyn yn beryglus ac yn annoeth. Mae angen cynhyrchu allweddi gan ddefnyddio swyddogaethau arbenigol KDF (swyddogaeth tarddiad allweddol). Unwaith eto, gadewch i ni beidio Γ’ hollti blew a dyfeisio rhywbeth: HKDF wedi bod yn hysbys ers tro, wedi'i ymchwilio'n dda ac nid oes ganddo unrhyw broblemau hysbys. Yn anffodus, nid oes gan y llyfrgell Python brodorol y swyddogaeth hon, felly rydym yn defnyddio hkdf bag plastig. Mae HKDF yn defnyddio'n fewnol GLlEM, sydd yn ei dro yn defnyddio swyddogaeth hash. Mae gweithredu enghreifftiol yn Python ar y dudalen Wicipedia yn cymryd dim ond ychydig linellau o god. Fel yn achos 34.10/2012/256, byddwn yn defnyddio Stribog-XNUMX fel y swyddogaeth hash. Gelwir allbwn ein swyddogaeth cytundeb allweddol yn allwedd sesiwn, a bydd y rhai cymesur coll yn cael eu cynhyrchu ohono:

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

    Strwythurau/Cynlluniau

    Edrychwn ar ba strwythurau ASN.1 sydd gennym nawr ar gyfer trosglwyddo'r holl ddata hwn:

    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 yw'r hyn a gaiff ei lofnodi. HandshakeTBE - beth fydd yn cael ei amgryptio. Tynnaf eich sylw at y maes ukm yn MsgHandshake1. 34.10 Mae VKO, ar gyfer hapnodi hyd yn oed yn fwy o'r allweddi a gynhyrchir, yn cynnwys y paramedr UKM (deunydd bysellu defnyddiwr) - dim ond entropi ychwanegol.

    Ychwanegu Cryptograffeg at y Cod

    Gadewch i ni ystyried y newidiadau a wnaed i'r cod gwreiddiol yn unig, gan fod y fframwaith wedi aros yr un fath (mewn gwirionedd, ysgrifennwyd y gweithrediad terfynol yn gyntaf, ac yna torrwyd yr holl cryptograffeg allan ohono).

    Gan y bydd dilysu ac adnabod interlocutors yn cael ei wneud gan ddefnyddio allweddi cyhoeddus, bellach mae angen eu storio yn rhywle am amser hir. Er mwyn symlrwydd, rydym yn defnyddio JSON fel hyn:

    {
        "our": {
            "prv": "21254cf66c15e0226ef2669ceee46c87b575f37f9000272f408d0c9283355f98",
            "pub": "938c87da5c55b27b7f332d91b202dbef2540979d6ceaa4c35f1b5bfca6df47df0bdae0d3d82beac83cec3e353939489d9981b7eb7a3c58b71df2212d556312a1"
        },
        "their": {
            "alice": "d361a59c25d2ca5a05d21f31168609deeec100570ac98f540416778c93b2c7402fd92640731a707ec67b5410a0feae5b78aeec93c4a455a17570a84f2bc21fce",
            "bob": "aade1207dd85ecd283272e7b69c078d5fae75b6e141f7649ad21962042d643512c28a2dbdc12c7ba40eb704af920919511180c18f4d17e07d7f5acd49787224a"
        }
    }
    

    ein - ein pΓ’r allweddol, allweddi preifat a chyhoeddus hecsadegol. eu β€” enwau interlocutors a'u allweddi cyhoeddus. Gadewch i ni newid y dadleuon llinell orchymyn ac ychwanegu Γ΄l-brosesu 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),
        }
    # }}}
    

    Allwedd breifat yr algorithm 34.10 yw rhif ar hap. Maint 256-did ar gyfer cromliniau eliptig 256-did. Nid yw PyGOST yn gweithio gyda set o beit, ond gyda niferoedd mawr, felly mae angen trosi ein allwedd breifat (urandom(32)) i rif gan ddefnyddio gost3410.prv_unmarshal(). Mae'r allwedd gyhoeddus yn cael ei phennu'n benderfynol o'r allwedd breifat gan ddefnyddio gost3410.public_key(). Mae allwedd gyhoeddus 34.10 yn ddau rif mawr y mae angen eu trosi hefyd yn ddilyniant beit er mwyn ei storio a'i drosglwyddo'n rhwydd gan ddefnyddio gost3410.pub_marshal().

    Ar Γ΄l darllen y ffeil JSON, mae angen trosi'r allweddi cyhoeddus yn Γ΄l gan ddefnyddio gost3410.pub_unmarshal(). Gan y byddwn yn derbyn dynodwyr y interlocutors ar ffurf hash o'r allwedd gyhoeddus, gellir eu cyfrifo ar unwaith ymlaen llaw a'u gosod mewn geiriadur ar gyfer chwiliad cyflym. Mae hash Stribog-256 yn gost34112012256.GOST34112012256(), sy'n bodloni'n llawn y rhyngwyneb hashlib o swyddogaethau hash.

    Sut mae coroutine y cychwynnwr wedi newid? Mae popeth yn unol Γ’'r cynllun ysgwyd llaw: rydym yn cynhyrchu cwci (mae 128-bit yn ddigon), pΓ’r allwedd byrhoedlog 34.10, a fydd yn cael ei ddefnyddio ar gyfer swyddogaeth cytundeb allwedd 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()
    

    • rydym yn aros am ymateb ac yn dadgodio'r neges Msg sy'n dod i mewn;
    • gwnewch yn siΕ΅r eich bod yn cael ysgwyd llaw1;
    • datgodio allwedd gyhoeddus fyrhoedlog y parti arall a chyfrifo allwedd y sesiwn;
    • Rydym yn cynhyrchu allweddi cymesuredd angenrheidiol ar gyfer prosesu rhan TBE y neges.

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

    Mae UKM yn rhif 64-did (wrandom(8)), sydd hefyd angen ei ddad-gyfeirio o'i gynrychiolaeth beit gan ddefnyddio gost3410_vko.ukm_unmarshal(). Swyddogaeth VKO ar gyfer 34.10/2012/256 3410-bit yw gost34102012256_vko.kek_XNUMX() (KEK - allwedd amgryptio).

    Mae'r allwedd sesiwn a gynhyrchir eisoes yn ddilyniant beit ffug-hap 256-did. Felly, gellir ei ddefnyddio ar unwaith mewn swyddogaethau HKDF. Gan fod GOST34112012256 yn bodloni'r rhyngwyneb hashlib, gellir ei ddefnyddio ar unwaith yn y dosbarth Hkdf. Nid ydym yn nodi'r halen (dadl gyntaf Hkdf), gan y bydd yr allwedd a gynhyrchir, oherwydd byrhoedledd y parau allweddol sy'n cymryd rhan, yn wahanol ar gyfer pob sesiwn ac mae eisoes yn cynnwys digon o entropi. Mae kdf.expand() yn ddiofyn eisoes yn cynhyrchu'r bysellau 256-bit sydd eu hangen ar gyfer Grasshopper yn nes ymlaen.

    Nesaf, mae rhannau TBE a TBS y neges sy'n dod i mewn yn cael eu gwirio:

    • mae'r MAC dros y testun cipher sy'n dod i mewn yn cael ei gyfrifo a'i wirio;
    • mae'r testun cipher wedi'i ddadgryptio;
    • Mae strwythur TBE wedi'i ddadgodio;
    • cymerir dynodydd yr ymddyddan oddiyno a gwirir a ydyw yn hysbys i ni o gwbl ;
    • Mae MAC dros y dynodwr hwn yn cael ei gyfrifo a'i wirio;
    • mae'r llofnod dros y strwythur TBS yn cael ei wirio, sy'n cynnwys cwci'r ddau barti ac allwedd gyhoeddus byrhoedlog y parti arall. Mae'r llofnod yn cael ei wirio gan allwedd llofnod hirhoedlog y cydgysylltydd.

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

    Fel yr ysgrifennais uchod, mae 34.13/2015/XNUMX yn disgrifio amrywiol dulliau gweithredu seiffr bloc o 34.12/2015/3413. Yn eu plith mae modd cynhyrchu mewnosodiadau dynwared a chyfrifiadau MAC. Yn PyGOST mae hyn yn gost34.12.mac(). Mae'r modd hwn yn gofyn am basio'r swyddogaeth amgryptio (derbyn a dychwelyd un bloc o ddata), maint y bloc amgryptio ac, mewn gwirionedd, y data ei hun. Pam na allwch chi god caled maint y bloc amgryptio? Mae 2015/128/64 yn disgrifio nid yn unig y seiffr ceiliogod rhedyn XNUMX-did, ond hefyd y seiffr XNUMX-did Magma - GOST 28147-89 wedi'i addasu ychydig, a grΓ«wyd yn Γ΄l yn y KGB ac mae ganddo un o'r trothwyon diogelwch uchaf o hyd.

    Mae Kuznechik yn cael ei gychwyn trwy ffonio gost.3412.GOST3412Kuznechik(allwedd) ac yn dychwelyd gwrthrych gyda dulliau .encrypt()/.decrypt() sy'n addas ar gyfer trosglwyddo i swyddogaethau 34.13. Cyfrifir MAC fel a ganlyn: gost3413.mac(GOST3412Kuznechik(key).encrypt, KUZNECHIK_BLOCKSIZE, ciphertext). I gymharu'r MAC a gyfrifwyd ac a dderbyniwyd, ni allwch ddefnyddio'r gymhariaeth arferol (==) o linynnau beit, gan fod y llawdriniaeth hon yn gollwng amser cymharu, a all, yn yr achos cyffredinol, arwain at wendidau angheuol fel BEAST ymosodiadau ar TLS. Mae gan Python swyddogaeth arbennig, hmac.compare_digest, ar gyfer hyn.

    Dim ond un bloc o ddata y gall swyddogaeth seiffr bloc ei amgryptio. Ar gyfer nifer fwy, a hyd yn oed nid lluosrif o'r hyd, mae angen defnyddio'r modd amgryptio. Mae 34.13-2015 yn disgrifio'r canlynol: ECB, CTR, OFB, CBS, CFB. Mae gan bob un ei feysydd cymhwyso a nodweddion derbyniol ei hun. Yn anffodus, nid ydym wedi safoni o hyd dulliau amgryptio dilys (fel CCM, OCB, GCM ac yn y blaen) - rydym yn cael ein gorfodi i o leiaf ychwanegu MAC ein hunain. Rwy'n dewis modd cownter (CTR): nid oes angen padin i faint y bloc, gellir ei gyfochrog, mae'n defnyddio'r swyddogaeth amgryptio yn unig, gellir ei ddefnyddio'n ddiogel i amgryptio nifer fawr o negeseuon (yn wahanol i CBS, sydd Γ’ gwrthdrawiadau yn gymharol gyflym).

    Fel .mac(), .ctr() yn cymryd mewnbwn tebyg: ciphertext = gost3413.ctr(GOST3412Kuznechik(key).encrypt, KUZNECHIK_BLOCKSIZE, plaintext, iv). Mae'n ofynnol nodi fector cychwyn sydd union hanner hyd y bloc amgryptio. Os mai dim ond i amgryptio un neges y defnyddir ein bysell amgryptio (er o sawl bloc), yna mae'n ddiogel gosod fector cychwyniad sero. I amgryptio negeseuon ysgwyd llaw, rydym yn defnyddio allwedd ar wahΓ’n bob tro.

    Mae dilysu'r llofnod gost3410.verify() yn ddibwys: rydyn ni'n pasio'r gromlin eliptig rydyn ni'n gweithio o'i mewn (rydym yn ei gofnodi yn ein protocol GOSTIM), allwedd gyhoeddus yr arwyddwr (peidiwch ag anghofio y dylai hwn fod yn dwple o ddau niferoedd mawr, ac nid llinyn beit), 34.11/2012/XNUMX hash a'r llofnod ei hun.

    Nesaf, yn y cychwynnwr rydym yn paratoi ac yn anfon neges ysgwyd llaw i ysgwyd llaw2, gan berfformio'r un gweithredoedd ag y gwnaethom yn ystod y dilysu, dim ond yn gymesur: llofnodi ein bysellau yn lle gwirio, ac ati ...

     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)
     

    Pan fydd y sesiwn wedi'i sefydlu, cynhyrchir allweddi trafnidiaeth (allwedd ar wahΓ’n ar gyfer amgryptio, ar gyfer dilysu, ar gyfer pob un o'r partΓ―on), ac mae'r Grasshopper yn cael ei gychwyn i ddadgryptio a gwirio'r 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     # }}}
    

    Mae coroutine msg_sender bellach yn amgryptio negeseuon cyn eu hanfon ar gysylltiad TCP. Mae gan bob neges nonce sy'n cynyddu'n undonog, sef y fector ymgychwyn hefyd pan gaiff ei hamgryptio yn y modd cownter. Mae pob neges a bloc neges yn sicr o gael gwrthwerth gwahanol.

    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
    

    Mae negeseuon sy'n dod i mewn yn cael eu prosesu gan y coroutine msg_receiver, sy'n delio Γ’ dilysu a dadgryptio:

    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)
    

    Casgliad

    Bwriedir i GOSTIM gael ei ddefnyddio at ddibenion addysgol yn unig (gan nad yw'n cael ei gwmpasu gan brofion, o leiaf)! Gellir lawrlwytho cod ffynhonnell y rhaglen yma (Π‘Ρ‚Ρ€ΠΈΠ±ΠΎΠ³-256 Ρ…ΡΡˆ: 995bbd368c04e50a481d138c5fa2e43ec7c89bc77743ba8dbabee1fde45de120). Как ΠΈ всС ΠΌΠΎΠΈ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Ρ‹, Ρ‚ΠΈΠΏΠ° GoGOST, PyDERASN, NCCP, GoVPN, GOSTIM yn gwbl meddalwedd am ddimdosbarthu dan y telerau GPLv3 +.

    Sergey Matveev, cypherpunk, aelod Sefydliad SPO, Python/Go-developer, prif arbenigwr Menter Unedol Talaith Ffederal "STC "Atlas".

Ffynhonnell: hab.com

Ychwanegu sylw