GOSTIM: P2P F2F E2EE IM GOST క్రిప్టోగ్రఫీతో ఒక సాయంత్రం

డెవలపర్‌గా ఉండటం పైగోస్ట్ లైబ్రరీలు (స్వచ్ఛమైన పైథాన్‌లో GOST క్రిప్టోగ్రాఫిక్ ప్రిమిటివ్స్), మోకాలిపై సరళమైన సురక్షిత సందేశాన్ని ఎలా అమలు చేయాలనే దాని గురించి నేను తరచుగా ప్రశ్నలను అందుకుంటాను. చాలా మంది వ్యక్తులు అనువర్తిత క్రిప్టోగ్రఫీని చాలా సరళంగా భావిస్తారు మరియు ఒక బ్లాక్ సైఫర్‌లో .encrypt()ని కాల్ చేయడం ద్వారా దానిని కమ్యూనికేషన్ ఛానెల్‌లో సురక్షితంగా పంపడానికి సరిపోతుంది. మరికొందరు అనువర్తిత క్రిప్టోగ్రఫీ అనేది కొందరికి విధి అని నమ్ముతారు మరియు ఒలింపియాడ్-గణిత శాస్త్రజ్ఞులతో టెలిగ్రామ్ వంటి రిచ్ కంపెనీలు ఆమోదయోగ్యమైనవి. అమలు చేయలేము సురక్షిత ప్రోటోకాల్.

క్రిప్టోగ్రాఫిక్ ప్రోటోకాల్‌లు మరియు సురక్షిత IMని అమలు చేయడం అంత కష్టమైన పని కాదని చూపించడానికి ఇవన్నీ ఈ కథనాన్ని వ్రాయడానికి నన్ను ప్రేరేపించాయి. అయితే, మీ స్వంత ప్రమాణీకరణ మరియు కీలక ఒప్పంద ప్రోటోకాల్‌లను కనుగొనడం విలువైనది కాదు.

GOSTIM: P2P F2F E2EE IM GOST క్రిప్టోగ్రఫీతో ఒక సాయంత్రం
వ్యాసం వ్రాస్తాను పీర్-టు-పీర్, స్నేహితుడు-మిత్రుడు, ఎండ్-టు-ఎండ్ ఎన్‌క్రిప్ట్ చేయబడింది తక్షణ మెసెంజర్ తో సిగ్మా-I ప్రమాణీకరణ మరియు కీలక ఒప్పంద ప్రోటోకాల్ (దీని ఆధారంగా ఇది అమలు చేయబడుతుంది IPsec IKE), ప్రత్యేకంగా GOST క్రిప్టోగ్రాఫిక్ అల్గారిథమ్‌లు PyGOST లైబ్రరీ మరియు ASN.1 సందేశ ఎన్‌కోడింగ్ లైబ్రరీని ఉపయోగించడం పైడెరాస్ఎన్ (దీని గురించి నేను ఇప్పటికే ముందు రాశారు) ఒక అవసరం: ఇది చాలా సరళంగా ఉండాలి, ఇది ఒక సాయంత్రం (లేదా పనిదినం) మొదటి నుండి వ్రాయబడుతుంది, లేకుంటే అది సాధారణ ప్రోగ్రామ్ కాదు. ఇది బహుశా లోపాలు, అనవసరమైన సమస్యలు, లోపాలను కలిగి ఉండవచ్చు మరియు ఇది అసిన్సియో లైబ్రరీని ఉపయోగించే నా మొదటి ప్రోగ్రామ్.

IM డిజైన్

ముందుగా, మన IM ఎలా ఉంటుందో అర్థం చేసుకోవాలి. సరళత కోసం, పాల్గొనేవారిని కనుగొనకుండానే పీర్-టు-పీర్ నెట్‌వర్క్‌గా ఉండనివ్వండి. మేము వ్యక్తిగతంగా ఏ చిరునామాను సూచిస్తాము: సంభాషణకర్తతో కమ్యూనికేట్ చేయడానికి కనెక్ట్ చేయడానికి పోర్ట్.

ఈ సమయంలో, రెండు ఏకపక్ష కంప్యూటర్‌ల మధ్య డైరెక్ట్ కమ్యూనికేషన్ అందుబాటులో ఉందనే భావన ఆచరణలో IM యొక్క వర్తింపుపై గణనీయమైన పరిమితి అని నేను అర్థం చేసుకున్నాను. కానీ ఎక్కువ మంది డెవలపర్లు అన్ని రకాల NAT-ట్రావర్సల్ క్రచెస్‌లను అమలు చేస్తే, మేము IPv4 ఇంటర్నెట్‌లో ఎక్కువ కాలం ఉంటాము, ఏకపక్ష కంప్యూటర్‌ల మధ్య కమ్యూనికేషన్ యొక్క నిరుత్సాహకరమైన సంభావ్యతతో. ఇంట్లో మరియు కార్యాలయంలో IPv6 లేకపోవడాన్ని మీరు ఎంతకాలం సహించగలరు?

మేము స్నేహితుల నుండి స్నేహితుల నెట్‌వర్క్‌ని కలిగి ఉంటాము: సాధ్యమయ్యే అన్ని సంభాషణకర్తలు ముందుగానే తెలుసుకోవాలి. మొదట, ఇది ప్రతిదీ చాలా సులభతరం చేస్తుంది: మేము మమ్మల్ని పరిచయం చేసుకున్నాము, పేరు/కీని కనుగొన్నాము లేదా కనుగొనలేకపోయాము, డిస్‌కనెక్ట్ చేసాము లేదా పనిని కొనసాగించాము, సంభాషణకర్తను తెలుసుకోవడం. రెండవది, సాధారణంగా, ఇది సురక్షితమైనది మరియు అనేక దాడులను తొలగిస్తుంది.

IM ఇంటర్‌ఫేస్ క్లాసిక్ సొల్యూషన్‌లకు దగ్గరగా ఉంటుంది కరువైన ప్రాజెక్టులు, వారి మినిమలిజం మరియు యునిక్స్-వే ఫిలాసఫీ కోసం నేను నిజంగా ఇష్టపడతాను. IM ప్రోగ్రామ్ ప్రతి సంభాషణకర్త కోసం మూడు Unix డొమైన్ సాకెట్‌లతో డైరెక్టరీని సృష్టిస్తుంది:

  • లో - సంభాషణకర్తకు పంపబడిన సందేశాలు దానిలో నమోదు చేయబడ్డాయి;
  • అవుట్ - సంభాషణకర్త నుండి అందుకున్న సందేశాలు దాని నుండి చదవబడతాయి;
  • రాష్ట్రం - దాని నుండి చదవడం ద్వారా, సంభాషణకర్త ప్రస్తుతం కనెక్ట్ చేయబడిందా, కనెక్షన్ చిరునామా/పోర్ట్ అని మేము కనుగొంటాము.

అదనంగా, మేము రిమోట్ సంభాషణకర్తకు కనెక్షన్‌ని ప్రారంభించే హోస్ట్ పోర్ట్‌ను వ్రాయడం ద్వారా కాన్ సాకెట్ సృష్టించబడుతుంది.

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

