GOSTIM: P2P F2F E2EE IM sa usa ka gabii nga adunay GOST cryptography

Pagkahimong usa ka developer PyGOST mga librarya (GOST cryptographic primitives sa purong Python), kanunay kong makadawat og mga pangutana kon unsaon pagpatuman ang pinakasimple nga luwas nga mensahe sa tuhod. Daghang mga tawo ang nag-isip sa gipadapat nga cryptography nga yano ra, ug ang pagtawag sa .encrypt() sa usa ka block cipher igo na aron ipadala kini nga luwas sa usa ka channel sa komunikasyon. Ang uban nagtuo nga ang gipadapat nga cryptography mao ang kapalaran sa pipila, ug gidawat nga ang mga adunahan nga kompanya sama sa Telegram nga adunay mga olympiad-mathematician. dili ma implementar luwas nga protocol.

Kining tanan nag-aghat kanako sa pagsulat niini nga artikulo aron ipakita nga ang pagpatuman sa cryptographic protocol ug luwas nga IM dili ingon ka lisud nga buluhaton. Bisan pa, dili kini angayan nga mag-imbento sa imong kaugalingon nga pag-authenticate ug mga protocol sa panguna nga kasabutan.

GOSTIM: P2P F2F E2EE IM sa usa ka gabii nga adunay GOST cryptography
Ang artikulo mosulat -aw-sa-peer, higala-sa-higala, end-to-end nga gi-encrypt instant messenger uban sa SIGMA-I authentication ug key agreement protocol (base sa kung asa kini gipatuman IPsec IKE), gamit ang eksklusibong GOST cryptographic algorithms PyGOST library ug ASN.1 message encoding library PyDERASN (mahitungod niini ako na misulat kaniadto). Usa ka kinahanglanon: kini kinahanglan nga yano kaayo nga kini mahimong isulat gikan sa wala sa usa ka gabii (o adlaw sa trabaho), kung dili kini usa ka yano nga programa. Tingali kini adunay mga sayup, wala kinahanglana nga mga komplikasyon, mga kakulangan, ug kini ang akong una nga programa gamit ang asyncio library.

IM nga disenyo

Una, kinahanglan natong masabtan kung unsa ang hitsura sa atong IM. Alang sa kayano, himoa nga kini usa ka peer-to-peer nga network, nga walay bisan unsang pagdiskobre sa mga partisipante. Personal namong ipaila kung asa nga adres: pantalan nga makonektar aron makigkomunikar sa interlocutor.

Akong nasabtan nga, niining panahona, ang pangagpas nga ang direktang komunikasyon anaa sa taliwala sa duha ka arbitraryong mga kompyuter usa ka mahinungdanong limitasyon sa paggamit sa IM sa praktis. Apan kon mas daghan nga mga developers ang nagpatuman sa tanang matang sa NAT-traversal crutches, mas dugay nga kita magpabilin sa IPv4 Internet, nga adunay makapaguol nga posibilidad sa komunikasyon tali sa arbitraryong mga kompyuter. Hangtod kanus-a nimo maagwanta ang kakulang sa IPv6 sa balay ug trabaho?

Kita adunay usa ka higala-sa-higala nga network: ang tanan nga posible nga mga interlocutors kinahanglan nga mahibal-an daan. Una, gipasimple kaayo niini ang tanan: gipaila namo ang among kaugalingon, nakit-an o wala makit-an ang ngalan / yawe, giputol o nagpadayon sa pagtrabaho, nahibal-an ang interlocutor. Ikaduha, sa kinatibuk-an, kini luwas ug nagwagtang sa daghang mga pag-atake.

Ang IM interface mahimong duol sa klasiko nga mga solusyon walay pulos nga mga proyekto, nga ganahan kaayo ko sa ilang minimalism ug Unix-way nga pilosopiya. Ang programa sa IM nagmugna og usa ka direktoryo nga adunay tulo ka Unix domain sockets alang sa matag interlocutor:

  • saβ€”mga mensahe nga gipadala ngadto sa interlocutor girekord niini;
  • out - ang mga mensahe nga nadawat gikan sa interlocutor gibasa gikan niini;
  • estado - pinaagi sa pagbasa gikan niini, atong mahibal-an kung ang interlocutor sa pagkakaron konektado, ang koneksyon address/port.

Dugang pa, ang usa ka conn socket gihimo, pinaagi sa pagsulat sa host port diin kami nagsugod sa usa ka koneksyon sa hilit nga interlocutor.

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

Kini nga pamaagi nagtugot kanimo sa paghimo sa mga independente nga pagpatuman sa IM transport ug user interface, tungod kay walay higala, dili nimo mapahimut-an ang tanan. Paggamit tmux ug / o daghang ikog, mahimo kang makakuha og multi-window interface nga adunay syntax highlighting. Ug uban sa tabang rlwrap makakuha ka ug GNU Readline-compatible message input line.

Sa tinuud, ang mga proyekto nga wala’y sipsip naggamit sa mga file sa FIFO. Sa personal, dili nako masabtan kung giunsa pagtrabaho ang mga file nga kompetisyon sa asyncio nga wala’y sinulat sa kamot nga background gikan sa gipahinungod nga mga hilo (Gigamit nako ang sinultian alang sa ingon nga mga butang sa dugay nga panahon Go). Busa, nakahukom ko nga buhaton ang Unix domain sockets. Ikasubo, kini naghimo nga imposible nga buhaton ang echo 2001: 470: patay:: babe 6666 > conn. Gisulbad nako kini nga problema gamit ang socat: echo 2001:470:patay::babe 6666 | socat - UNIX-CONNECT:conn, socat READLINE UNIX-CONNECT:alice/in.

