GOSTIM: P2P F2F E2EE IM nan yon aswè ak kriptografi GOST

Pou ou kab vin yon pwomotè PyGOST bibliyotèk (GOST primitif kriptografik nan pi Python), mwen souvan resevwa kesyon sou fason pou aplike mesaj ki pi senp an sekirite sou jenou an. Anpil moun konsidere kriptografi aplike yo dwe byen senp, epi rele .encrypt() sou yon chifreman blòk pral ase yo voye li an sekirite sou yon kanal kominikasyon. Gen lòt ki kwè ke kriptografi aplike se desten kèk moun, e li akseptab ke konpayi rich tankou Telegram ak olympiad-matematisyen. pa ka aplike pwotokòl sekirite.

Tout bagay sa a te pouse m 'ekri atik sa a pou montre ke mete ann aplikasyon pwotokòl kriptografik ak IM an sekirite se pa yon travay difisil. Sepandan, li pa vo envante pwòp otantifikasyon ou ak pwotokòl akò kle yo.

GOSTIM: P2P F2F E2EE IM nan yon aswè ak kriptografi GOST
Atik la pral ekri kanmarad-a-kanmarad, zanmi-a-zanmi, bout-a-fen chiffres instant messenger ak SIGMA-I otantifikasyon ak pwotokòl akò kle (sou baz li aplike IPsec IKE), lè l sèvi avèk sèlman GOST algoritm kriptografik bibliyotèk PyGOST ak bibliyotèk kodaj mesaj ASN.1. PyDERASN (sou sa mwen deja ekri avan). Yon avantou: li dwe tèlman senp ke li ka ekri nan grafouyen nan yon aswè (oswa jou travay), otreman li pa yon pwogram senp ankò. Li pwobableman gen erè, konplikasyon nesesè, enpèfeksyon, plis sa a se premye pwogram mwen an lè l sèvi avèk bibliyotèk asyncio la.

IM konsepsyon

Premyèman, nou bezwen konprann ki sa IM nou an pral sanble. Pou senplisite, se pou li yon rezo kanmarad-a-kanmarad, san okenn dekouvèt nan patisipan yo. Nou pral pèsonèlman endike ki adrès: pò pou konekte ak kominike ak entèrlokuteur a.

Mwen konprann ke, nan moman sa a, sipozisyon ke kominikasyon dirèk disponib ant de òdinatè abitrè se yon limit enpòtan sou aplikasyon IM nan pratik. Men, plis devlopè yo aplike tout kalite beki NAT-travèsal, se plis n ap rete sou Entènèt IPv4 la, ak yon pwobabilite depresyon pou kominikasyon ant òdinatè abitrè. Konbyen tan ou ka tolere mank IPv6 nan kay la ak nan travay?

Nou pral gen yon rezo zanmi-a-zanmi: tout entèrlokuteur posib yo dwe konnen davans. Premyèman, sa a anpil senplifye tout bagay: nou prezante tèt nou, jwenn oswa pa jwenn non / kle a, dekonekte oswa kontinye travay, konnen entèrlokuteur la. Dezyèmman, an jeneral, li an sekirite epi li elimine anpil atak.

Koòdone IM la pral fèmen nan solisyon klasik pwojè san tete, ki mwen vrèman renmen pou minimalist yo ak filozofi Unix-fason. Pwogram IM kreye yon anyè ak twa sipò domèn Unix pou chak entèrlokuteur:

  • nan—mesaj yo voye bay entèrlokuteur a anrejistre ladan l;
  • soti - mesaj yo resevwa nan men entèrlokuteur a li nan li;
  • eta - pa li nan li, nou chèche konnen si entèrlokuteur a kounye a konekte, adrès la koneksyon / pò.

Anplis de sa, yon priz koneksyon kreye, lè w ekri pò a lame nan ki nou kòmanse yon koneksyon ak entèrlokuteur a aleka.

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

Apwòch sa a pèmèt ou fè aplikasyon endepandan nan transpò IM ak koòdone itilizatè, paske pa gen okenn zanmi, ou pa ka tanpri tout moun. Sèvi ak tmux ak / oswa multi, ou ka jwenn yon koòdone milti-fenèt ak sentaks en. Ak èd la rlwrap ou ka jwenn yon liy antre mesaj GNU Readline ki konpatib.

An reyalite, pwojè suckless itilize FIFO dosye. Pèsonèlman, mwen pa t 'kapab konprann ki jan yo travay ak dosye konpetitif nan asyncio san yon background ekri men nan fil dedye (mwen te itilize lang lan pou bagay sa yo pou yon tan long. Go). Se poutèt sa, mwen deside fè fè ak sipò domèn Unix. Malerezman, sa fè li enposib fè echo 2001:470:dead::babe 6666 > konn. Mwen rezoud pwoblèm sa a lè l sèvi avèk socat: echo 2001:470:mouri::babe 6666 | socat - UNIX-CONNECT:conn, socat READLINE UNIX-CONNECT:alice/in.

Pwotokòl orijinal la ensekirite