ఈ విధానం IM రవాణా మరియు వినియోగదారు ఇంటర్‌ఫేస్ యొక్క స్వతంత్ర అమలులను చేయడానికి మిమ్మల్ని అనుమతిస్తుంది, ఎందుకంటే స్నేహితుడు లేనందున, మీరు అందరినీ మెప్పించలేరు. ఉపయోగించి tmux మరియు / లేదా మల్టీటైల్, మీరు సింటాక్స్ హైలైటింగ్‌తో బహుళ-విండో ఇంటర్‌ఫేస్‌ను పొందవచ్చు. మరియు సహాయంతో rlwrap మీరు GNU రీడ్‌లైన్-అనుకూల సందేశ ఇన్‌పుట్ లైన్‌ని పొందవచ్చు.

నిజానికి, సక్‌లెస్ ప్రాజెక్ట్‌లు FIFO ఫైల్‌లను ఉపయోగిస్తాయి. వ్యక్తిగతంగా, అంకితమైన థ్రెడ్‌ల నుండి చేతితో వ్రాసిన నేపథ్యం లేకుండా అసిన్సియోలో ఫైల్‌లతో పోటీతత్వంతో ఎలా పని చేయాలో నాకు అర్థం కాలేదు (నేను చాలా కాలంగా అలాంటి విషయాల కోసం భాషను ఉపయోగిస్తున్నాను Go) అందువల్ల, నేను Unix డొమైన్ సాకెట్‌లతో చేయాలని నిర్ణయించుకున్నాను. దురదృష్టవశాత్తూ, ఇది echo 2001:470:dead::babe 6666 > conn చేయడం అసాధ్యం. నేను ఉపయోగించి ఈ సమస్యను పరిష్కరించాను సోకాట్: echo 2001:470:dead::babe 6666 | socat - UNIX-Connect:conn, socat రీడ్‌లైన్ UNIX-కనెక్ట్:alice/in.

అసలైన అసురక్షిత ప్రోటోకాల్

TCP రవాణాగా ఉపయోగించబడుతుంది: ఇది డెలివరీ మరియు దాని ఆర్డర్‌కు హామీ ఇస్తుంది. UDP ఏదీ హామీ ఇవ్వదు (క్రిప్టోగ్రఫీని ఉపయోగించినప్పుడు ఇది ఉపయోగకరంగా ఉంటుంది), కానీ మద్దతు ఇస్తుంది SCTP పైథాన్ బాక్స్ నుండి బయటకు రాదు.

దురదృష్టవశాత్తు, TCPలో సందేశం యొక్క భావన లేదు, బైట్‌ల స్ట్రీమ్ మాత్రమే. అందువల్ల, ఈ థ్రెడ్‌లో సందేశాల కోసం ఒక ఫార్మాట్‌తో ముందుకు రావడం అవసరం, తద్వారా అవి ఈ థ్రెడ్‌లో భాగస్వామ్యం చేయబడతాయి. మేము లైన్ ఫీడ్ అక్షరాన్ని ఉపయోగించడానికి అంగీకరించవచ్చు. ప్రారంభకులకు ఇది బాగానే ఉంది, కానీ ఒకసారి మన సందేశాలను గుప్తీకరించడం ప్రారంభించిన తర్వాత, ఈ అక్షరం సాంకేతికతలో ఎక్కడైనా కనిపించవచ్చు. నెట్‌వర్క్‌లలో, కాబట్టి, జనాదరణ పొందిన ప్రోటోకాల్‌లు ముందుగా సందేశం యొక్క పొడవును బైట్‌లలో పంపుతాయి. ఉదాహరణకు, పెట్టె వెలుపల పైథాన్ xdrlibని కలిగి ఉంది, ఇది మీరు ఇదే ఆకృతితో పని చేయడానికి అనుమతిస్తుంది XDR.

మేము TCP రీడింగ్‌తో సరిగ్గా మరియు సమర్ధవంతంగా పని చేయము - మేము కోడ్‌ను సరళీకృతం చేస్తాము. మేము పూర్తి సందేశాన్ని డీకోడ్ చేసే వరకు మేము సాకెట్ నుండి డేటాను అంతులేని లూప్‌లో చదువుతాము. XMLతో ఉన్న JSONని కూడా ఈ విధానం కోసం ఫార్మాట్‌గా ఉపయోగించవచ్చు. కానీ క్రిప్టోగ్రఫీని జోడించినప్పుడు, డేటా సంతకం చేయబడి, ప్రామాణీకరించబడాలి - మరియు దీనికి JSON/XML అందించని వస్తువుల యొక్క బైట్-ఫర్-బైట్ సారూప్య ప్రాతినిధ్యం అవసరం (డంప్‌ల ఫలితాలు మారవచ్చు).

XDR ఈ పనికి అనుకూలంగా ఉంటుంది, అయితే నేను DER ఎన్‌కోడింగ్‌తో ASN.1ని ఎంచుకుంటాను మరియు పైడెరాస్ఎన్ లైబ్రరీ, ఎందుకంటే మన దగ్గర ఉన్నత స్థాయి వస్తువులు ఉంటాయి, దానితో ఇది తరచుగా మరింత ఆహ్లాదకరంగా మరియు పని చేయడానికి సౌకర్యవంతంగా ఉంటుంది. స్కీమాలెస్ కాకుండా బెంకోడ్, మెసేజ్‌ప్యాక్ లేదా CBOR, ASN.1 హార్డ్-కోడెడ్ స్కీమాకు వ్యతిరేకంగా డేటాను స్వయంచాలకంగా తనిఖీ చేస్తుంది.

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

అందుకున్న సందేశం Msg అవుతుంది: టెక్స్ట్ MsgText (ప్రస్తుతానికి ఒక టెక్స్ట్ ఫీల్డ్‌తో) లేదా Msg హ్యాండ్‌షేక్ హ్యాండ్‌షేక్ సందేశం (ఇందులో సంభాషణకర్త పేరు ఉంటుంది). ఇప్పుడు ఇది చాలా క్లిష్టంగా కనిపిస్తుంది, కానీ ఇది భవిష్యత్తుకు పునాది.

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

క్రిప్టోగ్రఫీ లేకుండా IM

నేను ఇప్పటికే చెప్పినట్లుగా, అన్ని సాకెట్ కార్యకలాపాలకు asyncio లైబ్రరీ ఉపయోగించబడుతుంది. లాంచ్‌లో మనం ఏమి ఆశిస్తున్నామో తెలియజేస్తాము:

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

మీ స్వంత పేరును సెట్ చేయండి (--మా-పేరు ఆలిస్). ఊహించిన అన్ని సంభాషణకర్తలు కామాలతో వేరు చేయబడి ఉంటారు (-వారి పేర్లు బాబ్, ఈవ్). ప్రతి సంభాషణకర్తల కోసం, Unix సాకెట్‌లతో కూడిన డైరెక్టరీ సృష్టించబడుతుంది, అలాగే ప్రతి ఇన్, అవుట్, స్టేట్ కోసం ఒక కొరూటిన్:

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

ఇన్ సాకెట్ నుండి వినియోగదారు నుండి వచ్చే సందేశాలు IN_QUEUES క్యూకి పంపబడతాయి:

async def unixsock_in_processor(reader, writer, in_queue: asyncio.Queue) -> None:
    while True:
        text = await reader.read(MaxTextLen)
        if text == b"":
            break
        await in_queue.put(text.decode("utf-8"))

సంభాషణకర్తల నుండి వచ్చే సందేశాలు OUT_QUEUES క్యూలకు పంపబడతాయి, దాని నుండి డేటా అవుట్ సాకెట్‌కు వ్రాయబడుతుంది:

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