Ang orihinal nga dili sigurado nga protocol

Ang TCP gigamit isip transportasyon: gigarantiyahan niini ang pagpadala ug ang order niini. UDP dili garantiya (nga mahimong mapuslanon kon cryptography gigamit), apan suporta SCTP Ang Python dili mogawas sa kahon.

Ikasubo, sa TCP walay konsepto sa usa ka mensahe, usa lamang ka sapa sa bytes. Busa, kinahanglan nga maghimo ug usa ka pormat alang sa mga mensahe aron kini mapaambit sa ilang kaugalingon niini nga hilo. Mahimo kaming magkauyon nga gamiton ang karakter sa feed sa linya. Maayo kini alang sa mga nagsugod, apan sa higayon nga magsugod kami sa pag-encrypt sa among mga mensahe, kini nga karakter mahimong makita bisan asa sa ciphertext. Sa mga network, busa, ang mga sikat nga protocol mao ang una nga nagpadala sa gitas-on sa mensahe sa mga byte. Pananglitan, gikan sa kahon ang Python adunay xdrlib, nga nagtugot kanimo sa pagtrabaho sa susama nga format XDR.

Dili kami molihok sa husto ug episyente sa pagbasa sa TCP - among pasimplehon ang code. Gibasa namo ang datos gikan sa socket sa walay katapusan nga loop hangtud nga among gi-decode ang kompleto nga mensahe. Ang JSON nga adunay XML mahimo usab nga magamit ingon usa ka format alang niini nga pamaagi. Apan kung idugang ang kriptograpiya, kinahanglan nga pirmahan ug pamatud-an ang datos - ug magkinahanglan kini usa ka byte-for-byte nga parehas nga representasyon sa mga butang, nga wala gihatag sa JSON/XML (mahimong magkalainlain ang mga resulta sa dump).

Ang XDR angay alang niini nga buluhaton, bisan pa niana gipili nako ang ASN.1 nga adunay DER encoding ug PyDERASN librarya, tungod kay kita adunay taas nga lebel nga mga butang nga magamit diin kini kanunay nga labi ka makapahimuot ug kombenyente sa pagtrabaho. Dili sama sa schemaless bencode, MessagePack o CBOR, ASN.1 awtomatik nga susihon ang datos batok sa usa ka hard-coded schema.

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

Ang nadawat nga mensahe mao ang Msg: bisan usa ka text MsgText (nga adunay usa ka text field sa pagkakaron) o usa ka MsgHandshake handshake message (nga adunay ngalan sa interlocutor). Karon kini morag sobra ka komplikado, apan kini usa ka pundasyon alang sa umaabot.

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

IM nga walay cryptography

Sa ako nang giingon, ang asyncio library kay gamiton para sa tanang socket operations. Atong ipahibalo ang atong gipaabot sa paglusad:

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

Ibutang ang imong kaugalingong ngalan (--our-name alice). Ang tanan nga gipaabot nga mga interlocutor gilista nga gibulag sa mga koma (β€”ilang-ngalan bob, eve). Alang sa matag usa sa mga interlocutors, usa ka direktoryo nga adunay mga socket sa Unix gihimo, ingon man usa ka coroutine alang sa matag sulod, gawas, estado:

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

Ang mga mensahe nga gikan sa user gikan sa in socket ipadala sa IN_QUEUES queue:

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

Ang mga mensahe gikan sa mga interlocutor ipadala ngadto sa OUT_QUEUES nga mga pila, diin ang datos gisulat ngadto sa out socket:

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

Kung nagbasa gikan sa usa ka socket sa estado, ang programa nangita alang sa adres sa interlocutor sa PEER_ALIVE nga diksyonaryo. Kung wala pa'y koneksyon sa interlocutor, unya usa ka walay sulod nga linya ang gisulat.

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

Sa pagsulat sa usa ka adres sa usa ka conn socket, ang koneksyon nga "initiator" function gilunsad:

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

Atong tagdon ang initiator. Una kini klaro nga nagbukas sa usa ka koneksyon sa piho nga host / port ug nagpadala usa ka mensahe sa handshake nga adunay ngalan niini:

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

Unya, naghulat kini og tubag gikan sa hilit nga partido. Naningkamot sa pag-decode sa umaabot nga tubag gamit ang Msg ASN.1 scheme. Kami nagtuo nga ang tibuok nga mensahe ipadala sa usa ka bahin sa TCP ug ato kining madawat sa atomikong paagi sa pagtawag sa .read(). Among gisusi nga nadawat namo ang mensahe sa handshake.

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

Gisusi namo nga nahibal-an namo ang nadawat nga ngalan sa interlocutor. Kung dili, nan among giputol ang koneksyon. Among susihon kung nakatukod na ba kami og koneksyon uban kaniya (ang interlocutor mihatag na usab ug sugo nga makonektar kanamo) ug isira kini. Ang IN_QUEUES nga pila nagkupot sa Python nga mga kuwerdas nga adunay teksto sa mensahe, apan adunay espesyal nga kantidad nga Wala nga nagsenyas sa msg_sender coroutine nga mohunong sa pagtrabaho aron makalimtan ang bahin sa magsusulat niini nga adunay kalabotan sa kabilin nga koneksyon sa TCP.

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

Gidawat sa msg_sender ang mga outgoing messages (gipila gikan sa in socket), gi-serialize kini sa usa ka mensahe sa MsgText ug gipadala kini pinaagi sa koneksyon sa TCP. Mahimong maguba kini bisan unsang orasa - klaro namon nga gipugngan kini.

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