TCP yo itilize kòm transpò: li garanti livrezon ak lòd li yo. UDP garanti ni (ki ta itil lè yo itilize kriptografik), men sipò SCTP Python pa soti nan bwat la.

Malerezman, nan TCP pa gen okenn konsèp nan yon mesaj, se sèlman yon kouran nan bytes. Se poutèt sa, li nesesè yo vini ak yon fòma pou mesaj pou yo ka pataje nan mitan tèt yo nan fil sa a. Nou ka dakò pou sèvi ak karaktè liy manje a. Li bon pou kòmanse, men yon fwa nou kòmanse kode mesaj nou yo, karaktè sa a ka parèt nenpòt kote nan chifreman an. Nan rezo, Se poutèt sa, pwotokòl popilè yo se moun ki premye voye longè mesaj la an byte. Pou egzanp, soti nan bwat la Python gen xdrlib, ki pèmèt ou travay ak yon fòma menm jan an XDR.

Nou pa pral travay kòrèkteman ak efikasite ak lekti TCP - nou pral senplifye kòd la. Nou li done ki soti nan priz la nan yon bouk kontinuèl jiskaske nou dekode mesaj la konplè. JSON ak XML ka itilize tou kòm yon fòma pou apwòch sa a. Men, lè yo ajoute kriptografik, done yo pral dwe siyen ak otantifye - e sa pral mande pou yon byte-pou-byte reprezantasyon ki idantik nan objè, ki JSON/XML pa bay (rezilta pil fatra yo ka varye).

XDR se apwopriye pou travay sa a, sepandan mwen chwazi ASN.1 ak DER kodaj ak PyDERASN bibliyotèk, depi nou pral gen objè wo nivo nan men ak ki souvan li se pi bèl ak pratik nan travay. Kontrèman ak schemaless benkode, MessagePack oswa CBOR, ASN.1 pral otomatikman tcheke done yo kont yon chema difisil-kode.

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

Mesaj resevwa a pral Msg: swa yon tèks MsgText (ak yon sèl jaden tèks pou kounye a) oswa yon mesaj MsgHandshake handshake (ki gen non entèrlokuteur a). Koulye a, li sanble twò konplike, men sa a se yon fondasyon pou lavni an.

     ┌─────┐ ┌─────┐ │PeerA│ │PeerB│ └──┬──┘ └───┐ │PeerA│ │PeerB│ └──┬──┘ └───└───── │ │──────── ─ ────────>│ │ │ │MsgHandshake(IdB) │ │<─────────────────────── │ │ MsgText() │ │─── ─ MsgText() │ │ │

IM san kriptografik

Kòm mwen te deja di, yo pral itilize bibliyotèk asyncio pou tout operasyon socket. Ann anonse sa nou espere nan lansman:

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

Mete pwòp non ou (--non-nou alice). Tout entèrlokuteur espere yo nan lis separe pa vigil (—non-yo bob,eve). Pou chak entèrlokuteur yo, yo kreye yon anyè ak sipò Unix, ansanm ak yon koroutin pou chak nan, soti, eta:

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

Mesaj ki soti nan itilizatè a soti nan priz la yo voye nan keu IN_QUEUES la:

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

Mesaj ki soti nan entèrlokuteur yo voye nan OUT_QUEUES ke moun kap kriye, ki soti nan ki done yo ekri nan priz la soti:

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

Lè li nan yon priz leta, pwogram nan chèche adrès entèrlokuteur a nan diksyonè PEER_ALIVE la. Si pa gen okenn koneksyon ak entèrlokuteur a ankò, Lè sa a, yon liy vid ekri.

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

Lè w ap ekri yon adrès nan yon priz koneksyon, fonksyon "inisyatè" koneksyon an lanse:

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

Ann konsidere inisyatè a. Premyèman, li evidamman ouvè yon koneksyon ak lame / pò a espesifye epi voye yon mesaj lanmen ak non li:

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

Lè sa a, li tann pou yon repons soti nan pati a aleka. Eseye dekode repons fèk ap rantre lè l sèvi avèk konplo Msg ASN.1 la. Nou sipoze ke tout mesaj la pral voye nan yon sèl segman TCP epi nou pral resevwa li atomik lè w ap rele .read(). Nou tcheke si nou te resevwa mesaj lanmen an.

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

Nou tcheke si non entèlokutè a te resevwa a konnen nou. Si ou pa, Lè sa a, nou kraze koneksyon an. Nou tcheke si nou te deja etabli yon koneksyon avèk li (entèrlokuteur a ankò te bay lòd la konekte ak nou) epi fèmen li. Keu IN_QUEUES la kenbe kòd Python ak tèks mesaj la, men li gen yon valè espesyal None ki siyal koroutin msg_sender la sispann travay pou li bliye ekriven li ki asosye ak koneksyon TCP eritaj la.

 159     msg_handshake = msg.value
 160     peer_name = str(msg_handshake["peerName"])
 161     if peer_name not in THEIR_NAMES:
 162         logging.warning("unknown peer name: %s", peer_name)
 163         writer.close()
 164         return
 165     logging.info("%s: session established: %s", _id, peer_name)
 166     # Run text message sender, initialize transport decoder {{{
 167     peer_alive = PEER_ALIVES.pop(peer_name, None)
 168     if peer_alive is not None:
 169         peer_alive.close()
 170         await IN_QUEUES[peer_name].put(None)
 171     PEER_ALIVES[peer_name] = writer
 172     asyncio.ensure_future(msg_sender(peer_name, writer))
 173     # }}}