స్టేట్ సాకెట్ నుండి చదివేటప్పుడు, ప్రోగ్రామ్ PEER_ALIVE డిక్షనరీలో సంభాషణకర్త చిరునామా కోసం చూస్తుంది. సంభాషణకర్తకు ఇంకా కనెక్షన్ లేనట్లయితే, ఖాళీ లైన్ వ్రాయబడుతుంది.

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

అప్పుడు, అది రిమోట్ పార్టీ నుండి ప్రతిస్పందన కోసం వేచి ఉంది. Msg ASN.1 పథకాన్ని ఉపయోగించి ఇన్‌కమింగ్ ప్రతిస్పందనను డీకోడ్ చేయడానికి ప్రయత్నిస్తుంది. మొత్తం సందేశం ఒక TCP విభాగంలో పంపబడుతుందని మేము ఊహిస్తాము మరియు .read()కి కాల్ చేసినప్పుడు మేము దానిని పరమాణుపరంగా స్వీకరిస్తాము. మేము హ్యాండ్‌షేక్ సందేశాన్ని అందుకున్నామని మేము తనిఖీ చేస్తాము.

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

సంభాషణకర్త యొక్క స్వీకరించబడిన పేరు మాకు తెలిసినట్లు మేము తనిఖీ చేస్తాము. లేకపోతే, మేము కనెక్షన్‌ను విచ్ఛిన్నం చేస్తాము. మేము అతనితో ఇప్పటికే కనెక్షన్‌ని ఏర్పరచుకున్నామో లేదో తనిఖీ చేస్తాము (మళ్లీ సంభాషణకర్త మాకు కనెక్ట్ చేయమని ఆదేశాన్ని ఇచ్చాడు) మరియు దాన్ని మూసివేయండి. IN_QUEUES క్యూ సందేశం యొక్క టెక్స్ట్‌తో పైథాన్ స్ట్రింగ్‌లను కలిగి ఉంది, కానీ msg_sender కరోటిన్‌ని పని చేయడాన్ని ఆపివేయడానికి సిగ్నల్ ఇచ్చే ప్రత్యేక విలువ ఏదీ లేదు, తద్వారా అది లెగసీ 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     # }}}

msg_sender అవుట్‌గోయింగ్ మెసేజ్‌లను అంగీకరిస్తుంది (ఇన్ సాకెట్ నుండి క్యూలో ఉంది), వాటిని MsgText సందేశంగా సీరియలైజ్ చేస్తుంది మరియు వాటిని TCP కనెక్షన్ ద్వారా పంపుతుంది. ఇది ఏ క్షణంలోనైనా విరిగిపోవచ్చు - మేము దీన్ని స్పష్టంగా అడ్డగిస్తాము.

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

ముగింపులో, ఇనిషియేటర్ సాకెట్ నుండి సందేశాలను చదివే అనంతమైన లూప్‌లోకి ప్రవేశిస్తుంది. ఈ సందేశాలు వచన సందేశాలు కాదా అని తనిఖీ చేస్తుంది మరియు వాటిని OUT_QUEUES క్యూలో ఉంచుతుంది, దాని నుండి అవి సంబంధిత సంభాషణకర్త యొక్క అవుట్ సాకెట్‌కు పంపబడతాయి. మీరు సందేశాన్ని .read() మరియు డీకోడ్ చేయడం ఎందుకు చేయలేరు? ఎందుకంటే వినియోగదారు నుండి అనేక సందేశాలు ఆపరేటింగ్ సిస్టమ్ బఫర్‌లో సమగ్రపరచబడి ఒక TCP విభాగంలో పంపబడే అవకాశం ఉంది. మేము మొదటిదాన్ని డీకోడ్ చేయవచ్చు, ఆపై దానిలో కొంత భాగం బఫర్‌లో ఉండవచ్చు. ఏదైనా అసాధారణ పరిస్థితి ఏర్పడితే, మేము TCP కనెక్షన్‌ని మూసివేసి, msg_sender కరోటిన్‌ను ఆపివేస్తాము (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)

ప్రధాన కోడ్‌కి తిరిగి వెళ్దాం. ప్రోగ్రామ్ ప్రారంభమయ్యే సమయంలో అన్ని కొరౌటిన్‌లను సృష్టించిన తర్వాత, మేము TCP సర్వర్‌ను ప్రారంభిస్తాము. ప్రతి స్థాపించబడిన కనెక్షన్ కోసం, ఇది ప్రతిస్పందించే కరోటిన్‌ను సృష్టిస్తుంది.

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

సురక్షిత ప్రోటోకాల్

ఇది మా కమ్యూనికేషన్‌లను సురక్షితంగా ఉంచే సమయం. భద్రత అంటే ఏమిటి మరియు మనకు ఏమి కావాలి:

  • ప్రసారం చేయబడిన సందేశాల గోప్యత;
  • ప్రసారం చేయబడిన సందేశాల యొక్క ప్రామాణికత మరియు సమగ్రత - వాటి మార్పులు తప్పనిసరిగా గుర్తించబడాలి;
  • రీప్లే దాడుల నుండి రక్షణ - తప్పిపోయిన లేదా పునరావృతమయ్యే సందేశాల వాస్తవాన్ని గుర్తించాలి (మరియు మేము కనెక్షన్‌ని ముగించాలని నిర్ణయించుకున్నాము);
  • ముందుగా నమోదు చేసిన పబ్లిక్ కీలను ఉపయోగించి సంభాషణకర్తల గుర్తింపు మరియు ప్రామాణీకరణ - మేము స్నేహితుల నుండి స్నేహితుల నెట్‌వర్క్‌ని తయారు చేస్తున్నామని ముందే నిర్ణయించుకున్నాము. ప్రామాణీకరణ తర్వాత మాత్రమే మనం ఎవరితో కమ్యూనికేట్ చేస్తున్నామో అర్థం అవుతుంది;
  • లభ్యత ఖచ్చితమైన ఫార్వర్డ్ గోప్యత లక్షణాలు (PFS) - మా దీర్ఘకాల సంతకం కీని రాజీ చేయడం వలన మునుపటి కరస్పాండెన్స్‌లన్నింటినీ చదవగలిగే సామర్థ్యం ఉండదు. అడ్డగించిన ట్రాఫిక్‌ను రికార్డ్ చేయడం నిరుపయోగంగా మారుతుంది;
  • సందేశాల చెల్లుబాటు/చెల్లుబాటు (రవాణా మరియు హ్యాండ్‌షేక్) ఒక TCP సెషన్‌లో మాత్రమే. మరొక సెషన్ నుండి (అదే సంభాషణకర్తతో కూడా) సరిగ్గా సంతకం చేయబడిన/ప్రామాణీకరించబడిన సందేశాలను చొప్పించడం సాధ్యం కాదు;
  • నిష్క్రియ పరిశీలకుడు యూజర్ ఐడెంటిఫైయర్‌లు, ట్రాన్స్‌మిట్ చేయబడిన లాంగ్-లైవ్ పబ్లిక్ కీలు లేదా వాటి నుండి హ్యాష్‌లను చూడకూడదు. నిష్క్రియ పరిశీలకుడి నుండి ఒక నిర్దిష్ట అజ్ఞాతం.

ఆశ్చర్యకరంగా, దాదాపు ప్రతి ఒక్కరూ ఏదైనా హ్యాండ్‌షేక్ ప్రోటోకాల్‌లో ఈ కనిష్టాన్ని కలిగి ఉండాలని కోరుకుంటారు మరియు పైన పేర్కొన్న వాటిలో చాలా తక్కువ "స్వదేశీ" ప్రోటోకాల్‌ల కోసం చివరికి కలుసుకుంటారు. ఇప్పుడు మనం కొత్తగా ఏదీ కనిపెట్టడం లేదు. నేను ఖచ్చితంగా ఉపయోగించమని సిఫార్సు చేస్తాను నాయిస్ ఫ్రేమ్‌వర్క్ ప్రోటోకాల్‌లను రూపొందించడం కోసం, అయితే సరళమైనదాన్ని ఎంచుకుందాం.