Sa katapusan, ang initiator mosulod sa usa ka walay katapusan nga loop sa pagbasa sa mga mensahe gikan sa socket. Susihon kung kini nga mga mensahe mga text message ug ibutang kini sa OUT_QUEUES nga pila, diin sila ipadala sa gawas nga socket sa katugbang nga interlocutor. Ngano nga dili nimo mahimo ang .read() ug i-decode ang mensahe? Tungod kay posible nga daghang mga mensahe gikan sa tiggamit ang matipon sa buffer sa operating system ug ipadala sa usa ka bahin sa TCP. Mahimo natong decode ang una, ug ang bahin sa sunod mahimong magpabilin sa buffer. Sa kaso sa bisan unsa nga dili normal nga sitwasyon, atong isira ang TCP nga koneksyon ug ihunong ang msg_sender coroutine (pinaagi sa pagpadala sa Wala sa OUT_QUEUES nga pila).

 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)

Balik ta sa main code. Human sa paghimo sa tanang coroutine sa panahon nga magsugod ang programa, atong sugdan ang TCP server. Alang sa matag natukod nga koneksyon, nagmugna kini usa ka coroutine sa pagtubag.

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

Ang responder susama sa initiator ug nagsalamin sa tanan nga parehas nga mga aksyon, apan ang walay katapusan nga loop sa pagbasa sa mga mensahe magsugod dayon, alang sa kayano. Sa pagkakaron, ang protocol sa handshake nagpadala usa ka mensahe gikan sa matag kilid, apan sa umaabot adunay duha gikan sa initiator sa koneksyon, pagkahuman ang mga text message mahimong ipadala dayon.

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

Secure nga protocol

Panahon na aron masiguro ang among komunikasyon. Unsa ang atong gipasabut sa seguridad ug unsa ang atong gusto:

  • kompidensyal sa gipasa nga mga mensahe;
  • pagkakasaligan ug integridad sa gipasa nga mga mensahe - ang ilang mga pagbag-o kinahanglan mahibal-an;
  • panalipod batok sa mga pag-atake sa replay - ang kamatuoran sa nawala o gibalikbalik nga mga mensahe kinahanglan mahibal-an (ug kami nakahukom nga tapuson ang koneksyon);
  • identification ug authentication sa mga interlocutors gamit ang pre-entered public keys - nakadesisyon na mi sa sayo pa nga naghimo mi ug friend-to-friend network. Human lamang sa pag-authentication atong masabtan kon kinsa ang atong gipakigsultihan;
  • pagkabaton hingpit nga sekreto sa unahan mga kabtangan (PFS) - ang pagkompromiso sa among dugay na nga pagpirma nga yawe kinahanglan dili mosangpot sa abilidad sa pagbasa sa tanan nga nangaging mga sulat. Ang pagrekord sa natanggong nga trapiko nahimong walay kapuslanan;
  • balido/balido sa mga mensahe (transportasyon ug handshake) sulod lang sa usa ka sesyon sa TCP. Ang pagsal-ot sa husto nga gipirmahan/authenticated nga mga mensahe gikan sa laing sesyon (bisan sa parehas nga interlocutor) dili mahimo;
  • ang usa ka passive observer kinahanglan nga dili makakita sa bisan hain sa user identifiers, transmitted long-lived public keys, o hash gikan kanila. Usa ka piho nga anonymity gikan sa usa ka passive observer.

Katingad-an, hapit tanan gusto nga adunay kini nga minimum sa bisan unsang protocol sa handshake, ug gamay ra sa mga nahisgutan sa katapusan ang nahimamat alang sa "homegrown" nga mga protocol. Karon dili na kami mag-imbento og bisan unsang bag-o. Ako siguradong morekomendar sa paggamit Balay sa kasaba para sa pagtukod og mga protocol, pero pilion ta og mas simple.

Ang duha ka labing popular nga mga protocol mao ang:

  • TLS - usa ka komplikado kaayo nga protocol nga adunay taas nga kasaysayan sa mga bug, jambs, vulnerabilities, dili maayo nga panghunahuna, pagkakomplikado ug mga kakulangan (bisan pa, kini wala’y kalabotan sa TLS 1.3). Apan dili nato kini tagdon tungod kay kini sobra ka komplikado.
  • IPsec с Ike β€” wala’y seryoso nga mga problema sa cryptographic, bisan kung dili usab kini yano. Kung nagbasa ka bahin sa IKEv1 ug IKEv2, nan ang gigikanan niini STS, ISO/IEC IS 9798-3 ug SIGMA (SIGn-and-MAc) nga mga protocol - igo ra nga ipatuman sa usa ka gabii.

Unsa ang maayo bahin sa SIGMA, ingon ang pinakabag-o nga link sa pagpauswag sa mga protocol sa STS / ISO? Nagtagbo kini sa tanan namong mga kinahanglanon (lakip ang "pagtago" sa mga identifier sa interlocutor) ug walay nahibal-an nga mga problema sa cryptographic. Kini minimalistic - ang pagtangtang sa labing menos usa ka elemento gikan sa mensahe sa protocol mosangpot sa pagkawalay kasiguruhan niini.

Gikan sa pinakasimple nga protocol sa panimalay ngadto sa SIGMA. Ang labing sukaranan nga operasyon nga interesado kami mao yawe nga kasabutan: Usa ka function nga nagpagawas sa duha ka partisipante sa samang bili, nga mahimong gamiton isip simetriko nga yawe. Sa walay pag-adto sa mga detalye: ang matag usa sa mga partido makamugna og usa ka ephemeral (gigamit lamang sulod sa usa ka sesyon) nga pares nga yawe (publiko ug pribado nga mga yawe), pagbayloay sa mga yawe sa publiko, pagtawag sa function sa kasabutan, ngadto sa input diin ilang gipasa ang ilang pribadong yawe ug ang publiko yawe sa interlocutor.