msg_sender aksepte mesaj sortan (ki nan keu nan yon priz nan), seri yo nan yon mesaj MsgText epi voye yo sou yon koneksyon TCP. Li ka kraze nan nenpòt ki moman - nou klèman entèsepte sa a.

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

Nan fen a, inisyatè a antre nan yon bouk enfini nan lekti mesaj ki soti nan priz la. Tcheke si mesaj sa yo se mesaj tèks epi mete yo nan keu OUT_QUEUES, kote yo pral voye yo nan priz la soti nan entèrlokuteur ki koresponn lan. Poukisa ou pa ka jis fè .read() ak dekode mesaj la? Paske li posib ke plizyè mesaj ki soti nan itilizatè a pral rasanble nan tanpon sistèm operasyon an epi voye nan yon sèl segman TCP. Nou ka dekode youn nan premye, ak Lè sa a, yon pati nan youn nan ki vin apre a ka rete nan tanpon an. Nan nenpòt sitiyasyon nòmal, nou fèmen koneksyon TCP a epi sispann koroutin msg_sender la (pa voye None nan keu 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)

Ann retounen nan kòd prensipal la. Apre ou fin kreye tout koroutin yo nan moman pwogram nan kòmanse, nou kòmanse sèvè TCP la. Pou chak koneksyon etabli, li kreye yon koroutin sekouris.

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

sekouris se menm jan ak inisyatè ak miwa tout aksyon yo menm, men bouk la enfini nan lekti mesaj kòmanse imedyatman, pou senplisite. Kounye a, pwotokòl lanmen voye yon mesaj soti nan chak bò, men nan lavni an pral gen de soti nan inisyatè koneksyon an, apre sa yo ka voye mesaj tèks imedyatman.

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

Pwotokòl sekirite

Li lè pou sekirize kominikasyon nou yo. Ki sa nou vle di pa sekirite ak sa nou vle:

  • konfidansyalite mesaj transmèt;
  • otantisite ak entegrite nan mesaj transmèt - chanjman yo dwe detekte;
  • pwoteksyon kont atak replay - yo dwe detekte lefèt nan mesaj ki manke oswa repete (epi nou deside mete fen nan koneksyon an);
  • idantifikasyon ak otantifikasyon entèrlokuteur lè l sèvi avèk kle piblik pre-antre - nou deja deside pi bonè ke nou te fè yon rezo zanmi-a-zanmi. Se sèlman apre otantifikasyon nou pral konprann ki moun nou ap kominike ak;
  • disponiblite pafè sekrè pou pi devan pwopriyete (PFS) - konpwomèt kle siyen ki dire lontan nou an pa ta dwe mennen nan kapasite nan li tout korespondans anvan yo. Anrejistreman trafik entèsepte vin initil;
  • validite/validite mesaj (transpò ak lanmen) sèlman nan yon sesyon TCP. Mete mesaj kòrèkteman siyen/otantifye ki soti nan yon lòt sesyon (menm avèk menm entèrlokuteur a) pa ta dwe posib;
  • yon obsèvatè pasif pa ta dwe wè ni idantifyan itilizatè yo, ni kle piblik ki dire lontan ki te transmèt, ni hache nan men yo. Yon sèten anonimite soti nan yon obsèvatè pasif.

Surprenante, prèske tout moun vle gen minimòm sa a nan nenpòt pwotokòl lanmen, epi anpil ti kras nan pi wo a finalman satisfè pou pwotokòl "homegrown". Koulye a, nou pa pral envante anyen nouvo. Mwen ta definitivman rekòmande pou itilize Fondasyon bri pou bati pwotokòl, men ann chwazi yon bagay ki pi senp.

De pwotokòl ki pi popilè yo se:

  • tl - yon pwotokòl trè konplèks ak yon istwa long nan pinèz, jambs, vilnerabilite, move panse, konpleksite ak enpèfeksyon (sepandan, sa a gen ti kras fè ak TLS 1.3). Men, nou pa konsidere li paske li twò konplike.
  • IPsec с Ike — pa gen pwoblèm kriptografik grav, byenke yo tou pa senp. Si ou li sou IKEv1 ak IKEv2, Lè sa a, sous yo se STS, ISO/IEC IS 9798-3 ak SIGMA (SIGn-and-MAc) pwotokòl - ase senp pou aplike nan yon aswè.