రెండు అత్యంత ప్రజాదరణ పొందిన ప్రోటోకాల్‌లు:

  • TLS - బగ్‌లు, జాంబ్‌లు, దుర్బలత్వాలు, పేలవమైన ఆలోచన, సంక్లిష్టత మరియు లోపాల సుదీర్ఘ చరిత్ర కలిగిన చాలా క్లిష్టమైన ప్రోటోకాల్ (అయితే, దీనికి TLS 1.3తో సంబంధం లేదు). కానీ అది చాలా క్లిష్టంగా ఉన్నందున మేము దానిని పరిగణించము.
  • IPsec с ఇక్ — తీవ్రమైన క్రిప్టోగ్రాఫిక్ సమస్యలు లేవు, అయినప్పటికీ అవి కూడా సాధారణమైనవి కావు. మీరు IKEv1 మరియు IKEv2 గురించి చదివితే, వాటి మూలం STS, ISO/IEC IS 9798-3 మరియు SIGMA (SIGn-and-MAc) ప్రోటోకాల్‌లు - ఒక సాయంత్రం అమలు చేయడానికి సరిపోతుంది.

STS/ISO ప్రోటోకాల్‌ల అభివృద్ధిలో తాజా లింక్‌గా SIGMAలో ఏది మంచిది? ఇది మా అన్ని అవసరాలను (“దాచడం” ఇంటర్‌లోక్యూటర్ ఐడెంటిఫైయర్‌లతో సహా) తీరుస్తుంది మరియు క్రిప్టోగ్రాఫిక్ సమస్యలు ఏవీ లేవు. ఇది మినిమలిస్టిక్ - ప్రోటోకాల్ సందేశం నుండి కనీసం ఒక మూలకాన్ని తీసివేయడం దాని అభద్రతకు దారి తీస్తుంది.

సరళమైన హోమ్-గ్రోన్ ప్రోటోకాల్ నుండి SIGMAకి వెళ్దాం. మేము ఆసక్తి కలిగి ఉన్న అత్యంత ప్రాథమిక ఆపరేషన్ కీలక ఒప్పందం: ఇద్దరు పాల్గొనేవారు ఒకే విలువను అవుట్‌పుట్ చేసే ఒక ఫంక్షన్, దీనిని సిమెట్రిక్ కీగా ఉపయోగించవచ్చు. వివరాల్లోకి వెళ్లకుండా: ప్రతి పక్షాలు అశాశ్వతమైన (ఒక సెషన్‌లో మాత్రమే ఉపయోగించబడుతుంది) కీ జత (పబ్లిక్ మరియు ప్రైవేట్ కీలు), పబ్లిక్ కీలను మార్పిడి చేయడం, అగ్రిమెంట్ ఫంక్షన్‌కు కాల్ చేయడం, దాని ఇన్‌పుట్‌కు వారు తమ ప్రైవేట్ కీ మరియు పబ్లిక్‌ను పాస్ చేస్తారు సంభాషణకర్త యొక్క కీ.

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

ఎవరైనా మధ్యలో జంప్ చేయవచ్చు మరియు పబ్లిక్ కీలను వారి స్వంత వాటితో భర్తీ చేయవచ్చు - ఈ ప్రోటోకాల్‌లో సంభాషణకర్తల ప్రమాణీకరణ లేదు. దీర్ఘకాలం ఉండే కీలతో సంతకాన్ని జోడిద్దాం.

┌─────┐ ┌─────┐ │A│ గుర్తు(SignPrvA, (PubA)) │ ╔═ │──────────── ───────── ───── ────── PubA = లోడ్()║ │ │ ║PrvA, PubA = DHgen() ║ │ │ ╚═══════ ═══════ ══════ ══════════════ , గుర్తు(SignPrvB, (PubB)) │ │════════════ ═══════││─‗ ─────────── ───────────── ──│ ║SignPrvB, SignPubB = లోడ్( )║ │ │ │ ║PrvB, PubB│ ══════════ ══════════════ ══╝ ────┐ ╔ ═════╗ │ │ ║verify( SignPubB, ...)║ │ <───┘ ║Key = DH(Pr vA, PubB) ║ │ │ ╚══════════════════ ══╝ │ │ │

అటువంటి సంతకం పని చేయదు, ఎందుకంటే ఇది నిర్దిష్ట సెషన్‌తో ముడిపడి ఉండదు. ఇటువంటి సందేశాలు ఇతర పాల్గొనేవారితో సెషన్‌లకు కూడా "అనుకూలమైనవి". మొత్తం సందర్భం తప్పనిసరిగా సబ్‌స్క్రైబ్ చేయాలి. ఇది A నుండి మరొక సందేశాన్ని కూడా జోడించేలా చేస్తుంది.

అదనంగా, సంతకం క్రింద మీ స్వంత ఐడెంటిఫైయర్‌ని జోడించడం చాలా కీలకం, లేకుంటే మేము IdXXXని భర్తీ చేయవచ్చు మరియు మరొక తెలిసిన సంభాషణకర్త యొక్క కీతో సందేశాన్ని మళ్లీ సంతకం చేయవచ్చు. నిరోధించడానికి ప్రతిబింబ దాడులు, సంతకం క్రింద ఉన్న మూలకాలు వాటి అర్థం ప్రకారం స్పష్టంగా నిర్వచించబడిన ప్రదేశాలలో ఉండటం అవసరం: A సంకేతాలు (PubA, PubB) అయితే, B తప్పనిసరిగా సంతకం చేయాలి (PubB, PubA). ఇది ధారావాహిక డేటా యొక్క నిర్మాణం మరియు ఆకృతిని ఎంచుకోవడం యొక్క ప్రాముఖ్యతను కూడా తెలియజేస్తుంది. ఉదాహరణకు, ASN.1 DER ఎన్‌కోడింగ్‌లోని సెట్‌లు క్రమబద్ధీకరించబడ్డాయి: SET OF(PubA, PubB) SET OF(PubB, PubA)కి సమానంగా ఉంటుంది.

┌─────┐ ┌─────┐ │APeerA│ │PeerB│ └──┬──┘ │ ╔══════════ ───────────────────── ────────── ─────────────>│ ║SignPrvA, SignPubA = లోడ్()║ │ │ ║PrvA, PubA = DHgen│┕┕┕ ═ ═══════ ═══════════════╝ │IdB, PubB, సైన్ (SignPrvB, (IdB, PubA, PubB)) │ ═════ ═════ ═══════════╗ │<────────────────── ────────── ─────────│ ║SignPrvB, SignPubB = లోడ్()║ │ │ ║PrvB, PubB = DHgen()║ │┕══════ ═ ════════ ══════════╝ │ గుర్తు(SignPrvA, (IdA, PubB, PubA)) │ ╔════════════ ════╗ │─ ───────────────────────────. ───>│ ║verify(SignPubB, ...) ║ │ │ ║key = dh (prva, PUBB) ║ │ │ │

