Будучи разработчиком PyGOST библиотеки (ГОСТовые криптографические примитивы на чистом Python), я нередко получаю вопросы о том, как на коленке реализовать простейший безопасный обмен сообщениями. Многие считают прикладную криптографию достаточно простой штукой, и .encrypt() вызова у блочного шифра будет достаточно для безопасной отсылки по каналу связи. Другие же считают, что прикладная криптография — удел немногих, и приемлемо, что богатые компании типа Telegram с олимпиадниками-математиками не могут реализовать безопасный протокол.
Всё это побудило меня написать данную статью, чтобы показать, что реализация криптографических протоколов и безопасного IM-а не такая сложная задача. Однако изобретать собственные протоколы аутентификации и согласования ключей не стоит.
|-- alice | |-- in | |-- out | `-- state |-- bob | |-- in | |-- out | `-- state `- conn
# 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))),
))
------¬ ------¬ ¦PeerA¦ ¦PeerB¦ L--T--- L--T--- ¦MsgHandshake(IdA) ¦ ¦----------------->¦ ¦ ¦ ¦MsgHandshake(IdB) ¦ ¦<-----------------¦ ¦ ¦ ¦ MsgText() ¦ ¦----------------->¦ ¦ ¦ ¦ MsgText() ¦ ¦<-----------------¦ ¦ ¦
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(","))
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"))
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"))
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()
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()
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))
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()
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 # }}}
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 # }}}
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))
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)
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()
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()
------¬ ------¬ ¦PeerA¦ ¦PeerB¦ L--T--- L--T--- ¦ IdA, PubA ¦ г====================¬ ¦--------------->¦ ¦PrvA, PubA = DHgen()¦ ¦ ¦ L====================- ¦ IdB, PubB ¦ г====================¬ ¦<---------------¦ ¦PrvB, PubB = DHgen()¦ ¦ ¦ L====================- ----¬ г=======¦============¬ ¦ ¦Key = DH(PrvA, PubB)¦ <---- L=======T============- ¦ ¦ ¦ ¦
------¬ ------¬ ¦PeerA¦ ¦PeerB¦ L--T--- L--T--- ¦IdA, PubA, sign(SignPrvA, (PubA)) ¦ г===========================¬ ¦--------------------------------->¦ ¦SignPrvA, SignPubA = load()¦ ¦ ¦ ¦PrvA, PubA = DHgen() ¦ ¦ ¦ L===========================- ¦IdB, PubB, sign(SignPrvB, (PubB)) ¦ г===========================¬ ¦<---------------------------------¦ ¦SignPrvB, SignPubB = load()¦ ¦ ¦ ¦PrvB, PubB = DHgen() ¦ ¦ ¦ L===========================- ----¬ г=====================¬ ¦ ¦ ¦verify(SignPubB, ...)¦ ¦ <---- ¦Key = DH(PrvA, PubB) ¦ ¦ ¦ L=====================- ¦ ¦ ¦
------¬ ------¬ ¦PeerA¦ ¦PeerB¦ L--T--- L--T--- ¦ IdA, PubA ¦ г===========================¬ ¦-------------------------------------------->¦ ¦SignPrvA, SignPubA = load()¦ ¦ ¦ ¦PrvA, PubA = DHgen() ¦ ¦ ¦ L===========================- ¦IdB, PubB, sign(SignPrvB, (IdB, PubA, PubB)) ¦ г===========================¬ ¦<--------------------------------------------¦ ¦SignPrvB, SignPubB = load()¦ ¦ ¦ ¦PrvB, PubB = DHgen() ¦ ¦ ¦ L===========================- ¦ sign(SignPrvA, (IdA, PubB, PubA)) ¦ г=====================¬ ¦-------------------------------------------->¦ ¦verify(SignPubB, ...)¦ ¦ ¦ ¦Key = DH(PrvA, PubB) ¦ ¦ ¦ L=====================- ¦ ¦
------¬ ------¬ ¦PeerA¦ ¦PeerB¦ L--T--- L--T--- ¦ IdA, PubA ¦ г===========================¬ ¦------------------------------------------------->¦ ¦SignPrvA, SignPubA = load()¦ ¦ ¦ ¦PrvA, PubA = DHgen() ¦ ¦ ¦ L===========================- ¦IdB, PubB, sign(SignPrvB, (PubA, PubB)), MAC(IdB) ¦ г===========================¬ ¦<-------------------------------------------------¦ ¦SignPrvB, SignPubB = load()¦ ¦ ¦ ¦PrvB, PubB = DHgen() ¦ ¦ ¦ L===========================- ¦ ¦ г=====================¬ ¦ sign(SignPrvA, (PubB, PubA)), MAC(IdA) ¦ ¦Key = DH(PrvA, PubB) ¦ ¦------------------------------------------------->¦ ¦verify(Key, IdB) ¦ ¦ ¦ ¦verify(SignPubB, ...)¦ ¦ ¦ L=====================- ¦ ¦
------¬ ------¬ ¦PeerA¦ ¦PeerB¦ L--T--- L--T--- ¦ IdA, PubA, CookieA ¦ г===========================¬ ¦---------------------------------------------------------------------->¦ ¦SignPrvA, SignPubA = load()¦ ¦ ¦ ¦PrvA, PubA = DHgen() ¦ ¦ ¦ L===========================- ¦IdB, PubB, CookieB, sign(SignPrvB, (CookieA, CookieB, PubB)), MAC(IdB) ¦ г===========================¬ ¦<----------------------------------------------------------------------¦ ¦SignPrvB, SignPubB = load()¦ ¦ ¦ ¦PrvB, PubB = DHgen() ¦ ¦ ¦ L===========================- ¦ ¦ г=====================¬ ¦ sign(SignPrvA, (CookieB, CookieA, PubA)), MAC(IdA) ¦ ¦Key = DH(PrvA, PubB) ¦ ¦---------------------------------------------------------------------->¦ ¦verify(Key, IdB) ¦ ¦ ¦ ¦verify(SignPubB, ...)¦ ¦ ¦ L=====================- ¦ ¦
------¬ ------¬ ¦PeerA¦ ¦PeerB¦ L--T--- L--T--- ¦ PubA, CookieA ¦ г===========================¬ ¦----------------------------------------------------------------------------->¦ ¦SignPrvA, SignPubA = load()¦ ¦ ¦ ¦PrvA, PubA = DHgen() ¦ ¦ ¦ L===========================- ¦PubB, CookieB, Enc((IdB, sign(SignPrvB, (CookieA, CookieB, PubB)), MAC(IdB))) ¦ г===========================¬ ¦<-----------------------------------------------------------------------------¦ ¦SignPrvB, SignPubB = load()¦ ¦ ¦ ¦PrvB, PubB = DHgen() ¦ ¦ ¦ L===========================- ¦ ¦ г=====================¬ ¦ Enc((IdA, sign(SignPrvA, (CookieB, CookieA, PubA)), MAC(IdA))) ¦ ¦Key = DH(PrvA, PubB) ¦ ¦----------------------------------------------------------------------------->¦ ¦verify(Key, IdB) ¦ ¦ ¦ ¦verify(SignPubB, ...)¦ ¦ ¦ L=====================- ¦ ¦
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")
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)
{
"our": {
"prv": "21254cf66c15e0226ef2669ceee46c87b575f37f9000272f408d0c9283355f98",
"pub": "938c87da5c55b27b7f332d91b202dbef2540979d6ceaa4c35f1b5bfca6df47df0bdae0d3d82beac83cec3e353939489d9981b7eb7a3c58b71df2212d556312a1"
},
"their": {
"alice": "d361a59c25d2ca5a05d21f31168609deeec100570ac98f540416778c93b2c7402fd92640731a707ec67b5410a0feae5b78aeec93c4a455a17570a84f2bc21fce",
"bob": "aade1207dd85ecd283272e7b69c078d5fae75b6e141f7649ad21962042d643512c28a2dbdc12c7ba40eb704af920919511180c18f4d17e07d7f5acd49787224a"
}
}
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),
}
# }}}
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()
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")
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"]
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)
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 # }}}
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
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)
К сожалению, не доступен сервер mySQL