Ki sa ki bon sou SIGMA, kòm dènye lyen nan devlopman nan pwotokòl STS / ISO? Li satisfè tout kondisyon nou yo (ki gen ladan "kache" idantifyan entèrlokuteur) epi li pa gen okenn pwoblèm kriptografik li te ye. Li se minimalist - retire omwen yon eleman nan mesaj pwotokòl la ap mennen nan ensekirite li yo.

Ann ale soti nan pwotokòl ki pi senp nan kay la nan SIGMA. Operasyon ki pi fondamantal nou enterese nan se akò kle: Yon fonksyon ki bay tou de patisipan yo menm valè, ki ka itilize kòm yon kle simetrik. San yo pa antre nan detay: chak nan pati yo jenere yon pè kle efemèr (itilize sèlman nan yon sèl sesyon) (kle piblik ak prive), echanj kle piblik, rele fonksyon akò a, nan opinyon nan ki yo pase kle prive yo ak piblik la. kle entèrlokuteur a.

┌─────┐ ┌─────┐ │PeerA│ │PeerB│ └──┬──┘ └───┐────────── ╔══════════ ══════════╗ │───────────────>│ ║PrvA, Pub───────────── ════════ ═══════════╝ │ IdB, PubB │ ╔═════════════════════════════════ ───────── ──────│ ║PrvB, PubB = DHgen()║ │ │ ╚═════════════════════════════════ ─ ──┐ ╔════ ═══╧════════════╗ │ ║Kle = DH(PrvA, PubB)║ <───┘════════════ ═══════ ════╝ │ │ │ │

Nenpòt moun ka sote nan mitan an epi ranplase kle piblik ak pwòp yo - pa gen okenn otantifikasyon nan entèrlokuteur nan pwotokòl sa a. Ann ajoute yon siyati ak kle ki dire lontan.

┌─────┐ ┌─────┐ │PeerA│ │PeerB│ └──┬──┘ └───┐ │PeerA│ │PeerB│ └──┬──┘ └──┘ └────────── rvA, (PubA)) │ ╔═ │──────────── ────────── ─────────────────── ────────────────>P───── = chaj ()║ │ │ ║PrvA, PubA = DHgen() ║ │ │ ╚════════ ══════ ════════════════B,═B,═B,═B═ (SiyenPrvB, (PubB)) │ ╔═══════════════ ══════ ══════════════╔───────╔╔ ────────── ────────────── ─│ ║SignPrvB, SignPubB = chaj( )║ │ │ ║PrvB, PubB = ║ ┐ │ │ DH = │ │ │ │ ════════ ═══════════════ ═╝ ────┐ ╔ ═════════════════════ ═════╗ │ │ ║verify( SignPubB, ...)║ │ <───┘ ║Kle = DH(PrvA , PubB) ║ │ │ ╚════════════════════════════ ═╝ │ │ │

Yon siyati sa a pa pral travay, paske li pa mare nan yon sesyon espesifik. Mesaj sa yo tou "apwopriye" pou sesyon ak lòt patisipan yo. Tout kontèks la dwe abònman. Sa a fòse nou ajoute yon lòt mesaj nan men A.

Anplis de sa, li enpòtan pou ajoute pwòp idantifyan ou anba siyati a, paske otreman nou ka ranplase IdXXX epi re-siyen mesaj la ak kle yon lòt entèrlokuteur li te ye. Pou anpeche atak refleksyon, li nesesè ke eleman ki anba siyati a yo nan kote ki byen defini dapre siyifikasyon yo: si A siy (PubA, PubB), Lè sa a, B dwe siyen (PubB, PubA). Sa a tou pale sou enpòtans ki genyen nan chwazi estrikti a ak fòma nan done seri. Pa egzanp, ansanm nan kodaj ASN.1 DER yo klase: SET OF(PubA, PubB) pral idantik ak SET OF(PubB, PubA).

┌─────┐ ┌─────┐ │PeerA│ │PeerB│ └──┬──┘ └────────────── ╔══════════ ═════════════════╗ │─────────────────────────── ────────── ─────────────>│ ║SignPrvA, SignPubA = chaj()║ │ │ ║PrvA, PubA = DHgen() ════════════ ═ ══════ ═══════════════╝ │IdB, PubB, siy(SignPrvB, (IdB, PubA, PubB)) │ ╔═══════════ ═════ ════════════╗ │<────────────────────────────── ───────── ─────────│ ║SignPrvB, SignPubB = chaj()║ │ │ ║PrvB, PubB = DHgen() ║ │ │ ║ │ │ ══════════════ ═ ═══════ ══════════╝ │ siy(SignPrvA, (IdA, PubB, PubA)) │ ╔═══════════════════════ ═══╗ │─ ──────────────────────────────────────────── ──>│ ║verify(SignPubB, ...)║ │ │ ║Kle = DH(PrvA, PubB) ║ │ │ ╚═══════════════════════════════

Sepandan, nou toujou pa "pwouve" ke nou te pwodwi menm kle pataje pou sesyon sa a. Nan prensip, nou ka fè san etap sa a - premye koneksyon transpò a pral envalid, men nou vle ke lè lanmen an fini, nou ta asire w ke tout bagay vrèman dakò sou. Nan moman sa a nou gen pwotokòl ISO/IEC IS 9798-3 nan men.