β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚PeerAβ”‚ β”‚PeerBβ”‚ β””β”€β”€β”¬β”€β”€β”˜ ── │─── β”‚ ╔══════════ ══════════╗ │───────────────>β”‚ ───────────────>β”‚ β•‘PrvA ─║─ DH, Pu ═ ════════ ═══════════╝ β”‚ IdB, PubB β”‚ ╔═══════════␐␕══␐␕══␐═══ β”‚<───────── ──────│ β•‘PrvB, PubB = DHgen()β•‘ β”‚ β”‚ β•šβ•β•β•β•β•β•β•β•β•β•β•β•ββ•β•β•ββ•β•β•ββ•β••β•ββ•β•β•ββ•β• ───┐ ╔════ ═══╧════════════╗ β”‚ β•‘Key = DH(PrvA, PubB)β•‘ <β”€β”€β”€β”ββ•šβ••β”ββ•šβ••β”β• ═══════ ════╝ β”‚ β”‚ β”‚ β”‚

Bisan kinsa mahimong moambak sa tunga ug pulihan ang mga yawe sa publiko sa ilang kaugalingon - wala’y panghimatuud sa mga interlocutors sa kini nga protocol. Magdugang ta og pirma nga adunay taas nga kinabuhi nga mga yawe.

β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚PeerAβ”‚ β”‚PeerBβ”‚ β””β”€β”€β”¬β”€β”€β”˜ ─── │┬── SignPrvA, (PubA)) β”‚ ╔═ │──────────── ────────── ───────────────────────── ubA = load()β•‘ β”‚ β”‚ β•‘PrvA, PubA = DHgen() β•‘ β”‚ β”‚ β•šβ•β•β•β•β•β•β• ═══════ ══════════ ═══════ ═══════␐═══════␐══ , timaan(SignPrvB, (PubB)) β”‚ ╔══════════════ ═══════ ══════════ ═══════ ══════‗ β”‚ ─────────── ───────────── ──│ β•‘SignPrvB, SignPubB = load( )β•‘ β”‚ β”‚ β•‘PrvB, PubB = DHβ•‘ β‘‚ () β”‚ ═════════ ══════════════ ══╝ ────┐ β•” ═════␐═══␐␕══␐══ ═════╗ β”‚ β”‚ β•‘verify( SignPubB, ...)β•‘ β”‚ <β”€β”€β”€β”˜ β•‘Key = DH(Pr vA, PubB) β•‘ β”‚ β”‚ β•šβ•β•β•β•β•β•β•β•β•ββ•β•β•ββ•β•β•ββ•β•β•β•ββ•β•β•β•ββ•β•β•β•ββ•β•β• ═╝ β”‚ β”‚ β”‚

Ang ingon nga pirma dili molihok, tungod kay wala kini gihigot sa usa ka piho nga sesyon. Ang ingon nga mga mensahe "angay" usab alang sa mga sesyon sa ubang mga partisipante. Ang tibuok konteksto kinahanglang mag-subscribe. Kini nagpugos kanato sa pagdugang usab og laing mensahe gikan sa A.

Dugang pa, hinungdanon nga idugang ang imong kaugalingon nga identifier sa ilawom sa pirma, tungod kay kung dili mahimo naton ilisan ang IdXXX ug pirmahan pag-usab ang mensahe gamit ang yawe sa lain nga nailhan nga interlocutor. Aron mapugngan mga pag-atake sa pagpamalandong, gikinahanglan nga ang mga elemento ubos sa pirma anaa sa tin-aw nga gihubit nga mga dapit sumala sa ilang kahulogan: kung A ang mga timailhan (PubA, PubB), nan ang B kinahanglan nga mopirma (PubB, PubA). Naghisgot usab kini sa kamahinungdanon sa pagpili sa istruktura ug format sa serialized data. Pananglitan, ang mga set sa ASN.1 DER encoding gisunod-sunod: SET OF(PubA, PubB) mahimong parehas sa SET OF(PubB, PubA).

β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚PeerAβ”‚ β”‚PeerBβ”‚ β””β”€β”€β”¬β”€β”€β”˜ ── │─── β”‚ ╔══════════ ═════════════════╗ │───────────—──——──— ────────── ─────────────>β”‚ β•‘SignPrvA, SignPubA = load()β•‘ β”‚ β”‚ β•‘PrvA, PubA = β‘‚ ␕ β•š ═ ═══════ ═══════════════╝ β”‚IdB, PubB, sign(SignPrvB, (IdB, PubA, PubB)) │═␐═╕╕ ═════ ════════════╗ β”‚<────────────────—───—─ ────────── ─────────│ β•‘SignPrvB, SignPubB = load()β•‘ β”‚ β”‚ β•‘PrvB, PubB = DHgen() β•‘ β”‚ β”β•β•šβ•β•β•š ═ ════════ ══════════╝ β”‚ ilhanan(SignPrvA, (IdA, PubB, PubA)) β”‚ ╔═══════␐═══␐═══␐══ ════╗ │─ ─────────────────────────────── ───—─ ───>β”‚ β•‘verify(SignPubB, ...) β•‘ β”‚ β”‚ β•‘key = dh (prva, PUBB) β•‘ β”‚ β”‚ β”‚