అయినప్పటికీ, ఈ సెషన్ కోసం మేము అదే భాగస్వామ్య కీని రూపొందించామని మేము ఇప్పటికీ "నిరూపించలేదు". సూత్రప్రాయంగా, మేము ఈ దశ లేకుండా చేయవచ్చు - మొట్టమొదటి రవాణా కనెక్షన్ చెల్లదు, కానీ హ్యాండ్‌షేక్ పూర్తయినప్పుడు, ప్రతిదీ నిజంగా అంగీకరించబడిందని మేము ఖచ్చితంగా అనుకుంటున్నాము. ప్రస్తుతం మేము ISO/IEC IS 9798-3 ప్రోటోకాల్‌ని కలిగి ఉన్నాము.

మేము రూపొందించిన కీపైనే సంతకం చేయవచ్చు. ఇది ప్రమాదకరం, ఎందుకంటే ఉపయోగించిన సిగ్నేచర్ అల్గారిథమ్‌లో లీక్‌లు ఉండే అవకాశం ఉంది (బిట్స్-పర్-సిగ్నేచర్, అయితే ఇప్పటికీ లీక్‌లు). డెరివేషన్ కీ యొక్క హ్యాష్‌పై సంతకం చేయడం సాధ్యపడుతుంది, అయితే డెరివేషన్ ఫంక్షన్‌పై బ్రూట్-ఫోర్స్ అటాక్‌లో డెరైవేటెడ్ కీ యొక్క హాష్‌ను కూడా లీక్ చేయడం విలువైనది. SIGMA పంపినవారి IDని ప్రమాణీకరించే MAC ఫంక్షన్‌ని ఉపయోగిస్తుంది.

┌─────┐ ┌─────┐ │APeerA│ │PeerB│ └──┬──┘ │ ╔══════════ ───────────────────── ────────── ──────────────────>│║SignPrvA, SignPubA = లోడ్()║ │ │ =A║Pr ╚ ═══════ ════════════════════╝ │IdB, PubB, గుర్తు ═══ │<────────────────────────── ────── ────────── ─│ ║SignPrvB, SignPubB = లోడ్()║ │ │ ║PrvB, PubB = DHgen() ║ │ │ ╚══════════ ═════════ * │ ║కీ = DH( PrvA, PubB) ║ │──────────────────── ── ──── ────────── ─────>│ ║verify(Key, IdB) ║ │ │ ║verify(SignPubB, ...)║ │ │ ╚════════ ═════ ═╝ │ │

ఆప్టిమైజేషన్‌గా, కొందరు తమ అశాశ్వత కీలను తిరిగి ఉపయోగించాలనుకోవచ్చు (ఇది PFSకి దురదృష్టకరం). ఉదాహరణకు, మేము ఒక కీ జతని రూపొందించాము, కనెక్ట్ చేయడానికి ప్రయత్నించాము, కానీ TCP అందుబాటులో లేదు లేదా ప్రోటోకాల్ మధ్యలో ఎక్కడో అంతరాయం కలిగింది. కొత్త జతపై వృధా అయిన ఎంట్రోపీ మరియు ప్రాసెసర్ వనరులను వృధా చేయడం సిగ్గుచేటు. అందువల్ల, మేము కుకీ అని పిలవబడే వాటిని పరిచయం చేస్తాము - ఇది అశాశ్వత పబ్లిక్ కీలను తిరిగి ఉపయోగిస్తున్నప్పుడు సాధ్యమయ్యే యాదృచ్ఛిక రీప్లే దాడుల నుండి రక్షించే ఒక నకిలీ-రాండమ్ విలువ. కుక్కీ మరియు ఎఫెమెరల్ పబ్లిక్ కీ మధ్య బైండింగ్ కారణంగా, వ్యతిరేక పక్షం యొక్క పబ్లిక్ కీని అనవసరంగా సంతకం నుండి తీసివేయవచ్చు.

┌─────┐ ┌─────┐ │PeerA│ │PeerB│ └──┬──┘ కుకీఏ │ ╔════════ ──────────────────── ────────── ───────────────────────────. ─>│ ║SignPrvA, SignPubA = లోడ్( )║ │ │ ║PrvA, PubA = DHgen() ║ │ │ ╚════════════════════ ══╝ │IdB, PubB, CookieB , గుర్తు(SignPrvB, (CookieA, CookieB, PubB)), MAC(IdB) │ ╔══════════════════════ ═ ╗ │< ───────────────────────────. ────────── ────────────────────│ ║SignPrvB, SignPubB = లోడ్ ()║ ││ │ ╚══════ ════════════════════╝ ││ ╔═══ ═══════╗ │ గుర్తు( SignPrvA, (CookieB, CookieA, PubA)), MAC(IdA) │ ║Key = DH(PrvA, PubB) ║ │─────────── ─ ── ───────────────────────────. ───────>│ ║ ధృవీకరించు(కీ, IdB) ║ │ │ ║verify(SignPubB, ...)║ │ │ ╚═════════════════════ │ │