Nou ta ka siyen kle a pwodwi tèt li. Sa a se danjere, paske li posib ke ka gen fwit nan algorithm siyati yo itilize (kwake Bits-pa-siyati, men yo toujou ap koule). Li posib pou siyen yon hash nan kle derivasyon an, men koule menm hash nan kle derive a ka gen anpil valè nan yon atak fòs brital sou fonksyon an derivasyon. SIGMA sèvi ak yon fonksyon MAC ki otantifye ID moun k ap voye a.

┌─────┐ ┌─────┐ │PeerA│ │PeerB│ └──┬──┘ └────────────── ╔══════════ ═════════════════╗ │─────────────────────────── ────────── ─────────────────>│ ║SignPrvA, SignPubA = load()║ │ │ ─────│ ║SignPrvA, SignPubA = load()║ │ │ ║ │ │ ║ │ │ ║PrvA, │ DH =ₕ │ DH ═ ══════ ════════════════════╝ │IdB, PubB, sign(SignPrvB, (PubA, PubB═)), MAC═│══════ ═══ ════════════════════╗ │<───────────────────── ──────── ─ ─────────────────────│ ║SignPrvB, SignPubB = load()║ ────────│ ║SignPrvB, SignPubB = load()║ ────│ │ DH = │ DH │ │ │ │ │ │ DH │ ╚════ ═ ══════════════════════╝ │ │ ╔═════════════════ ════════╗ │ siy (SiyPrvA, (PubB, PubA)), MAC(IdA) │ ║Kle = DH(PrvA, PubB) ║ │──────────────────────────── ─ ─ ─────────────────────────>│ ║verify(Kle, IdB)(Kle, IdB)(Synveye)──────────>│ ║verify(Key, IdB─)(Signify) ║ │ │ ╚ ═════════════════════╝ │ │

Kòm yon optimize, kèk ka vle reitilize kle efemèr yo (ki se, nan kou, malere pou PFS). Pou egzanp, nou te pwodwi yon pè kle, yo te eseye konekte, men TCP pa t 'disponib oswa yo te entèwonp yon kote nan mitan an nan pwotokòl la. Li se yon wont gaspiye entropi ak resous processeur gaspiye sou yon nouvo pè. Se poutèt sa, nou pral entwodui sa yo rele bonbon an - yon valè pseudo-o aza ki pral pwoteje kont posib atak reparèt o aza lè w ap reitilize kle piblik efemèr. Akòz obligatwa ki genyen ant bonbon an ak kle piblik la efemèr, kle piblik la nan pati opoze a ka retire nan siyati a kòm nesesè.

┌─────┐ ┌─────┐ │PeerA│ │PeerB│ └──┬──┘ └───┐ │PeerA│ │PeerB│ └──┬──┘ └──┘ └─────────A── │ ╔════════ ═══════════════════╗ │─────────────────────── ────────── ──────────────────────────────────────────── >│ ║SignPrvA, SignPubA = chaj ( )║ │ │ ║PrvA, PubA = DHgen() ║ │ │ ╚═════════════════════════════════ ═ ═╝ │IdB, PubB, CookieB , siy(SignPrvB, (CookieA, CookieB, PubB)), MAC(IdB) │ ╔════════════════════════════════════ │< ──────────────────────────────────────────── ───────── ────────────────────│ ║SignPrvB, SignPubB = chaj()║ │ ─────────│ ║SignPrvB, SignPubB = chaj()║ │ ── │ ─ DH││││ ╚══════ ═════════════════════╝ │ │ ╔═══════════════════ ══════╗ │ siy( SignPrvA, (CookieB, CookieA, PubA)), MAC(IdA) │ ║Key = DH(PrvA, PubB) ║ │────────────────────────────── ─ ──────────────────────────────────────────── ──────>│ ║ verifye(Kle, IdB) ║ │ │ ║verify(SignPubB, ...)║ │ │ ╚═══════════════════════════════ │