Bisan pa, wala gihapon kami "napamatud-an" nga nakamugna kami sa parehas nga gipaambit nga yawe alang sa kini nga sesyon. Sa prinsipyo, mahimo naton kung wala kini nga lakang - ang una nga koneksyon sa transportasyon mahimong dili balido, apan gusto namon nga kung mahuman ang paglamano, sigurado kami nga ang tanan nauyonan. Sa pagkakaron aduna kitay ISO/IEC IS 9798-3 nga protocol sa kamot.

Mahimo natong pirmahan ang namugna nga yawe mismo. Delikado kini, tungod kay posible nga adunay mga pagtulo sa pirma nga algorithm nga gigamit (bisan pa nga mga bit-per-pirma, apan nag-leak gihapon). Posible nga mopirma og hash sa derivation key, apan ang pag-leak bisan sa hash sa derivated key mahimong bililhon sa usa ka brute-force attack sa derivation function. Ang SIGMA naggamit ug MAC function nga nagpamatuod sa nagpadala ID.

β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚PeerAβ”‚ β”‚PeerBβ”‚ β””β”€β”€β”¬β”€β”€β”˜ ── │─── β”‚ ╔══════════ ═════════════════╗ │───────────—──——──— ────────── ──────────────────>β”‚ β•‘SignPrvA, SignPubA = load()β•‘ β”‚ β”‚ β•‘ β”‚ β”‚ β•‘PrvA, SignPubA = load()β•‘ β”‚ β”‚ β•‘PrvA β•š ═══════ ════════════════════╝ β”‚IdB, PubB, sign(SignPrvB, (PubA, PubB) ═(IdB) ═(IdB) ═ ═══ β”‚<───────────────── ──────────  ── ────────── ─│ β•‘SignPrvB, SignPubB = load()β•‘ β”‚ β”‚ β•‘PrvB, PubB = DHgen() β•‘ β”‚ β”‚ β•šβ•β•β•β• ══␐╕══␐␐╕═␐␐╕═␐␐╕═␐␐╕═␐␐╕═ ═════════ ══╝ β”‚ β”‚ ╔════════════ ═════════════════ ═════════╗ β”‚ sign, MAC(AP)B, Pu(AP)β”‚, Pu(AP)B, Pu(P) β•‘Yawe = DH( PrvA, PubB) β•‘ │───────────────────── ── ─ ─── ── ── ────────── ─────>β”‚ β•‘verify(Key, IdB) β•‘ β”‚ β”‚ β•‘verify(SignPubB, ...)β•‘ β”‚ β”‚ β•šβ•β•β•β•β•ββ•β•β•ββ•β•β•βββ•β••β•βββ••β•ββ•β•β••β•ββ•β•β•• ════ ═╝ β”‚ β”‚

Isip usa ka pag-optimize, ang uban tingali gusto nga gamiton pag-usab ang ilang mga ephemeral nga yawe (nga, siyempre, dili maayo alang sa PFS). Pananglitan, naghimo kami usa ka yawe nga pares, misulay sa pagkonektar, apan ang TCP wala magamit o nabalda bisan diin sa tunga-tunga sa protocol. Makauulaw ang pag-usik sa nausik nga entropy ug mga kapanguhaan sa processor sa usa ka bag-ong pares. Busa, among ipaila ang gitawag nga cookie - usa ka pseudo-random nga kantidad nga manalipod batok sa posible nga random replay nga mga pag-atake kung gamiton pag-usab ang ephemeral public key. Tungod sa pagbugkos tali sa cookie ug sa ephemeral nga yawe sa publiko, ang publiko nga yawe sa kaatbang nga partido mahimong tangtangon gikan sa pirma ingon nga wala kinahanglana.

β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚PeerAβ”‚ β”‚PeerBβ”‚ β””β”€β”€β”¬β”€β”€β”˜ ── │──, Pubookie Usa ka β”‚ ╔════════ ═══════════════════╗ │────═══════════╗ │─────────—──——──— ────────── ─────────────────────────────── ───—─ ─>β”‚ β•‘SignPrvA, SignPubA = load( )β•‘ β”‚ β”‚ β•‘PrvA, PubA = DHgen() β•‘ β”‚ β”‚ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•ββ•β•β•βββ••β•β•ββ•β••β•βββ•β• ══╝ β”‚IdB, PubB, CookieB , timaan(SignPrvB, (CookieA, CookieB, PubB)), MAC(IdB) β”‚ ╔═════════════════␐═══␐═══␐══ β•— β”‚< ─────────────────────────────── ───—─ ────────── ────────────────────│ β•‘SignPrvB, SignPubB = load()()β•‘ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β•šβ•β•β•β•β•β• ═════════════════════╝ β”‚ β”‚ ╔════␐═══␐═╕ ═══════╗ β”‚ timaan( SignPrvA, (CookieB, CookieA, PubA)), MAC(IdA) β”‚ β•‘Key = DH(PrvA, PubB) β•‘ │─────────── ─── ─── ── ── ─────────────────────────────── ───—─ ───────>β”‚ β•‘ verify(Key, IdB) β•‘ β”‚ β”‚ β•‘verify(SignPubB, ...)β•‘ β”‚ β”‚ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•ββ•β•β•ββ•β•β•ββ•β• β”‚

Sa katapusan, gusto namon nga makuha ang pribasiya sa among mga kauban sa pag-istoryahanay gikan sa usa ka passive observer. Aron mahimo kini, ang SIGMA nagsugyot nga una nga magbinayloay sa mga ephemeral nga yawe ug maghimo usa ka sagad nga yawe kung diin i-encrypt ang pag-authenticate ug pag-ila sa mga mensahe. Gihulagway sa SIGMA ang duha ka kapilian:

  • SIGMA-I - nanalipod sa initiator gikan sa aktibo nga mga pag-atake, ang responder gikan sa mga pasibo: ang initiator nagpamatuod sa responder ug kon adunay usa ka butang nga dili motakdo, nan kini dili paghatag sa iyang pag-ila. Ang akusado naghatag sa iyang identipikasyon kung adunay usa ka aktibong protocol nga gisugdan uban kaniya. Ang pasibo nga tigpaniid walay makat-onan;
    SIGMA-R - nanalipod sa responder gikan sa aktibo nga mga pag-atake, ang initiator gikan sa mga pasibo. Ang tanan sukwahi gyud, apan sa kini nga protocol upat ka mga mensahe sa handshake ang gipasa na.

    Gipili namo ang SIGMA-I tungod kay mas susama kini sa among gipaabot gikan sa pamilyar nga mga butang sa kliyente-server: ang kliyente giila lamang sa gipamatud-an nga server, ug ang tanan nakaila na sa server. Dugang pa nga kini mas sayon ​​nga ipatuman tungod sa mas diyutay nga mga mensahe sa handshake. Ang tanan nga among idugang sa protocol mao ang pag-encrypt sa bahin sa mensahe ug pagbalhin sa identifier A sa na-encrypt nga bahin sa katapusang mensahe:

    PubA, CookieA β”‚ ╔══════════ ═════════════════ ═══════════════‗ ══‗ ────────── ───── ────────── ────────────────────—─ ─────────── ───── ──────>β”‚ β•‘SignPrvA , SignPubA = load()β•‘ β”‚ β”‚ β•‘PrvA, PubA = DHgen() ␑ ════ ═════════ ═════════ ════╝ β”‚ PubB, CookieB, Enc((IdB, sign(SignPrvB, (CookieA, CookieB, PubB)), MAC(IdB) β•• ╔␐)β••) ═══════════════ ═══════╗ β”‚<──── ── ──— ─ ────────── ───── ────────── β•‘SignP rvB, SignPubB = load()β•‘ β”‚ β”‚ β•‘ PrvB, PubB = ═ DHgen() β•‘ ════════ ════════════════╝ β”‚ β”‚ ╔════════ ␐␕═══␐══ ══╗ β”‚ Enc((IdA, sign( SignpVA, (cookieb, cookiea, pua)), Mac (IDA))) β”‚ β•‘key = DH (PRVA, PURB) β•‘ │───────────────────── ────────────────── ────────── ──—─ ──—─ ─────────── ──────>β”‚ β•‘verify(Key, IdB) β•‘ β”‚ β”‚ β•‘verify( SignPubB, ...)β•‘ β”‚ β”‚ β•šβ•β•β•β•β•ββ•β••β•ββ•β••β•ββ•β•• ════ ══╝ β”‚ β”‚
    
    • GOST R gigamit alang sa pirma 34.10-2012 algorithm nga adunay 256-bit nga mga yawe.
    • Aron makamugna ang publikong yawe, gigamit ang 34.10/2012/XNUMX VKO.
    • Ang CMAC gigamit isip MAC. Sa teknikal, kini usa ka espesyal nga paagi sa operasyon sa block cipher, nga gihulagway sa GOST R 34.13-2015. Isip usa ka encryption function alang niini nga mode βˆ’ Balingkingpus (34.12-2015).
    • Ang hash sa iyang public key gigamit isip identifier sa interlocutor. Gigamit ingon usa ka hash Stribog-256 (34.11/2012/256 XNUMX ka gamay).

    Human sa handshake, magkasabot mi sa gipaambit nga yawe. Magamit namo kini alang sa authenticated encryption sa mga mensahe sa transportasyon. Kini nga bahin yano kaayo ug lisud nga masayop: gidugangan namon ang counter sa mensahe, gi-encrypt ang mensahe, gi-authenticate (MAC) ang counter ug ciphertext, ipadala. Kung makadawat usa ka mensahe, among susihon nga ang counter adunay gipaabut nga kantidad, pamatud-an ang ciphertext gamit ang counter, ug i-decrypt kini. Unsa nga yawe ang akong gamiton sa pag-encrypt sa mga mensahe sa handshake, pagdala sa mga mensahe, ug unsaon pag-authenticate niini? Ang paggamit sa usa ka yawe alang sa tanan niini nga mga buluhaton peligroso ug dili maalamon. Gikinahanglan ang pagmugna og mga yawe gamit ang espesyal nga mga gimbuhaton Ang KDF (key nga derivation function). Pag-usab, dili ta magbahin sa buhok ug mag-imbento og usa ka butang: HKDF dugay na nga nahibal-an, maayo nga gisiksik ug walay nahibal-an nga mga problema. Ikasubo, ang lumad nga librarya sa Python wala niini nga function, mao nga among gigamit hkdf plastik nga bag. Ang HKDF sa sulod gigamit Ang HMAC, nga sa baylo naggamit ug hash function. Usa ka pananglitan nga pagpatuman sa Python sa panid sa Wikipedia nagkinahanglan lang og pipila ka linya sa code. Sama sa kaso sa 34.10/2012/256, atong gamiton ang Stribog-XNUMX isip hash function. Ang output sa among key agreement function tawgon nga session key, diin ang mga nawala nga simetriko mamugna:

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

    Mga Istruktura/Skema

    Atong tan-awon kung unsa nga mga istruktura sa ASN.1 ang naa kanato karon alang sa pagpadala niining tanan nga datos:

    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 ang pirmahan. HandshakeTBE - unsa ang ma-encrypt. Gidani nako ang imong atensyon sa natad sa ukm sa MsgHandshake1. 34.10 VKO, alang sa mas dako nga randomization sa namugna nga mga yawe, naglakip sa UKM (user keying material) parameter - dugang nga entropy.

    Pagdugang sa Cryptography sa Code

    Atong tagdon lamang ang mga pagbag-o nga gihimo sa orihinal nga code, tungod kay ang gambalay nagpabilin nga pareho (sa pagkatinuod, ang katapusan nga pagpatuman gisulat una, ug unya ang tanan nga cryptography giputol gikan niini).

    Tungod kay ang panghimatuud ug pag-ila sa mga interlocutor himuon gamit ang mga yawe sa publiko, kinahanglan na sila nga itago sa usa ka lugar sa dugay nga panahon. Alang sa kayano, gigamit namo ang JSON nga sama niini:

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

    among - among key pares, hexadecimal pribado ug publikong yawe. ilang β€” mga ngalan sa mga interlocutors ug ilang mga yawe sa publiko. Atong usbon ang mga argumento sa command line ug idugang ang post-processing sa JSON data:

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

    Ang pribadong yawe sa 34.10 algorithm usa ka random nga numero. 256-bit nga gidak-on alang sa 256-bit elliptic curves. Ang PyGOST wala magtrabaho sa usa ka set sa bytes, apan uban sa dako nga numero, mao nga ang atong pribadong yawe (urandom(32)) kinahanglang i-convert sa numero gamit ang gost3410.prv_unmarshal(). Ang publiko nga yawe determinado nga deterministiko gikan sa pribadong yawe gamit ang gost3410.public_key(). Ang public key 34.10 kay duha ka dagkong numero nga kinahanglan usab nga i-convert ngadto sa byte sequence para sa kasayon ​​sa pagtipig ug transmission gamit ang gost3410.pub_marshal().

    Human mabasa ang JSON file, ang publiko nga mga yawe sumala niana kinahanglan nga mabag-o balik gamit ang gost3410.pub_unmarshal(). Tungod kay makadawat kami sa mga identifier sa mga interlocutors sa porma sa usa ka hash gikan sa publiko nga yawe, kini mahimo dayon nga kalkulado nga daan ug ibutang sa usa ka diksyonaryo alang sa dali nga pagpangita. Ang Stribog-256 hash mao ang gost34112012256.GOST34112012256(), nga hingpit nga nagtagbaw sa hashlib interface sa hash functions.

    Sa unsang paagi nausab ang initiator coroutine? Ang tanan sumala sa laraw sa paglamano: naghimo kami usa ka cookie (daghan ang 128-bit), usa ka ephemeral key pair 34.10, nga gamiton alang sa function sa VKO key agreement.

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

    • maghulat kami sa tubag ug mag-decode sa umaabot nga mensahe sa Msg;
    • siguroha nga makakuha ka ug handshake1;
    • decode ang ephemeral public key sa kaatbang nga partido ug kuwentaha ang session key;
    • Naghimo kami og simetriko nga mga yawe nga gikinahanglan alang sa pagproseso sa TBE nga bahin sa mensahe.

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

    Ang UKM usa ka 64-bit nga numero (urandom(8)), nga nagkinahanglan usab og deserialization gikan sa byte nga representasyon niini gamit ang gost3410_vko.ukm_unmarshal(). Ang VKO function alang sa 34.10/2012/256 3410-bit mao ang gost34102012256_vko.kek_XNUMX() (KEK - encryption key).

    Ang namugna nga yawe sa sesyon kay usa na ka 256-bit pseudo-random byte sequence. Busa, kini magamit dayon sa mga gimbuhaton sa HKDF. Tungod kay ang GOST34112012256 nakatagbaw sa hashlib interface, mahimo kini dayon nga gamiton sa Hkdf nga klase. Wala namo ipiho ang asin (ang unang argumento sa Hkdf), tungod kay ang namugna nga yawe, tungod sa ephemerality sa mga nag-apil nga mga pares nga yawe, magkalahi alang sa matag sesyon ug adunay igo nga entropy. kdf.expand() pinaagi sa default naghimo na sa 256-bit nga mga yawe nga gikinahanglan alang sa Grasshopper sa ulahi.

    Sunod, ang TBE ug TBS nga mga bahin sa umaabot nga mensahe gisusi:

    • ang MAC sa umaabot nga ciphertext gikalkula ug gisusi;
    • ang ciphertext gi-decrypted;
    • Ang istruktura sa TBE gi-decode;
    • ang identifier sa interlocutor gikuha gikan niini ug kini gisusi kon siya nakaila kanato sa tanan;
    • MAC sa ibabaw niini nga identifier kalkulado ug gisusi;
    • ang pirma sa TBS nga istruktura gipamatud-an, nga naglakip sa cookie sa duha ka partido ug ang publiko nga ephemeral nga yawe sa kaatbang nga partido. Ang pirma gipamatud-an sa dugay na nga pirma nga yawe sa interlocutor.

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

    Sama sa akong gisulat sa ibabaw, 34.13/2015/XNUMX naghulagway sa lain-laing pag-block sa mga mode sa operasyon sa cipher gikan sa 34.12/2015/3413. Lakip kanila adunay usa ka mode alang sa pagmugna imitasyon pagsal-ot ug MAC kalkulasyon. Sa PyGOST kini mao ang gost34.12.mac(). Kini nga mode nagkinahanglan sa pagpasa sa encryption function (pagdawat ug pagbalik sa usa ka block sa data), ang gidak-on sa encryption block ug, sa pagkatinuod, ang data mismo. Nganong dili nimo ma-hardcode ang gidak-on sa encryption block? 2015/128/64 naghulagway dili lamang sa XNUMX-bit Grasshopper cipher, apan usab sa XNUMX-bit Magma - usa ka gamay nga giusab GOST 28147-89, gimugna balik sa KGB ug sa gihapon adunay usa sa mga labing taas nga safety thresholds.

    Ang Kuznechik gisugdan pinaagi sa pagtawag sa gost.3412.GOST3412Kuznechik(key) ug gibalik ang usa ka butang nga adunay .encrypt()/.decrypt() nga mga pamaagi nga angay para sa pagpasa sa 34.13 functions. Ang MAC kalkulado sama sa mosunod: gost3413.mac(GOST3412Kuznechik(key).encrypt, KUZNECHIK_BLOCKSIZE, ciphertext). Aron itandi ang kalkulado ug nadawat nga MAC, dili nimo magamit ang naandan nga pagtandi (==) sa mga kuwerdas sa byte, tungod kay kini nga operasyon nag-leak sa oras sa pagtandi, nga, sa kinatibuk-ang kaso, mahimong mosangpot sa makamatay nga mga kahuyangan sama sa MANANAP pag-atake sa TLS. Ang Python adunay espesyal nga function, hmac.compare_digest, alang niini.

    Ang block cipher function mahimo ra maka-encrypt sa usa ka block sa data. Alang sa mas dako nga gidaghanon, ug bisan dili usa ka multiple sa gitas-on, gikinahanglan nga gamiton ang encryption mode. 34.13-2015 naghulagway sa mosunod: ECB, CTR, OFB, CBC, CFB. Ang matag usa adunay kaugalingon nga madawat nga mga lugar sa aplikasyon ug mga kinaiya. Ikasubo, wala gihapon mi na-standardize gipamatud-an nga mga mode sa pag-encrypt (sama sa CCM, OCB, GCM ug uban pa) - napugos kami sa pagdugang sa MAC sa among kaugalingon. pilion ko kontra mode (CTR): wala kini magkinahanglan og padding sa block nga gidak-on, mahimong parallelize, naggamit lamang sa encryption function, mahimong luwas nga gamiton sa pag-encrypt sa daghang mga mensahe (dili sama sa CBC, nga adunay mga bangga nga medyo dali).

    Sama sa .mac(), .ctr() nagkuha ug susamang input: ciphertext = gost3413.ctr(GOST3412Kuznechik(key).encrypt, KUZNECHIK_BLOCKSIZE, plaintext, iv). Gikinahanglan nga ipiho ang usa ka inisyal nga vector nga eksaktong katunga sa gitas-on sa block sa pag-encrypt. Kung ang among encryption nga yawe gigamit lamang sa pag-encrypt sa usa ka mensahe (bisan gikan sa daghang mga bloke), nan luwas nga magbutang ug zero initialization vector. Aron ma-encrypt ang mga mensahe sa handshake, mogamit kami usa ka bulag nga yawe matag higayon.

    Ang pag-verify sa pirma nga gost3410.verify() kay trivial: gipasa namo ang elliptic curve diin kami nagtrabaho (girekord lang namo kini sa among GOSTIM protocol), ang public key sa nagpirma (ayaw kalimti nga kini kinahanglan nga usa ka tuple sa duha dako nga numero, ug dili usa ka byte string), 34.11/2012/XNUMX hash ug ang pirma mismo.

    Sunod, sa initiator nag-andam kami ug nagpadala usa ka mensahe sa handshake sa handshake2, nga gihimo ang parehas nga mga aksyon sama sa among gibuhat sa panahon sa pag-verify, simetrikal lang: pagpirma sa among mga yawe imbis nga susihon, ug uban pa...

     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)
     

    Kung natukod ang sesyon, ang mga yawe sa transportasyon namugna (usa ka lahi nga yawe alang sa pag-encrypt, alang sa pag-authenticate, alang sa matag usa sa mga partido), ug ang Grasshopper gisugdan aron ma-decrypt ug susihon ang 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     # }}}
    

    Ang msg_sender coroutine karon nag-encrypt sa mga mensahe sa dili pa ipadala kini sa koneksyon sa TCP. Ang matag mensahe adunay monotonically increase nonce, nga mao usab ang initialization vector kung ma-encrypt sa counter mode. Ang matag mensahe ug bloke sa mensahe gigarantiyahan nga adunay lahi nga kantidad sa counter.

    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
    

    Ang umaabot nga mga mensahe giproseso sa msg_receiver coroutine, nga nagdumala sa authentication ug decryption:

    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)
    

    konklusyon

    Ang GOSTIM gituyo nga gamiton lamang alang sa mga katuyoan sa edukasyon (tungod kay dili kini sakop sa mga pagsulay, labing menos)! Ang source code sa programa mahimong ma-download dinhi (Π‘Ρ‚Ρ€ΠΈΠ±ΠΎΠ³-256 Ρ…ΡΡˆ: 995bbd368c04e50a481d138c5fa2e43ec7c89bc77743ba8dbabee1fde45de120). Как ΠΈ всС ΠΌΠΎΠΈ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Ρ‹, Ρ‚ΠΈΠΏΠ° GoGOST, PyDERASN, NCCP, GoVPN, GOSTIM mao ang bug-os libre nga softwaregiapod-apod ubos sa mga termino GPLv3 +.

    Sergey Matveev, cypherpunk, membro SPO Foundation, Python/Go-developer, punoan nga espesyalista Federal State Unitary Enterprise "STC "Atlas".

Source: www.habr.com

Idugang sa usa ka comment