చివరగా, మేము మా సంభాషణ భాగస్వాముల గోప్యతను నిష్క్రియ పరిశీలకుడి నుండి పొందాలనుకుంటున్నాము. దీన్ని చేయడానికి, SIGMA మొదట అశాశ్వత కీలను మార్పిడి చేయాలని మరియు ప్రమాణీకరించే మరియు గుర్తించే సందేశాలను గుప్తీకరించడానికి ఒక సాధారణ కీని అభివృద్ధి చేయాలని ప్రతిపాదించింది. SIGMA రెండు ఎంపికలను వివరిస్తుంది:

  • SIGMA-I - సక్రియ దాడుల నుండి ఇనిషియేటర్‌ను రక్షిస్తుంది, ప్రతిస్పందనదారుని నిష్క్రియాత్మకమైన వాటి నుండి రక్షిస్తుంది: ఇనిషియేటర్ ప్రతిస్పందనదారుని ప్రమాణీకరిస్తుంది మరియు ఏదైనా సరిపోలకపోతే, అది దాని గుర్తింపును ఇవ్వదు. అతనితో క్రియాశీల ప్రోటోకాల్ ప్రారంభించబడితే ప్రతివాది తన గుర్తింపును ఇస్తాడు. నిష్క్రియ పరిశీలకుడు ఏమీ నేర్చుకోడు;
    SIGMA-R - క్రియాశీల దాడుల నుండి ప్రతిస్పందనదారుని, నిష్క్రియాత్మకమైన వాటి నుండి ప్రారంభించేవారిని రక్షిస్తుంది. ప్రతిదీ సరిగ్గా వ్యతిరేకం, కానీ ఈ ప్రోటోకాల్‌లో నాలుగు హ్యాండ్‌షేక్ సందేశాలు ఇప్పటికే ప్రసారం చేయబడ్డాయి.

    క్లయింట్-సర్వర్ సుపరిచిత విషయాల నుండి మేము ఆశించిన దానితో సమానంగా ఉన్నందున మేము SIGMA-Iని ఎంచుకుంటాము: క్లయింట్ ప్రామాణీకరించబడిన సర్వర్ ద్వారా మాత్రమే గుర్తించబడతారు మరియు ప్రతి ఒక్కరికి సర్వర్ ఇప్పటికే తెలుసు. అంతేకాకుండా తక్కువ హ్యాండ్‌షేక్ మెసేజ్‌ల కారణంగా దీన్ని అమలు చేయడం సులభం. మేము ప్రోటోకాల్‌కు జోడించేది సందేశంలో కొంత భాగాన్ని గుప్తీకరించడం మరియు ఐడెంటిఫైయర్ Aని చివరి సందేశం యొక్క గుప్తీకరించిన భాగానికి బదిలీ చేయడం:

    పబ్ఏ, కుకీఏ │ ╔══════════ ─────────── ──────────────────────────── ─────────── ───── ──────>│ ║SignPrvA , SignPubA = load()║ │ │ ║PrvA, PubA = DHgen()│║║ ═════════ ═════════ ════╝ │ │ పబ్బి, కుకీబ్, ఎన్.ఎన్ ═════════════════════│< ────────── ───── ────────── ║SignP rvB, SignPubB = load()║ │ │ │ ║ PrvB, PubB│┕│ ═════════ ════════════════╝ │ │ ══╗ │ Enc((IdA, సైన్( SignPrvA, (CookieB, CookieA, PubA)), MAC(IdA)) │ ║Key = DH(PrvA, PubB) ║ │────────────── ── ─────────────────────────── ─────────── ──────>│║verify(Key, IdB) ║ │ │ ║verify( SignPubB, ...)║ │ │ ╚═══════ ═════ ══╝ │ │
    
    • సంతకం కోసం GOST R ఉపయోగించబడుతుంది 34.10-2012 256-బిట్ కీలతో అల్గోరిథం.
    • పబ్లిక్ కీని రూపొందించడానికి, 34.10/2012/XNUMX VKO ఉపయోగించబడుతుంది.
    • CMAC MACగా ఉపయోగించబడుతుంది. సాంకేతికంగా, ఇది GOST R 34.13-2015లో వివరించబడిన బ్లాక్ సాంకేతికలిపి యొక్క ప్రత్యేక మోడ్ ఆపరేషన్. ఈ మోడ్ కోసం ఎన్‌క్రిప్షన్ ఫంక్షన్‌గా - మిడత (34.12-2015).
    • అతని పబ్లిక్ కీ యొక్క హాష్ సంభాషణకర్త యొక్క ఐడెంటిఫైయర్‌గా ఉపయోగించబడుతుంది. హాష్‌గా ఉపయోగించబడుతుంది స్ట్రిబోగ్-256 (34.11/2012/256 XNUMX బిట్స్).

    కరచాలనం తర్వాత, మేము భాగస్వామ్య కీని అంగీకరిస్తాము. రవాణా సందేశాల యొక్క ప్రామాణీకరించబడిన ఎన్క్రిప్షన్ కోసం మేము దీనిని ఉపయోగించవచ్చు. ఈ భాగం చాలా సులభం మరియు పొరపాటు చేయడం కష్టం: మేము మెసేజ్ కౌంటర్‌ని పెంచుతాము, సందేశాన్ని గుప్తీకరించాము, కౌంటర్‌ని ప్రామాణీకరించాము (MAC) మరియు సాంకేతికత, పంపండి. సందేశాన్ని స్వీకరించినప్పుడు, మేము కౌంటర్ ఆశించిన విలువను కలిగి ఉందో లేదో తనిఖీ చేస్తాము, కౌంటర్‌తో సాంకేతికలిపిని ప్రమాణీకరించి, దానిని డీక్రిప్ట్ చేస్తాము. హ్యాండ్‌షేక్ సందేశాలను, రవాణా సందేశాలను గుప్తీకరించడానికి మరియు వాటిని ఎలా ప్రామాణీకరించడానికి నేను ఏ కీని ఉపయోగించాలి? ఈ పనులన్నింటికీ ఒక కీని ఉపయోగించడం ప్రమాదకరం మరియు తెలివితక్కువది. ప్రత్యేక ఫంక్షన్లను ఉపయోగించి కీలను రూపొందించడం అవసరం కెడిఎఫ్ (కీ డెరివేషన్ ఫంక్షన్). మళ్ళీ, వెంట్రుకలను విభజించి, ఏదైనా కనిపెట్టవద్దు: HKDF చాలా కాలంగా తెలుసు, బాగా పరిశోధించబడింది మరియు ఎటువంటి సమస్యలు లేవు. దురదృష్టవశాత్తూ, స్థానిక పైథాన్ లైబ్రరీకి ఈ ఫంక్షన్ లేదు, కాబట్టి మేము ఉపయోగిస్తాము hkdf ప్లాస్టిక్ సంచి. HKDF అంతర్గతంగా ఉపయోగిస్తుంది HMAC, ఇది హాష్ ఫంక్షన్‌ను ఉపయోగిస్తుంది. వికీపీడియా పేజీలో పైథాన్‌లో ఒక ఉదాహరణ అమలు కేవలం కొన్ని పంక్తుల కోడ్‌ను తీసుకుంటుంది. 34.10/2012/256 విషయంలో వలె, మేము Stribog-XNUMXని హాష్ ఫంక్షన్‌గా ఉపయోగిస్తాము. మా కీలక ఒప్పంద ఫంక్షన్ యొక్క అవుట్‌పుట్‌ను సెషన్ కీ అని పిలుస్తారు, దాని నుండి తప్పిపోయిన సిమెట్రిక్‌లు ఉత్పత్తి చేయబడతాయి:

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

    నిర్మాణాలు/పథకాలు

    ఈ మొత్తం డేటాను ప్రసారం చేయడానికి ఇప్పుడు మన వద్ద ఉన్న ASN.1 నిర్మాణాలను చూద్దాం:

    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)
    

    హ్యాండ్‌షేక్‌టిబిఎస్ అంటే సంతకం చేయబడుతుంది. హ్యాండ్‌షేక్‌టిబిఇ - ఏది ఎన్‌క్రిప్ట్ చేయబడుతుంది. MsgHandshake1లోని ukm ఫీల్డ్‌పై నేను మీ దృష్టిని ఆకర్షిస్తున్నాను. 34.10 VKO, ఉత్పత్తి చేయబడిన కీల యొక్క మరింత ఎక్కువ రాండమైజేషన్ కోసం, UKM (యూజర్ కీయింగ్ మెటీరియల్) పరామితిని కలిగి ఉంటుంది - కేవలం అదనపు ఎంట్రోపీ.

    కోడ్‌కి క్రిప్టోగ్రఫీని జోడిస్తోంది

    అసలు కోడ్‌లో చేసిన మార్పులను మాత్రమే పరిశీలిద్దాం, ఎందుకంటే ఫ్రేమ్‌వర్క్ అలాగే ఉంది (వాస్తవానికి, తుది అమలు మొదట వ్రాయబడింది, ఆపై అన్ని క్రిప్టోగ్రఫీ దాని నుండి కత్తిరించబడింది).

    పబ్లిక్ కీలను ఉపయోగించి ఇంటర్‌లోక్యూటర్‌ల ప్రామాణీకరణ మరియు గుర్తింపు నిర్వహించబడుతుంది కాబట్టి, అవి ఇప్పుడు ఎక్కడా ఎక్కువ కాలం నిల్వ చేయబడాలి. సరళత కోసం, మేము JSONని ఇలా ఉపయోగిస్తాము:

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

    మా - మా కీ జత, హెక్సాడెసిమల్ ప్రైవేట్ మరియు పబ్లిక్ కీలు. వారి — సంభాషణకర్తల పేర్లు మరియు వారి పబ్లిక్ కీలు. కమాండ్ లైన్ ఆర్గ్యుమెంట్‌లను మారుద్దాం మరియు 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),
        }
    # }}}
    

    34.10 అల్గోరిథం యొక్క ప్రైవేట్ కీ యాదృచ్ఛిక సంఖ్య. 256-బిట్ ఎలిప్టిక్ వక్రతలకు 256-బిట్ పరిమాణం. PyGOST బైట్‌ల సెట్‌తో పని చేయదు, కానీ దానితో పెద్ద సంఖ్యలో, కాబట్టి మన ప్రైవేట్ కీ (urandom(32))ని gost3410.prv_unmarshal() ఉపయోగించి సంఖ్యగా మార్చాలి. పబ్లిక్ కీ gost3410.public_key()ని ఉపయోగించి ప్రైవేట్ కీ నుండి నిర్ణయాత్మకంగా నిర్ణయించబడుతుంది. పబ్లిక్ కీ 34.10 అనేది gost3410.pub_marshal()ని ఉపయోగించి నిల్వ మరియు ప్రసార సౌలభ్యం కోసం బైట్ సీక్వెన్స్‌గా మార్చాల్సిన రెండు పెద్ద సంఖ్యలు.

    JSON ఫైల్‌ని చదివిన తర్వాత, పబ్లిక్ కీలను gost3410.pub_unmarshal() ఉపయోగించి తిరిగి మార్చాలి. మేము పబ్లిక్ కీ నుండి హాష్ రూపంలో ఇంటర్‌లోక్యూటర్‌ల ఐడెంటిఫైయర్‌లను స్వీకరిస్తాము కాబట్టి, వాటిని వెంటనే ముందుగానే లెక్కించి, శీఘ్ర శోధన కోసం డిక్షనరీలో ఉంచవచ్చు. Stribog-256 హాష్ అనేది gost34112012256.GOST34112012256(), ఇది హాష్ ఫంక్షన్‌ల హాష్లిబ్ ఇంటర్‌ఫేస్‌ను పూర్తిగా సంతృప్తిపరుస్తుంది.

    ఇనిషియేటర్ కొరోటిన్ ఎలా మారింది? ప్రతిదీ హ్యాండ్‌షేక్ పథకం ప్రకారం ఉంది: మేము కుకీని (128-బిట్ పుష్కలంగా ఉంది), ఒక ఎఫెమెరల్ కీ జత 34.10ని రూపొందిస్తాము, ఇది 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()
    

    • మేము ప్రతిస్పందన కోసం వేచి ఉన్నాము మరియు ఇన్‌కమింగ్ సందేశాన్ని డీకోడ్ చేస్తాము;
    • మీరు హ్యాండ్‌షేక్ 1 పొందారని నిర్ధారించుకోండి;
    • వ్యతిరేక పక్షం యొక్క అశాశ్వత పబ్లిక్ కీని డీకోడ్ చేయండి మరియు సెషన్ కీని లెక్కించండి;
    • మేము సందేశంలోని TBE భాగాన్ని ప్రాసెస్ చేయడానికి అవసరమైన సిమెట్రిక్ కీలను రూపొందిస్తాము.

     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 అనేది 64-బిట్ సంఖ్య (urandom(8)), దీనికి gost3410_vko.ukm_unmarshal()ని ఉపయోగించి దాని బైట్ ప్రాతినిధ్యం నుండి డీరియలైజేషన్ కూడా అవసరం. 34.10/2012/256 3410-బిట్ కోసం VKO ఫంక్షన్ gost34102012256_vko.kek_XNUMX() (KEK - ఎన్‌క్రిప్షన్ కీ).

    జనరేట్ చేయబడిన సెషన్ కీ ఇప్పటికే 256-బిట్ సూడో-రాండమ్ బైట్ సీక్వెన్స్. అందువలన, ఇది వెంటనే HKDF ఫంక్షన్లలో ఉపయోగించబడుతుంది. GOST34112012256 hashlib ఇంటర్‌ఫేస్‌ను సంతృప్తిపరుస్తుంది కాబట్టి, ఇది వెంటనే Hkdf తరగతిలో ఉపయోగించబడుతుంది. మేము ఉప్పు (Hkdf యొక్క మొదటి ఆర్గ్యుమెంట్)ని పేర్కొనము, ఎందుకంటే పాల్గొనే కీ జతల యొక్క అశాశ్వతత కారణంగా ఉత్పత్తి చేయబడిన కీ, ప్రతి సెషన్‌కు భిన్నంగా ఉంటుంది మరియు ఇప్పటికే తగినంత ఎంట్రోపీని కలిగి ఉంటుంది. kdf.expand() డిఫాల్ట్‌గా ఇప్పటికే గ్రాస్‌షాపర్‌కు అవసరమైన 256-బిట్ కీలను ఉత్పత్తి చేస్తుంది.

    తర్వాత, ఇన్‌కమింగ్ సందేశంలోని TBE మరియు TBS భాగాలు తనిఖీ చేయబడతాయి:

    • ఇన్‌కమింగ్ సైఫర్‌టెక్స్ట్‌పై MAC లెక్కించబడుతుంది మరియు తనిఖీ చేయబడుతుంది;
    • సాంకేతికలిపి డిక్రిప్ట్ చేయబడింది;
    • TBE నిర్మాణం డీకోడ్ చేయబడింది;
    • సంభాషణకర్త యొక్క ఐడెంటిఫైయర్ దాని నుండి తీసుకోబడింది మరియు అతను మనకు తెలిసినవాడో లేదో తనిఖీ చేయబడుతుంది;
    • ఈ ఐడెంటిఫైయర్‌పై MAC లెక్కించబడుతుంది మరియు తనిఖీ చేయబడుతుంది;
    • TBS నిర్మాణంపై సంతకం ధృవీకరించబడింది, ఇందులో రెండు పార్టీల కుక్కీ మరియు వ్యతిరేక పక్షం యొక్క పబ్లిక్ ఎఫెమెరల్ కీ ఉన్నాయి. సంభాషణకర్త యొక్క దీర్ఘకాల సంతకం కీ ద్వారా సంతకం ధృవీకరించబడుతుంది.

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

    నేను పైన వ్రాసినట్లుగా, 34.13/2015/XNUMX వివిధ రకాలను వివరిస్తుంది సాంకేతికలిపి ఆపరేటింగ్ మోడ్‌లను నిరోధించండి 34.12/2015/3413 నుండి. వాటిలో అనుకరణ ఇన్సర్ట్‌లు మరియు MAC లెక్కలను రూపొందించడానికి ఒక మోడ్ ఉంది. PyGOSTలో ఇది gost34.12.mac(). ఈ మోడ్‌కు ఎన్‌క్రిప్షన్ ఫంక్షన్ (ఒక బ్లాక్ డేటాను స్వీకరించడం మరియు తిరిగి ఇవ్వడం), ఎన్‌క్రిప్షన్ బ్లాక్ పరిమాణం మరియు వాస్తవానికి డేటా కూడా పాస్ చేయడం అవసరం. మీరు ఎన్‌క్రిప్షన్ బ్లాక్ పరిమాణాన్ని ఎందుకు హార్డ్‌కోడ్ చేయలేరు? 2015/128/64 XNUMX-బిట్ గ్రాస్‌షాపర్ సాంకేతికలిపిని మాత్రమే కాకుండా, XNUMX-బిట్‌ను కూడా వివరిస్తుంది శిలాద్రవం - కొద్దిగా సవరించిన GOST 28147-89, KGBలో తిరిగి సృష్టించబడింది మరియు ఇప్పటికీ అత్యధిక భద్రతా థ్రెషోల్డ్‌లలో ఒకటిగా ఉంది.

    Kuznechik gost.3412.GOST3412Kuznechik(కీ)కి కాల్ చేయడం ద్వారా ప్రారంభించబడింది మరియు 34.13 ఫంక్షన్‌లకు పాస్ చేయడానికి అనువైన .encrypt()/.decrypt() పద్ధతులతో ఒక వస్తువును అందిస్తుంది. MAC ఈ క్రింది విధంగా లెక్కించబడుతుంది: gost3413.mac(GOST3412Kuznechik(key).encrypt, KUZNECHIK_BLOCKSIZE, సాంకేతికలిపి). లెక్కించిన మరియు స్వీకరించిన MACని సరిపోల్చడానికి, మీరు సాధారణ పోలిక (==) బైట్ స్ట్రింగ్‌లను ఉపయోగించలేరు, ఎందుకంటే ఈ ఆపరేషన్ పోలిక సమయాన్ని లీక్ చేస్తుంది, ఇది సాధారణ సందర్భంలో, ప్రాణాంతకమైన దుర్బలత్వాలకు దారి తీస్తుంది మృగం TLS పై దాడులు. దీని కోసం పైథాన్‌కు hmac.compare_digest అనే ప్రత్యేక ఫంక్షన్ ఉంది.

    బ్లాక్ సైఫర్ ఫంక్షన్ ఒక బ్లాక్ డేటాను మాత్రమే గుప్తీకరించగలదు. పెద్ద సంఖ్య కోసం, మరియు పొడవు యొక్క మల్టిపుల్ కాకపోయినా, ఎన్‌క్రిప్షన్ మోడ్‌ను ఉపయోగించడం అవసరం. 34.13-2015 కింది వాటిని వివరిస్తుంది: ECB, CTR, OFB, CBC, CFB. ప్రతి దాని స్వంత ఆమోదయోగ్యమైన అప్లికేషన్ మరియు లక్షణాలు ఉన్నాయి. దురదృష్టవశాత్తు, మేము ఇప్పటికీ ప్రామాణికతను కలిగి లేము ప్రామాణీకరించబడిన ఎన్క్రిప్షన్ మోడ్‌లు (CCM, OCB, GCM మరియు ఇలాంటివి) - కనీసం MACని మనమే జోడించుకోవలసి వస్తుంది. నేను ఎంచుకున్న కౌంటర్ మోడ్ (CTR): దీనికి బ్లాక్ పరిమాణానికి పాడింగ్ అవసరం లేదు, సమాంతరంగా చేయవచ్చు, ఎన్‌క్రిప్షన్ ఫంక్షన్‌ను మాత్రమే ఉపయోగిస్తుంది, పెద్ద సంఖ్యలో సందేశాలను గుప్తీకరించడానికి సురక్షితంగా ఉపయోగించవచ్చు (CBC వలె కాకుండా, ఇది చాలా త్వరగా ఘర్షణలను కలిగి ఉంటుంది).

    .mac(), .ctr() ఇలాంటి ఇన్‌పుట్‌ని తీసుకుంటుంది: సాంకేతికలిపి = gost3413.ctr(GOST3412Kuznechik(key).encrypt, KUZNECHIK_BLOCKSIZE, plaintext, iv). ఎన్‌క్రిప్షన్ బ్లాక్‌లో సరిగ్గా సగం పొడవు ఉండే ఇనిషియలైజేషన్ వెక్టార్‌ని పేర్కొనడం అవసరం. మా ఎన్‌క్రిప్షన్ కీ ఒక సందేశాన్ని గుప్తీకరించడానికి మాత్రమే ఉపయోగించబడితే (అనేక బ్లాక్‌ల నుండి), అప్పుడు సున్నా ప్రారంభ వెక్టార్‌ని సెట్ చేయడం సురక్షితం. హ్యాండ్‌షేక్ సందేశాలను గుప్తీకరించడానికి, మేము ప్రతిసారీ ప్రత్యేక కీని ఉపయోగిస్తాము.

    సంతకాన్ని ధృవీకరించడం gost3410.verify() చిన్నవిషయం: మేము పని చేస్తున్న దీర్ఘవృత్తాకార వక్రరేఖను (మేము దానిని మా GOSTIM ప్రోటోకాల్‌లో రికార్డ్ చేస్తాము), సంతకం చేసిన వ్యక్తి యొక్క పబ్లిక్ కీ (ఇది రెండు టుపుల్ అని మర్చిపోవద్దు పెద్ద సంఖ్యలు, మరియు బైట్ స్ట్రింగ్ కాదు), 34.11/2012/XNUMX హాష్ మరియు సంతకం కూడా.

    తర్వాత, ఇనిషియేటర్‌లో మేము హ్యాండ్‌షేక్ మెసేజ్‌ని సిద్ధం చేసి, హ్యాండ్‌షేక్2కి పంపుతాము, ధృవీకరణ సమయంలో మేము చేసిన అదే చర్యలను సుష్టంగా చేస్తాము: తనిఖీ చేయడానికి బదులుగా మా కీలపై సంతకం చేయడం మొదలైనవి...

     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)
     

    సెషన్ స్థాపించబడినప్పుడు, రవాణా కీలు ఉత్పత్తి చేయబడతాయి (గుప్తీకరణ కోసం, ప్రమాణీకరణ కోసం, ప్రతి పక్షాల కోసం ఒక ప్రత్యేక కీ), మరియు 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     # }}}
    

    msg_sender coroutine ఇప్పుడు TCP కనెక్షన్‌లో సందేశాలను పంపే ముందు వాటిని గుప్తీకరిస్తుంది. ప్రతి సందేశం ఒక మోనోటోనికల్‌గా పెరుగుతున్న నాన్స్‌ని కలిగి ఉంటుంది, ఇది కౌంటర్ మోడ్‌లో గుప్తీకరించబడినప్పుడు ప్రారంభ వెక్టర్ కూడా. ప్రతి మెసేజ్ మరియు మెసేజ్ బ్లాక్‌కి వేరే కౌంటర్ వాల్యూ ఉంటుందని హామీ ఇవ్వబడుతుంది.

    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
    

    ఇన్‌కమింగ్ సందేశాలు msg_receiver కొరోటిన్ ద్వారా ప్రాసెస్ చేయబడతాయి, ఇది ప్రమాణీకరణ మరియు డిక్రిప్షన్‌ను నిర్వహిస్తుంది:

    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)
    

    తీర్మానం

    GOSTIM విద్యా ప్రయోజనాల కోసం ప్రత్యేకంగా ఉపయోగించబడుతుంది (ఇది పరీక్షల ద్వారా కవర్ చేయబడదు కాబట్టి, కనీసం)! ప్రోగ్రామ్ యొక్క సోర్స్ కోడ్‌ను డౌన్‌లోడ్ చేసుకోవచ్చు ఇక్కడ (Стрибог-256 хэш: 995bbd368c04e50a481d138c5fa2e43ec7c89bc77743ba8dbabee1fde45de120). Как и все мои проекты, типа గోగోస్ట్, పైడెరాస్ఎన్, ఎన్‌ఎన్‌సిపి, GoVPN, GOSTIM పూర్తిగా ఉంది ఉచిత సాఫ్ట్వేర్నిబంధనల ప్రకారం పంపిణీ చేయబడింది GPLv3 +.

    సెర్గీ మాట్వీవ్, సైఫర్‌పంక్, సభ్యుడు SPO ఫౌండేషన్, పైథాన్/గో-డెవలపర్, చీఫ్ స్పెషలిస్ట్ ఫెడరల్ స్టేట్ యూనిటరీ ఎంటర్‌ప్రైజ్ "STC "అట్లాస్".

మూలం: www.habr.com

ఒక వ్యాఖ్యను జోడించండి