Finalman, nou vle jwenn konfidansyalite patnè konvèsasyon nou yo nan men yon obsèvatè pasif. Pou fè sa, SIGMA pwopoze premye echanj kle efemèr epi devlope yon kle komen sou ki ankripte mesaj otantifikasyon ak idantifye. SIGMA dekri de opsyon:

  • SIGMA-I - pwoteje inisyatè a kont atak aktif, sekouris a soti nan atak pasif: inisyatè a otantifye sekouris la epi si yon bagay pa matche, Lè sa a, li pa bay idantifikasyon li yo. Akize a bay idantifikasyon li si yo kòmanse yon pwotokòl aktif avèk li. Obsèvatè pasif la pa aprann anyen;
    SIGMA-R - pwoteje sekouris la kont atak aktif, inisyatè a soti nan atak pasif. Tout bagay se egzakteman opoze a, men nan pwotokòl sa a kat mesaj lanmen yo deja transmèt.

    Nou chwazi SIGMA-I kòm li pi sanble ak sa nou espere nan bagay ki abitye kliyan-sèvè: kliyan an rekonèt sèlman pa sèvè otantifye a, epi tout moun deja konnen sèvè a. Anplis de sa, li pi fasil pou aplike akòz mwens mesaj lanmen. Tout sa nou ajoute nan pwotokòl la se ankripte yon pati nan mesaj la epi transfere idantifyan an A nan pati a chiffres nan dènye mesaj la:

    ┌─────┐ ┌─────┐ │PeerA│ │PeerB│ └──┬──┘ └──┘ └─────────── ╔══════════ ═════════════════╗ │─────────────────────────── ────────── ──────────────────────────────────────────── ─────>│ ║SignPrvA , SignPubA = chaj ()║ │ │ ║PrvA, PubA = DHgen () ║ │ │ ╚══════════════════════════════════ ════╝ │ PubB, CookieB, Enc((IdB, sign(SignPrvB, (CookieA, CookieB, PubB)), MAC(IdB))) │ ╔═════════════════════════ ═══════╗ │<────────────────────────────────── ───────── ║SignPrv B, SignPubB = chaj()║ │ │ ║ PrvB, PubB = DHgen() ║ │ │ ╚════════════════════════════════ ══════ ═╝ │ │ ╔ ════════ ═════════════╗ │ Enc sign((Iign, MACA) IdA) )) │ ║Kle = DH(PrvA, PubB) ║ │───────────────────────────────── ─────── ──── ───────── ────────────────────────────── ─>│ ║verifye(Kle, IdB) ║ │ │ ║verify(Siyen PubB, ...)║ │ │ ╚═════════════════════════════
    
    • GOST R yo itilize pou siyati 34.10-2012 algorithm ak kle 256-bit.
    • Pou jenere kle piblik la, yo itilize 34.10/2012/XNUMX VKO.
    • CMAC yo itilize kòm MAC la. Teknikman, sa a se yon mòd operasyon espesyal nan yon chifreman blòk, ki dekri nan GOST R 34.13-2015. Kòm yon fonksyon chifreman pou mòd sa a - Sotrèl (34.12-2015).
    • Yo itilize hash la nan kle piblik li kòm idantifyan entèrlokuteur a. Itilize kòm yon hash Stribog-256 (34.11/2012/256 XNUMX Bits).

    Apre lanmen an, nou pral dakò sou yon kle pataje. Nou ka sèvi ak li pou chifreman otantifye nan mesaj transpò. Pati sa a trè senp epi difisil pou fè yon erè: nou ogmante kontwa mesaj la, ankripte mesaj la, otantifye (MAC) kontwa a ak chifretèks, voye. Lè n ap resevwa yon mesaj, nou tcheke si kontwa a gen valè espere, otantifye tèks chifreman an ak kontwa an, epi dechifre li. Ki kle mwen ta dwe itilize pou ankripte mesaj lanmen, transpòte mesaj, ak kijan pou otantifye yo? Sèvi ak yon sèl kle pou tout travay sa yo se danjere e saj. Li nesesè pou jenere kle lè l sèvi avèk fonksyon espesyalize KDF (fonksyon derivasyon kle). Ankò, se pou nou pa fann cheve epi envante yon bagay: HKDF li te konnen depi lontan, byen rechèch epi li pa gen okenn pwoblèm li te ye. Malerezman, bibliyotèk natif natal Python pa gen fonksyon sa a, kidonk nou itilize hkdf sak plastik. HKDF entèn itilize HMAC, ki an vire sèvi ak yon fonksyon hash. Yon egzanp aplikasyon nan Python sou paj Wikipedya a pran jis kèk liy kòd. Kòm nan ka a nan 34.10/2012/256, nou pral sèvi ak Stribog-XNUMX kòm fonksyon an hash. Pwodiksyon fonksyon akò kle nou an pral rele kle sesyon an, ki soti nan ki simetrik ki manke yo pral pwodwi:

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

    Estrikti/Schémas

    Ann gade ki estrikti ASN.1 nou genyen kounye a pou transmèt tout done sa yo:

    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 se sa ki pral siyen. HandshakeTBE - sa ki pral chiffres. Mwen atire atansyon w sou jaden ukm nan MsgHandshake1. 34.10 VKO, pou menm pi gwo randomisation nan kle yo pwodwi, gen ladan UKM (materyèl keying itilizatè) paramèt la - jis antropi adisyonèl.

    Ajoute kriptografik nan Kòd

    Ann konsidere sèlman chanjman ki fèt nan kòd orijinal la, depi fondasyon an rete menm jan an (an reyalite, aplikasyon final la te ekri an premye, ak Lè sa a, tout kriptografi a te koupe soti nan li).

    Depi otantifikasyon ak idantifikasyon entèrlokuteur yo pral fèt lè l sèvi avèk kle piblik, yo kounye a bezwen yo dwe estoke yon kote pou yon tan long. Pou senplisite, nou itilize JSON tankou sa a:

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

    nou - pè kle nou an, kle prive ak piblik egzadesimal. yo — non entèrlokuteur yo ak kle piblik yo. Ann chanje agiman liy kòmand yo epi ajoute apre-pwosesis done 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),
        }
    # }}}
    

    Kle prive algorithm 34.10 la se yon nimewo o aza. Gwosè 256-bit pou koub eliptik 256-bit. PyGOST pa travay ak yon seri bytes, men ak gwo kantite, Se konsa, kle prive nou an (urandom(32)) bezwen konvèti nan yon nimewo lè l sèvi avèk gost3410.prv_unmarshal (). Se kle piblik la detèmine detèministic soti nan kle prive a lè l sèvi avèk gost3410.public_key(). Kle piblik 34.10 la se de gwo nimewo ki bezwen tou konvèti nan yon sekans byte pou fasilite depo ak transmisyon lè l sèvi avèk gost3410.pub_marshal().

    Apre ou fin li fichye JSON la, kle piblik yo dwe konvèti tounen lè l sèvi avèk gost3410.pub_unmarshal(). Depi nou pral resevwa idantifyan yo nan entèrlokuteur yo nan fòm lan nan yon hash soti nan kle piblik la, yo ka imedyatman kalkile davans epi mete yo nan yon diksyonè pou rechèch rapid. Stribog-256 hash se gost34112012256.GOST34112012256 (), ki konplètman satisfè koòdone nan hashlib nan fonksyon hash.

    Ki jan koroutin inisyatè a chanje? Tout bagay se tankou pou chak konplo lanmen: nou jenere yon bonbon (128-bit se anpil), yon pè kle efemèr 34.10, ki pral itilize pou fonksyon an akò kle 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()
    

    • nou tann yon repons epi dekode mesaj Msg kap vini an;
    • asire w ke ou jwenn handshake1;
    • dekode kle piblik efemèr pati opoze a epi kalkile kle sesyon an;
    • Nou jenere kle simetrik ki nesesè pou trete pati TBE mesaj la.

     423     logging.info("%s: got %s message", _id, msg.choice)
     424     if msg.choice != "handshake1":
     425         logging.warning("%s: unexpected message, disconnecting", _id)
     426         writer.close()
     427         return
     428     # }}}
     429     msg_handshake1 = msg.value
     430     # Validate Handshake message {{{
     431     cookie_their = msg_handshake1["cookieResponder"]
     432     pub_their_raw = msg_handshake1["pubKeyResponder"]
     433     pub_their = gost3410.pub_unmarshal(bytes(pub_their_raw))
     434     ukm_raw = bytes(msg_handshake1["ukm"])
     435     ukm = ukm_unmarshal(ukm_raw)
     436     key_session = kek_34102012256(CURVE, prv, pub_their, ukm, mode=2001)
     437     kdf = Hkdf(None, key_session, hash=GOST34112012256)
     438     key_handshake1_mac_identity = kdf.expand(b"handshake1-mac-identity")
     439     key_handshake1_enc = kdf.expand(b"handshake1-enc")
     440     key_handshake1_mac = kdf.expand(b"handshake1-mac")
    

    UKM se yon nimewo 64-bit (urandom(8)), ki tou mande pou deserialization soti nan reprezantasyon byte li yo lè l sèvi avèk gost3410_vko.ukm_unmarshal (). Fonksyon VKO pou 34.10/2012/256 3410-bit se gost34102012256_vko.kek_XNUMX () (KEK - kle chifreman).

    Kle sesyon pwodwi a se deja yon sekans byte pseudo-o aza 256-bit. Se poutèt sa, li ka imedyatman itilize nan fonksyon HKDF. Depi GOST34112012256 satisfè koòdone hashlib la, li ka imedyatman itilize nan klas Hkdf. Nou pa presize sèl la (premye agiman nan Hkdf), depi kle ki pwodwi a, akòz efemèr nan pè kle k ap patisipe yo, yo pral diferan pou chak sesyon epi li deja gen ase entropi. kdf.expand() pa default deja pwodui kle 256-bit ki nesesè pou Grasshopper pita.

    Apre sa, yo tcheke pati TBE ak TBS nan mesaj k ap vini an:

    • MAC a sou tèks chifreman fèk ap rantre yo kalkile epi tcheke;
    • se chifreman tèks la dechifre;
    • estrikti TBE dekode;
    • yo pran idantifikasyon entèrlokuteur a nan men li epi yo tcheke si nou konnen li ditou;
    • MAC sou idantifyan sa a kalkile epi tcheke;
    • siyati sou estrikti TBS la verifye, ki gen ladann bonbon tou de pati yo ak kle piblik efemèr pati opoze a. Siyati a verifye pa kle siyati ki dire lontan entèlokutè a.

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

    Kòm mwen te ekri pi wo a, 34.13/2015/XNUMX dekri divès kalite bloke mòd operasyon chifreman yo soti 34.12/2015/3413. Pami yo gen yon mòd pou jenere imitasyon foure ak kalkil MAC. Nan PyGOST sa a se gost34.12.mac (). Mòd sa a mande pou pase fonksyon an chifreman (resevwa ak retounen yon blòk nan done), gwosè a nan blòk la chifreman ak, an reyalite, done nan tèt li. Poukisa ou pa ka hardcode gwosè blòk chifreman an? 2015/128/64 dekri non sèlman chifreman Grasshopper XNUMX-bit, men tou XNUMX-bit la. Magma - yon GOST 28147-89 yon ti kras modifye, ki te kreye tounen nan KGB a epi li toujou gen youn nan pi wo papòt sekirite yo.

    Kuznechik inisyalize lè w rele gost.3412.GOST3412Kuznechik(kle) epi li retounen yon objè ak metòd .encrypt()/.decrypt() apwopriye pou pase nan fonksyon 34.13. MAC kalkile jan sa a: gost3413.mac(GOST3412Kuznechik(key).encrypt, KUZNECHIK_BLOCKSIZE, ciphertext). Pou konpare MAC kalkile a ak resevwa, ou pa ka itilize konparezon abityèl (==) nan fisèl byte, depi operasyon sa a fwit tan konparezon, ki, nan ka jeneral la, ka mennen nan frajilite fatal tankou BÈT atak sou TLS. Python gen yon fonksyon espesyal, hmac.compare_digest, pou sa.

    Fonksyon chifre blòk an ka sèlman ankripte yon blòk done. Pou yon nimewo ki pi gwo, e menm pa yon miltip nan longè a, li nesesè yo sèvi ak mòd nan chifreman. 34.13-2015 dekri sa ki annapre yo: ECB, CTR, OFB, CBC, CFB. Chak gen pwòp zòn akseptab aplikasyon ak karakteristik. Malerezman, nou toujou pa gen estanda mòd chifreman otantifye (tankou CCM, OCB, GCM ak renmen an) - nou oblije omwen ajoute MAC tèt nou. mwen chwazi mòd kontwa (CTR): li pa mande pou padding nan gwosè blòk la, yo ka paralelize, itilize sèlman fonksyon an chifreman, yo ka itilize san danje pou ankripte gwo kantite mesaj (kontrèman ak CBC, ki gen kolizyon relativman byen vit).

    Tankou .mac (), .ctr () pran opinyon menm jan an: ciphertext = gost3413.ctr (GOST3412Kuznechik (key). Encrypt, KUZNECHIK_BLOCKSIZE, plaintext, iv). Li oblije presize yon vektè inisyalizasyon ki se egzakteman mwatye longè blòk chifreman an. Si yo itilize kle chifreman nou an sèlman pou ankripte yon mesaj (kwake soti nan plizyè blòk), Lè sa a, li an sekirite pou mete yon vektè inisyalizasyon zewo. Pou ankripte mesaj lanmen, nou itilize yon kle separe chak fwa.

    Verifye siyati gost3410.verify() se trivial: nou pase koub eliptik nan kote n ap travay la (nou tou senpleman anrejistre li nan pwotokòl GOSTIM nou an), kle piblik siyatè a (pa bliye ke sa a ta dwe yon tuple de de). gwo nimewo, epi yo pa yon fisèl byte), 34.11/2012/XNUMX hash ak siyati nan tèt li.

    Apre sa, nan inisyatè a nou prepare epi voye yon mesaj lanmen bay handshake2, fè menm aksyon nou te fè pandan verifikasyon, sèlman simetrik: siyen sou kle nou yo olye pou yo tcheke, elatriye...

     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)
     

    Lè sesyon an etabli, kle transpò yo pwodwi (yon kle separe pou chifreman, pou otantifikasyon, pou chak pati yo), epi Grasshopper la inisyalize pou dechifre epi tcheke MAC la:

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

    Koroutin msg_sender la kounye a ankripte mesaj anvan yo voye yo sou yon koneksyon TCP. Chak mesaj gen yon nonce monotonically ogmante, ki se tou vektè inisyalizasyon an lè chiffres nan mòd kontwa. Chak mesaj ak blòk mesaj garanti gen yon valè kontwa diferan.

    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
    

    Mesaj k ap rantre yo trete pa koroutin msg_receiver, ki okipe otantifikasyon ak dechifre:

    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)
    

    Konklizyon

    GOSTIM fèt pou itilize sèlman pou rezon edikasyon (piske li pa kouvri pa tès yo, omwen)! Kòd sous pwogram lan ka telechaje isit la (Стрибог-256 хэш: 995bbd368c04e50a481d138c5fa2e43ec7c89bc77743ba8dbabee1fde45de120). Как и все мои проекты, типа GoGOST, PyDERASN, NCCP, GoVPN, GOSTIM se konplètman lojisyèl gratis, distribye anba kondisyon yo GPLv3 +.

    Sergey Matveev, cypherpunk, manm Fondasyon SPO, Pwomotè Python/Go, espesyalis an chèf FSUE "STC "Atlas".

Sous: www.habr.com

Add nouvo kòmantè