ProHoster > Blog > yönetim > GOSTIM: GOST kriptografisi ile bir akşam P2P F2F E2EE IM
GOSTIM: GOST kriptografisi ile bir akşam P2P F2F E2EE IM
Geliştirici olmak PyGOST kütüphaneleri (saf Python'daki GOST kriptografik ilkelleri) kullandığımda, sık sık en basit güvenli mesajlaşmanın nasıl uygulanacağına dair sorular alıyorum. Birçok kişi uygulamalı kriptografinin oldukça basit olduğunu düşünür ve blok şifre üzerinden .encrypt() işlevinin çağrılması, şifrenin bir iletişim kanalı üzerinden güvenli bir şekilde gönderilmesi için yeterli olacaktır. Diğerleri uygulamalı kriptografinin az sayıda kişinin kaderi olduğuna inanıyor ve Telegram gibi zengin şirketlerin olimpiyat matematikçilerine sahip olması kabul edilebilir. uygulayamıyorum güvenli protokol.
Bütün bunlar, kriptografik protokolleri ve güvenli IM'yi uygulamanın o kadar da zor bir iş olmadığını göstermek için beni bu makaleyi yazmaya sevk etti. Ancak kendi kimlik doğrulama ve anahtar anlaşma protokollerinizi icat etmeye değmez.
Makale yazılacak peer-to-peer, arkadaştan arkadaşa, uçtan uca şifrelenmiş ile anlık mesajlaşma SIGMA-I kimlik doğrulama ve anahtar anlaşma protokolü (bu esasa göre uygulanır) IPsec IKE), yalnızca GOST şifreleme algoritmaları PyGOST kütüphanesi ve ASN.1 mesaj kodlama kütüphanesi kullanılarak PyDERASN (bunun hakkında zaten daha önce yazdı). Önkoşul: Bir akşam (veya iş günü) içinde sıfırdan yazılabilecek kadar basit olmalıdır, aksi halde artık basit bir program değildir. Muhtemelen hataları, gereksiz komplikasyonları, eksiklikleri var, ayrıca bu benim asyncio kütüphanesini kullanan ilk programım.
Sohbet tasarımı
Öncelikle IM'mizin nasıl görüneceğini anlamamız gerekiyor. Basit olması açısından, katılımcıların herhangi bir şekilde keşfedilmediği eşler arası bir ağ olmasına izin verin. Muhatapla iletişim kurmak için hangi adrese bağlanılacağını kişisel olarak belirteceğiz: bağlantı noktası.
Şu anda, iki rastgele bilgisayar arasında doğrudan iletişimin mevcut olduğu varsayımının IM'nin pratikte uygulanabilirliği üzerinde önemli bir sınırlama olduğunu anlıyorum. Ancak geliştiriciler her türlü NAT geçiş desteğini ne kadar çok uygularsa, IPv4 İnternet'te o kadar uzun süre kalacağız ve keyfi bilgisayarlar arasında iletişim kurma olasılığı da azalıyor. Evde ve işyerinizde IPv6 eksikliğine ne kadar dayanabilirsiniz?
Arkadaştan arkadaşa bir ağımız olacak: olası tüm muhatapların önceden bilinmesi gerekiyor. Birincisi, bu her şeyi büyük ölçüde basitleştirir: kendimizi tanıttık, adı/anahtarı bulduk veya bulamadık, bağlantımız kesildi veya muhatabı bilerek çalışmaya devam ettik. İkincisi, genel olarak güvenlidir ve birçok saldırıyı ortadan kaldırır.
IM arayüzü klasik çözümlere yakın olacak berbat projelerMinimalizmleri ve Unix tarzı felsefeleri nedeniyle gerçekten hoşuma gidiyor. IM programı, her muhatap için üç Unix etki alanı soketine sahip bir dizin oluşturur:
içinde - muhataplara gönderilen mesajlar buna kaydedilir;
dışarı - muhataptan alınan mesajlar ondan okunur;
durum - ondan okuyarak muhatabın şu anda bağlı olup olmadığını, bağlantı adresini/bağlantı noktasını öğreniriz.
Ayrıca uzak muhatapla bağlantı başlattığımız ana bilgisayar portunu yazarak bir bağlantı soketi oluşturulur.
|-- alice
| |-- in
| |-- out
| `-- state
|-- bob
| |-- in
| |-- out
| `-- state
`- conn
Bu yaklaşım, IM aktarımı ve kullanıcı arayüzünün bağımsız uygulamalarını yapmanıza olanak tanır, çünkü arkadaş yoktur, herkesi memnun edemezsiniz. Kullanma tmux ve / veya çok kuyruklusözdizimi vurgulamalı çoklu pencere arayüzüne sahip olabilirsiniz. Ve yardımıyla sarma GNU Readline uyumlu bir mesaj giriş satırı alabilirsiniz.
Aslında berbat projeler FIFO dosyalarını kullanır. Kişisel olarak, özel başlıklardan elle yazılmış bir arka plan olmadan, eşzamansız olarak dosyalarla rekabetçi bir şekilde nasıl çalışılacağını anlayamadım (dili bu tür şeyler için uzun zamandır kullanıyorum) Go). Bu nedenle Unix alan soketleriyle yetinmeye karar verdim. Ne yazık ki bu, echo 2001:470:dead::babe 6666 > conn yapılmasını imkansız hale getiriyor. Bu sorunu kullanarak çözdüm sokat: echo 2001:470:ölü::bebek 6666 | socat - UNIX-CONNECT:bağlantı, socat READLINE UNIX-CONNECT:alice/in.
Orijinal güvenli olmayan protokol
TCP aktarım olarak kullanılır: teslimatı ve sırasını garanti eder. UDP ikisini de garanti etmez (ki bu, kriptografi kullanıldığında faydalı olabilir), ancak desteği garanti eder SCTP Python kutudan çıkmıyor.
Ne yazık ki TCP'de mesaj kavramı yoktur, yalnızca bayt akışı vardır. Bu nedenle mesajların bu başlıkta kendi aralarında paylaşılabilmesi için bir formatın getirilmesi gerekiyor. Satır besleme karakterini kullanmayı kabul edebiliriz. Yeni başlayanlar için sorun değil ama mesajlarımızı şifrelemeye başladığımızda bu karakter şifreli metnin herhangi bir yerinde görünebilir. Bu nedenle ağlarda popüler protokoller, mesajın uzunluğunu bayt cinsinden ilk gönderen protokollerdir. Örneğin, Python'da benzer bir formatla çalışmanıza olanak tanıyan xdrlib bulunur. XDR.
TCP okumayla doğru ve verimli çalışmayacağız - kodu basitleştireceğiz. Mesajın tamamını çözene kadar soketteki verileri sonsuz bir döngüde okuruz. XML'li JSON da bu yaklaşımın formatı olarak kullanılabilir. Ancak kriptografi eklendiğinde, verilerin imzalanması ve doğrulanması gerekir; bu da nesnelerin bayt bayt özdeş temsilini gerektirir; JSON/XML bunu sağlamaz (döküm sonuçları farklılık gösterebilir).
XDR bu göreve uygundur ancak ben DER kodlamalı ASN.1'i seçiyorum ve PyDERASN kütüphane, çünkü elimizde çalışmanın daha keyifli ve rahat olduğu yüksek seviyeli nesneler olacak. Şemasızın aksine kod, Mesaj Paketi veya CBORASN.1, verileri sabit kodlanmış bir şemaya göre otomatik olarak kontrol edecektir.
Alınan mesaj Msg olacaktır: ya bir MsgText metni (şimdilik tek metin alanıyla birlikte) ya da bir MsgHandshake el sıkışma mesajı (muhatabın adını içerir). Şimdi aşırı karmaşık görünüyor, ancak bu gelecek için bir temel.
Kendi adınızı belirleyin (--adımız alice). Beklenen tüm muhataplar virgülle ayrılmış olarak listelenir (—isimleri bob,eve). Muhatapların her biri için Unix soketlerine sahip bir dizin ve ayrıca her giriş, çıkış durumu için bir eşyordam oluşturulur:
Daha sonra uzak taraftan bir yanıt bekler. Msg ASN.1 şemasını kullanarak gelen yanıtın kodunu çözmeye çalışır. Mesajın tamamının tek bir TCP segmentinde gönderileceğini ve .read() çağrıldığında onu atomik olarak alacağımızı varsayıyoruz. El sıkışma mesajını alıp almadığımızı kontrol ediyoruz.
Muhatabın alınan adının bizim tarafımızdan bilindiğini kontrol ediyoruz. Değilse bağlantıyı keseriz. Onunla daha önce bir bağlantı kurup kurmadığımızı kontrol ediyoruz (muhatap yine bize bağlanma komutunu verdi) ve kapatıyoruz. IN_QUEUES kuyruğu, mesajın metniyle birlikte Python dizelerini tutar, ancak eski TCP bağlantısıyla ilişkili yazıcısını unutması için msg_sender eşyordamının çalışmayı durdurmasını işaret eden özel bir Yok değeri vardır.
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 giden mesajları kabul eder (bir giriş soketinden sıraya alınır), bunları bir MsgText mesajı halinde serileştirir ve bir TCP bağlantısı üzerinden gönderir. Her an kırılabilir - bunu açıkça engelliyoruz.
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))
Sonunda, başlatıcı soketten gelen mesajların okunması için sonsuz bir döngüye girer. Bu mesajların kısa mesaj olup olmadığını kontrol eder ve bunları ilgili muhatabın çıkış soketine gönderilecekleri OUT_QUEUES kuyruğuna yerleştirir. Neden .read() işlevini yapıp mesajın kodunu çözemiyorsunuz? Çünkü kullanıcıdan gelen birkaç mesajın işletim sistemi arabelleğinde toplanıp tek bir TCP segmentinde gönderilmesi mümkündür. İlkinin kodunu çözebiliriz ve ardından sonrakinin bir kısmı arabellekte kalabilir. Herhangi bir anormal durumda TCP bağlantısını kapatırız ve msg_sender koroutini durdururuz (OUT_QUEUES kuyruğuna Yok göndererek).
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)
Ana koda dönelim. Program başladığında tüm eşyordamları oluşturduktan sonra TCP sunucusunu başlatıyoruz. Kurulan her bağlantı için bir yanıtlayıcı eşyordamı oluşturur.
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()
yanıtlayıcı, başlatıcıya benzer ve aynı eylemlerin tümünü yansıtır, ancak basitlik açısından, mesajların sonsuz okuma döngüsü hemen başlar. Şu anda, el sıkışma protokolü her iki taraftan bir mesaj gönderiyor, ancak gelecekte bağlantı başlatıcıdan iki mesaj gönderilecek ve ardından kısa mesajlar hemen gönderilebilecek.
İletişimimizi güvence altına almanın zamanı geldi. Güvenlikten neyi kastediyoruz ve ne istiyoruz:
iletilen mesajların gizliliği;
iletilen mesajların özgünlüğü ve bütünlüğü - değişiklikler tespit edilmelidir;
tekrar saldırılarına karşı koruma - eksik veya tekrarlanan mesajların tespit edilmesi gerekir (ve bağlantıyı kesmeye karar veririz);
muhatapların önceden girilmiş genel anahtarları kullanarak tanımlanması ve doğrulanması - daha önce arkadaşlardan arkadaşa bir ağ kurmaya karar vermiştik. Kiminle iletişim kurduğumuzu ancak kimlik doğrulamadan sonra anlayacağız;
durumu Mükemmel ileri gizlilik özellikler (PFS) - uzun ömürlü imzalama anahtarımızdan ödün verilmesi, önceki tüm yazışmaları okuma becerisine yol açmamalıdır. Ele geçirilen trafiğin kaydedilmesi işe yaramaz hale gelir;
mesajların geçerliliği/geçerliliği (aktarım ve el sıkışma) yalnızca bir TCP oturumunda. Başka bir oturumdan (aynı muhatap olsa bile) doğru şekilde imzalanmış/kimliği doğrulanmış mesajların eklenmesi mümkün olmamalıdır;
Pasif bir gözlemci, kullanıcı tanımlayıcılarını, iletilen uzun ömürlü genel anahtarları veya bunlardan gelen karmaları görmemelidir. Pasif bir gözlemciden gelen belli bir anonimlik.
Şaşırtıcı bir şekilde, hemen hemen herkes herhangi bir el sıkışma protokolünde bu minimum değere sahip olmak ister ve sonuçta yukarıdakilerin çok azı "kendi kendine geliştirilen" protokoller için karşılanır. Artık yeni bir şey icat etmeyeceğiz. Kesinlikle kullanmanızı tavsiye ederim Gürültü çerçevesi protokol oluşturmak için ama daha basit bir şey seçelim.
En popüler iki protokol şunlardır:
TLS - hatalar, pervazlar, güvenlik açıkları, zayıf düşünce, karmaşıklık ve eksikliklerle dolu uzun bir geçmişi olan çok karmaşık bir protokol (ancak bunun TLS 1.3 ile pek ilgisi yoktur). Ancak çok karmaşık olduğu için bunu dikkate almıyoruz.
IPsec с IKE — basit olmasalar da ciddi kriptografik sorunları yoktur. IKEv1 ve IKEv2 hakkında okursanız, onların kaynağı STS, ISO/IEC IS 9798-3 ve SIGMA (SIGN-and-MAc) protokolleri - bir akşamda uygulanabilecek kadar basit.
STS/ISO protokollerinin geliştirilmesinde en son bağlantı olan SIGMA'nın iyi yanı nedir? Tüm gereksinimlerimizi karşılar (muhatap tanımlayıcılarının "gizlenmesi" dahil) ve bilinen hiçbir şifreleme sorunu yoktur. Minimalisttir; protokol mesajından en az bir öğenin çıkarılması güvensizliğe yol açacaktır.
Evde yetiştirilen en basit protokolden SIGMA'ya geçelim. İlgilendiğimiz en temel işlem anahtar anlaşma: Her iki katılımcıya da aynı değeri veren ve simetrik anahtar olarak kullanılabilen bir işlev. Ayrıntılara girmeden: tarafların her biri geçici (yalnızca bir oturumda kullanılan) bir anahtar çifti (genel ve özel anahtarlar) oluşturur, genel anahtarları değiştirir, girişe kendi özel anahtarlarını ve genel anahtarlarını ilettikleri anlaşma işlevini çağırır. muhatabın anahtarı.
Herkes ortaya atlayabilir ve genel anahtarları kendi anahtarlarıyla değiştirebilir - bu protokolde muhatapların kimlik doğrulaması yoktur. Uzun ömürlü anahtarlara sahip bir imza ekleyelim.
Böyle bir imza belirli bir oturuma bağlı olmadığı için işe yaramayacaktır. Bu tür mesajlar aynı zamanda diğer katılımcılarla yapılan oturumlar için de “uygundur”. İçeriğin tamamı abone olmalıdır. Bu bizi A'dan başka bir mesaj daha eklemeye zorluyor.
Ek olarak, imzanın altına kendi tanımlayıcınızı eklemeniz de önemlidir, çünkü aksi takdirde IdXXX'i değiştirebilir ve bilinen başka bir muhatabın anahtarıyla mesajı yeniden imzalayabiliriz. Önlemek yansıma saldırılarıimzanın altındaki unsurların anlamlarına göre açıkça tanımlanmış yerlerde olması gerekir: A imzalıyorsa (PubA, PubB), B'nin de imzalaması gerekir (PubB, PubA). Bu aynı zamanda serileştirilmiş verilerin yapısını ve formatını seçmenin önemine de değinmektedir. Örneğin, ASN.1 DER kodlamasındaki kümeler sıralanır: SET OF(PubA, PubB), SET OF(PubB, PubA) ile aynı olacaktır.
Ancak bu oturum için aynı paylaşılan anahtarı oluşturduğumuzu hâlâ "kanıtlayamadık". Prensip olarak bu adımı atlayabiliriz - ilk aktarım bağlantısı geçersiz olacaktır, ancak el sıkışma tamamlandığında her şeyin gerçekten üzerinde anlaşıldığından emin olmak istiyoruz. Şu anda elimizde ISO/IEC IS 9798-3 protokolü bulunmaktadır.
Oluşturulan anahtarın kendisini imzalayabiliriz. Bu tehlikelidir, çünkü kullanılan imza algoritmasında sızıntılar olması mümkündür (imza başına bit olsa da yine de sızıntı). Türetme anahtarının karma değerini imzalamak mümkündür, ancak türetilmiş anahtarın karma değerini sızdırmak bile türetme işlevine yapılan kaba kuvvet saldırısında değerli olabilir. SIGMA, gönderenin kimliğini doğrulayan bir MAC işlevi kullanır.
Bir optimizasyon olarak, bazıları geçici anahtarlarını yeniden kullanmak isteyebilir (ki bu elbette PFS için talihsiz bir durumdur). Örneğin, bir anahtar çifti oluşturduk, bağlanmaya çalıştık ama TCP kullanılamıyordu veya protokolün ortasında bir yerde kesintiye uğradı. Boşa harcanan entropiyi ve işlemci kaynaklarını yeni bir çifte harcamak utanç verici. Bu nedenle, geçici genel anahtarları yeniden kullanırken olası rastgele yeniden oynatma saldırılarına karşı koruma sağlayacak sözde rastgele bir değer olan sözde çerezi tanıtacağız. Çerez ile geçici genel anahtar arasındaki bağlantı nedeniyle karşı tarafın ortak anahtarı gereksiz olarak imzadan çıkarılabilir.
Son olarak, sohbet ortaklarımızın mahremiyetini pasif bir gözlemciden almak istiyoruz. Bunu yapmak için SIGMA, öncelikle geçici anahtarların değişimini ve kimlik doğrulama ve tanımlama mesajlarının şifreleneceği ortak bir anahtar geliştirmeyi öneriyor. SIGMA iki seçeneği açıklamaktadır:
SIGMA-I - başlatıcıyı aktif saldırılardan, yanıtlayıcıyı ise pasif saldırılardan korur: başlatıcı, yanıtlayıcının kimliğini doğrular ve bir şey eşleşmezse kimliğini vermez. Sanık, kendisiyle aktif bir protokol başlatılması halinde kimliğini veriyor. Pasif gözlemci hiçbir şey öğrenmez;
SIGMA-R - müdahaleciyi aktif saldırılardan, başlatıcıyı ise pasif saldırılardan korur. Her şey tam tersidir ancak bu protokolde zaten dört el sıkışma mesajı iletilmektedir.
İstemci-sunucudan beklediğimiz tanıdık şeylere daha çok benzediği için SIGMA-I'yi seçiyoruz: istemci yalnızca kimliği doğrulanmış sunucu tarafından tanınır ve herkes sunucuyu zaten bilir. Ayrıca daha az el sıkışma mesajı nedeniyle uygulanması daha kolaydır. Protokole eklediğimiz tek şey, mesajın bir kısmını şifrelemek ve A tanımlayıcısını son mesajın şifrelenmiş kısmına aktarmaktır:
GOST R imza için kullanılır 34.10-2012 256 bit anahtarlı algoritma.
Genel anahtarı oluşturmak için 34.10 VKO kullanılır.
CMAC, MAC olarak kullanılır. Teknik olarak bu, GOST R 34.13-2015'te açıklanan blok şifrenin özel bir çalışma modudur. Bu mod için bir şifreleme işlevi olarak – çekirge (34.12-2015).
Genel anahtarının karması muhatabın tanımlayıcısı olarak kullanılır. Hash olarak kullanılır Stribog-256 (34.11 2012 bit).
El sıkışmanın ardından ortak bir anahtar üzerinde anlaşacağız. Aktarım mesajlarının kimlik doğrulamalı şifrelemesi için kullanabiliriz. Bu kısım çok basit ve hata yapılması zor: mesaj sayacını artırıyoruz, mesajı şifreliyoruz, sayacın ve şifreli metnin kimliğini doğruluyor (MAC), gönderiyoruz. Mesaj alırken sayacın beklenen değere sahip olup olmadığını kontrol eder, şifreli metni sayaçla doğrular ve şifresini çözeriz. El sıkışma mesajlarını ve taşıma mesajlarını şifrelemek için hangi anahtarı kullanmalıyım ve bunların kimliğini nasıl doğrulayabilirim? Tüm bu görevler için tek bir anahtar kullanmak tehlikeli ve akıllıca değildir. Özel işlevleri kullanarak anahtarlar oluşturmak gereklidir KDF (anahtar türetme işlevi). Tekrar söylüyorum, kılı kırk yararak bir şeyler icat etmeyelim: HKDF uzun zamandır bilinmektedir, iyi araştırılmıştır ve bilinen hiçbir sorunu yoktur. Ne yazık ki yerel Python kütüphanesinde bu fonksiyon yoktur, bu yüzden kullanıyoruz HKDF naylon poşet. HKDF dahili olarak kullanır HMAC, bu da bir karma işlevi kullanır. Wikipedia sayfasındaki Python'daki örnek bir uygulama yalnızca birkaç satır kod gerektirir. 34.10'de olduğu gibi hash fonksiyonu olarak Stribog-2012'yı kullanacağız. Anahtar anlaşma fonksiyonumuzun çıktısına, eksik simetrik olanların oluşturulacağı oturum anahtarı adı verilecektir:
HandshakeTBS imzalanacak olandır. El SıkışmaTBE - ne şifrelenecek. MsgHandshake1'deki ukm alanına dikkatinizi çekerim. 34.10 VKO, oluşturulan anahtarların daha da rastgele hale getirilmesi için UKM (kullanıcı anahtarlama malzemesi) parametresini içerir - yalnızca ek entropi.
Koda Kriptografi Ekleme
Çerçeve aynı kaldığı için yalnızca orijinal kodda yapılan değişiklikleri ele alalım (aslında, önce son uygulama yazıldı ve ardından tüm şifreleme bundan çıkarıldı).
Muhatapların kimlik doğrulaması ve tanımlanması genel anahtarlar kullanılarak gerçekleştirileceğinden, artık bunların uzun süre bir yerde saklanması gerekiyor. Basit olması açısından JSON'u şu şekilde kullanıyoruz:
bizim - anahtar çiftimiz, onaltılık özel ve genel anahtarlarımız. onların - muhatapların adları ve genel anahtarları. Komut satırı argümanlarını değiştirelim ve JSON verilerinin sonradan işlenmesini ekleyelim:
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 algoritmasının özel anahtarı rastgele bir sayıdır. 256 bit eliptik eğriler için 256 bit boyut. PyGOST bir bayt kümesiyle çalışmaz, ancak büyük sayılaryani özel anahtarımızın (urandom(32)) gost3410.prv_unmarshal() kullanılarak bir sayıya dönüştürülmesi gerekiyor. Genel anahtar, gost3410.public_key() kullanılarak özel anahtardan deterministik olarak belirlenir. Genel anahtar 34.10, depolama ve iletim kolaylığı için gost3410.pub_marshal() kullanılarak bir bayt dizisine dönüştürülmesi gereken iki büyük sayıdır.
JSON dosyasını okuduktan sonra genel anahtarların gost3410.pub_unmarshal() kullanılarak geri dönüştürülmesi gerekir. Muhatapların tanımlayıcılarını genel anahtardan karma şeklinde alacağımız için, bunlar hemen önceden hesaplanabilir ve hızlı arama için bir sözlüğe yerleştirilebilir. Stribog-256 hash'i, hash fonksiyonlarının hashlib arayüzünü tamamen karşılayan gost34112012256.GOST34112012256()'dır.
Başlatıcı koroutini nasıl değişti? Her şey el sıkışma şemasına göre: VKO anahtar anlaşması işlevi için kullanılacak bir çerez (128 bit yeterli) ve geçici bir anahtar çifti 34.10 oluşturuyoruz.
UKM, 64 bitlik bir sayıdır (urandom(8)), bu da gost3410_vko.ukm_unmarshal() kullanılarak bayt temsilinden seri durumdan çıkarma gerektirir. 34.10 2012-bit için VKO işlevi gost256_vko.kek_3410()'dır (KEK - şifreleme anahtarı).
Oluşturulan oturum anahtarı zaten 256 bitlik sözde rastgele bayt dizisidir. Bu nedenle HKDF fonksiyonlarında hemen kullanılabilir. GOST34112012256 hashlib arayüzünü karşıladığından Hkdf sınıfında hemen kullanılabilir. Katılan anahtar çiftlerinin geçiciliği nedeniyle oluşturulan anahtar her oturum için farklı olacağından ve zaten yeterli entropi içereceğinden, tuzu (Hkdf'nin ilk argümanı) belirtmiyoruz. kdf.expand() varsayılan olarak daha sonra Grasshopper için gereken 256 bitlik anahtarları zaten üretmektedir.
Daha sonra gelen mesajın TBE ve TBS kısımları kontrol edilir:
Gelen şifreli metnin MAC'i hesaplanır ve kontrol edilir;
şifreli metnin şifresi çözülür;
TBE yapısının kodu çözüldü;
muhatabın kimliği ondan alınır ve onu tanıyıp tanımadığımız kontrol edilir;
Bu tanımlayıcı üzerinden MAC hesaplanır ve kontrol edilir;
Her iki tarafın çerezini ve karşı tarafın genel geçici anahtarını içeren TBS yapısı üzerindeki imza doğrulanır. İmza, muhatabın uzun ömürlü imza anahtarıyla doğrulanır.
Yukarıda yazdığım gibi 34.13 tarihinde çeşitli açıklamalar yapılıyor blok şifre çalışma modları 34.12 tarihinden itibaren. Bunların arasında taklit ekler ve MAC hesaplamaları oluşturmak için bir mod vardır. PyGOST'ta bu gost2015.mac()'tır. Bu mod, şifreleme fonksiyonunun (bir veri bloğunun alınması ve geri gönderilmesi), şifreleme bloğunun boyutunun ve aslında verinin kendisinin geçmesini gerektirir. Neden şifreleme bloğunun boyutunu sabit kodlayamıyorsunuz? 3413 yalnızca 34.12-bit Grasshopper şifresini değil, aynı zamanda 2015-bit şifresini de açıklıyor Magma - KGB'de yeniden oluşturulmuş ve hala en yüksek güvenlik eşiklerinden birine sahip olan, hafifçe değiştirilmiş bir GOST 28147-89.
Kuznechik, gost.3412.GOST3412Kuznechik(key) çağrılarak başlatılır ve 34.13 işlevlerine geçmeye uygun .encrypt()/.decrypt() yöntemlerine sahip bir nesne döndürür. MAC şu şekilde hesaplanır: gost3413.mac(GOST3412Kuznechik(key).encrypt, KUZNECHIK_BLOCKSIZE, ciphertext). Hesaplanan ve alınan MAC'yi karşılaştırmak için bayt dizelerinin olağan karşılaştırmasını (==) kullanamazsınız, çünkü bu işlem karşılaştırma süresini sızdırır ve bu genel durumda aşağıdaki gibi ölümcül güvenlik açıklarına yol açabilir: BEAST TLS'ye saldırılar. Python'un bunun için hmac.compare_digest adında özel bir işlevi vardır.
Blok şifreleme işlevi yalnızca bir veri bloğunu şifreleyebilir. Daha büyük bir sayı için ve hatta uzunluğun katları için şifreleme modunun kullanılması gerekir. 34.13-2015 aşağıdakileri açıklamaktadır: ECB, CTR, OFB, CBC, CFB. Her birinin kendi kabul edilebilir uygulama alanları ve özellikleri vardır. Ne yazık ki hala standartlaştıramadık kimliği doğrulanmış şifreleme modları (CCM, OCB, GCM ve benzeri gibi) - en azından MAC'ı kendimiz eklemek zorundayız. seçerim sayaç modu (TO): blok boyutuna göre doldurma gerektirmez, paralelleştirilebilir, yalnızca şifreleme işlevini kullanır, çok sayıda mesajı şifrelemek için güvenli bir şekilde kullanılabilir (nispeten hızlı çarpışmalara sahip olan CBC'den farklı olarak).
.mac() gibi, .ctr() da benzer girdiler alır: ciphertext = gost3413.ctr(GOST3412Kuznechik(key).encrypt, KUZNECHIK_BLOCKSIZE, plaintext, iv). Şifreleme bloğunun uzunluğunun tam olarak yarısı kadar bir başlatma vektörünün belirtilmesi gerekmektedir. Şifreleme anahtarımız yalnızca bir mesajı (birkaç bloktan da olsa) şifrelemek için kullanılıyorsa, sıfır başlatma vektörünü ayarlamak güvenlidir. El sıkışma mesajlarını şifrelemek için her seferinde ayrı bir anahtar kullanırız.
gost3410.verify() imzasını doğrulamak önemsizdir: içinde çalıştığımız eliptik eğriyi geçiririz (bunu GOSTIM protokolümüze kaydederiz), imzalayanın genel anahtarını (bunun ikiden oluşan bir demet olması gerektiğini unutmayın) büyük sayılar ve bir bayt dizisi değil), 34.11 karma değeri ve imzanın kendisi.
Daha sonra, başlatıcıda, doğrulama sırasında yaptığımız eylemlerin aynısını yalnızca simetrik olarak gerçekleştirerek, el sıkışma2'ye bir el sıkışma mesajı hazırlayıp göndeririz: kontrol etmek yerine anahtarlarımızı imzalamak vb.
Oturum oluşturulduğunda, taşıma anahtarları oluşturulur (tarafların her biri için şifreleme ve kimlik doğrulama için ayrı bir anahtar) ve Grasshopper, MAC'in şifresini çözüp kontrol etmek için başlatılır:
msg_sender eşyordamı artık mesajları TCP bağlantısıyla göndermeden önce şifreliyor. Her mesajın monoton olarak artan bir tekrarı vardır; bu aynı zamanda sayaç modunda şifrelendiğinde başlatma vektörüdür. Her mesaj ve mesaj bloğunun farklı bir sayaç değerine sahip olması garanti edilir.
GOSTIM yalnızca eğitim amaçlı kullanılmak üzere tasarlanmıştır (en azından testlerin kapsamına girmediği için)! Programın kaynak kodu indirilebilir burada (Стрибог-256 хэш: 995bbd368c04e50a481d138c5fa2e43ec7c89bc77743ba8dbabee1fde45de120). Как и все мои проекты, типа GoGOST, PyDERASN, NCCP, GoVPN, GOSTIM tamamen ücretsiz yazılımşartlara göre dağıtılır GPLv3 +.