Titkosítási implementációs referencia
A ténylegesen futó titkosítás szűkebb, mint a megírt titkosítás, és ez az oldal éppen erről a különbségről szól. A libsodium sealed box az egyetlen üzenettitkosítás, amelyet a Railgun végrehajt — és csak azon a kliensoldali útvonalon hajtja végre, ahol a natív libsignal modul nincs jelen. Az Electron asztali buildben, ahol a libsignal betöltődik, a közvetlen üzeneteket titkosító kezelő máig csak egy helykitöltő, amely base64 kódolással látja el a nyílt szöveget. A közösségi és csatornaüzenetek egyetlen útvonalon sem titkosítottak. Az oldal második felében leírt Signal-stílusú protokoll meg van írva és át van nézve, de nincs bekötve egyetlen letölthető kliensbe sem.
Ez architektúrareferencia, nem független biztonsági audit. Az 1. és 2. szakasz azt írja le, ami ténylegesen szállításra kerül. A 4–7. szakasz egy olyan tervet ír le, amely felé a Railgun halad, és mindenütt Tervezett jelöléssel szerepel. Semmit sem szabad úgy érteni, mintha egy Railgun kliens jelenleg érvényesítene bármely tervezettként megjelölt tulajdonságot.
Ami ma szállításra kerül
| Útvonal | Mechanizmus | Állapot |
|---|---|---|
| Közvetlen üzenetek — böngésző és fejlesztői buildek | libsodium sealed box — X25519 kulcsegyeztetés, XSalsa20-Poly1305 hitelesített titkosítás. Akkor választódik ki, ha a natív libsignal modul nincs jelen. | Titkosított |
| Közvetlen üzenetek — Electron asztali build | A libsignal betöltődik, ezért az encryptDm az Electron IPC-kezelőhöz kerül — egy helykitöltőhöz, amely base64 kódolással látja el a nyílt szöveget. A relé olvasható szöveget kap. | Nem titkosított |
| Közösségi és csatornaüzenetek | base64-be csomagolt nyílt szövegként mennek el. A csatornatitkosítás meg van írva, de nyílt szöveget jelző markert ad vissza. | Nem titkosított |
| X3DH + Double Ratchet | A libsignal ellenében megvalósítva, de csak típusként importálva. Egyetlen futásidejű útvonal sem példányosítja. | Tervezett |
| Privát kulcsok tárolása | A kulcsok az eszközön jönnek létre és ott is maradnak. Egyetlen kódútvonal sem továbbít privát vagy identitáskulcs-anyagot. | Megvalósítva |
1. Sealed box — a titkosítás, amely fut
Ahol a Railgun egyáltalán titkosít egy közvetlen üzenetet, ott ezt egy libsodium sealed box segítségével teszi — a crypto_box_seal konstrukcióval. Ez az X25519 kulcsegyeztetést az XSalsa20-Poly1305 hitelesített titkosítással kombinálja. Ez valódi, alaposan átvizsgált kriptográfia. Ugyanakkor szándékosan egyszerű konstrukció is, és a korlátai számítanak. Hogy mely kliensek jutnak el idáig, arról a 2. szakasz szól: a böngészős és a fejlesztői buildek igen, az Electron asztali build nem.
Hogyan épül fel egy sealed box
// Sender has only the recipient's public key, PK_r
(pk_e, sk_e) = crypto_box_keypair() // fresh, single-use
nonce = BLAKE2b(pk_e || PK_r) // derived, not random
ct = crypto_box(plaintext, nonce, PK_r, sk_e)
sealed = pk_e || ct // ephemeral key is prepended
erase(sk_e)
A címzett az üzenet elejéről visszanyeri az efemer nyilvános kulcsot, saját privát kulcsával újraszámolja ugyanazt a közös titkot, és kinyitja a dobozt. A Poly1305 címke azt jelenti, hogy a módosított titkosított szöveg nem nyílik ki, ahelyett hogy értelmetlen adattá fejtődne vissza.
Amit ez megad
- • Bizalmasság a reléval és a vonalon hallgatózókkal szemben.
- • Integritás — a manipuláció kiderül, nem pedig csendben visszafejtődik.
- • Feladói anonimitás: az efemer kulcs semmit sem árul el arról, ki küldte.
- • Nincs munkamenet-felépítés, így akkor is működik, ha a címzett offline.
Amit ez nem ad meg
- • Nincs előreirányuló titkosság. A címzett hosszú távú privát kulcsa minden valaha neki küldött üzenetet kinyit. Ha kiszivárog, vele szivárog a rögzített titkosított szöveg is.
- • Nincs feladóhitelesítés. A sealed box felépítésénél fogva névtelen; semmit sem bizonyít arról, ki írta.
- • Nincs kompromittálódás utáni helyreállás. Nincs racsni, amely túllépne egy kulcs kompromittálódásán.
Pontosan ezt a három hiányosságot hivatott bezárni az X3DH és a Double Ratchet, ezért folyik ez a munka. Amíg el nem készül, a sealed box alapú közvetlen üzenet őszinte összefoglalása ez: erős a passzív hálózati megfigyelővel és a reléval szemben, gyenge azzal a támadóval szemben, aki előbb-utóbb megszerzi a címzett eszközkulcsát. Ez az összefoglalás csak ott érvényes, ahol ez a kód ténylegesen fut.
2. Ami nincs titkosítva
A közösségi és csatornaüzenetek — vagyis annak a zöme, amit a legtöbb Railgun-felhasználó valójában ír — ma egyetlen kliensoldali útvonalon sem titkosítottak. A csatornatitkosító függvény létezik, és őszintén beszél önmagáról: figyelmeztetést naplóz, és egy markerkarakterlánc mögött base64-be csomagolt nyílt szöveget ad vissza, hogy a láncban utána következők közül senki se higgye titkosított szövegnek.
// What encryptChannel actually returns
logger.warn("Channel encryption not yet implemented")
ciphertext = base64("[DEVCRYPTO:PLAINTEXT]" + plaintext)
A gyakorlati következmény: a közösségi és csatornatartalom tekintetében a Railgun szervere ugyanabban a helyzetben van, mint bármely szokásos chatszerver. El tudja olvasni, amit ott írsz. A csoportkulcs-terjesztés — egy csatornánkénti kulcs, amelyet sealed boxokban kapnak meg a tagok — a tervezett megoldás, és nincs kiszállítva.
Közvetlen üzenetek az Electron asztali buildben
A második hiányosság a közvetlen üzenetek abban a kliensben, amelyet a legtöbben futtatnak, és ezt könnyű fordítva megfogalmazni, ezért itt a kiválasztási logika úgy, ahogy a kód végrehajtja. Az initCrypto() megkérdezi a fő folyamattól, betöltődött-e a libsignal. A @signalapp/libsignal-client valódi függőség, és indításkor be kell tölteni, így egy kiszállított Electron buildben be is töltődik — és éppen ez a válasz választja ki az Electron IPC-implementációt a sealed box alapú helyett. Ennek encryptDm függvénye a crypto:encryptDm kezelőhöz továbbít, amely egy helykitöltő:
// crypto:encryptDm, Electron main process
const plaintextBytes = new TextEncoder().encode(plaintext)
ciphertext: toBase64(plaintextBytes) // not encryption
Tehát az 1. szakasz sealed boxaihoz akkor jutunk el, ha a libsignal nincs jelen — böngészőben és fejlesztés közben. Az asztali kliensben egy közvetlen üzenet base64 kódolású nyílt szövegként ér a reléhez. A libsignal ott azért töltődik be, hogy identitáskulcsot és előkulcs-csomagot generáljon; nem épít fel munkamenetet, és semmit sem racsniz — a crypto:ensureDmSession egy törzs nélküli hibakeresési naplóbejegyzés.
3. Curve25519 — az alapul szolgáló görbe
Mind a kiszállított sealed boxok, mind a ki nem szállított X3DH terv ugyanazon az elliptikus görbén nyugszik: a Curve25519 görbén, amelyet Daniel J. Bernstein tervezett nagy sebességű Diffie-Hellman kulcscseréhez. Ez a szakasz magát a görbét írja le, és mindkettőre vonatkozik.
A görbe egyenlete
y² = x³ + 486662x² + x
over the prime field 𝔽ₚ where p = 2²⁵⁵ − 19
A 2²⁵⁵ − 19 prímet azért választották, mert rendkívül gyors moduláris aritmetikát tesz lehetővé. A 486662 együtthatót azért választották, mert ez a legkisebb érték, amely biztonságos görbét ad a megkövetelt biztonsági tulajdonságokkal.
Kulcsgenerálás
1. Privát kulcs: Generálj 32 véletlen bájtot egy CSPRNG (kriptográfiailag biztonságos álvéletlenszám-generátor) segítségével. Szorítsd meg a kulcsot: töröld a három legalsó bitet és a legfelső bitet, és állítsd be a második legfelső bitet. Ez biztosítja, hogy a kulcs 8 többszöröse legyen és az érvényes tartományba essen.
a = random(32 bytes)
a[0] &= 248 // Clear low 3 bits
a[31] &= 127 // Clear high bit
a[31] |= 64 // Set second-highest bit
2. Nyilvános kulcs: Számítsd ki a privát kulcs skalárszorzatát a G = 9 bázisponttal.
A = a · G // Scalar multiplication on Curve25519
A biztonság az elliptikus görbe feletti diszkrét logaritmus problémán (ECDLP) nyugszik: A és G ismeretében számításilag kivitelezhetetlen visszanyerni a értékét. A Curve25519 nagyjából 128 bit biztonságot nyújt.
Diffie-Hellman kulcscsere
Két fél (Alice és Bob) úgy tud közös titkot kiszámítani, hogy azt soha nem továbbítja:
Alice: a (private), A = a·G (public)
Bob: b (private), B = b·G (public)
Alice computes: S = a · B = a · (b·G)
Bob computes: S = b · A = b · (a·G)
Both arrive at S = ab·G — the same shared secret
Egy lehallgató, aki ismeri A és B értékét (mindkettő nyilvános), az ECDLP megoldása nélkül nem tudja kiszámítani S értékét. Ez a számítási Diffie-Hellman (CDH) feltevés.
A sealed box ugyanez a csere, azzal, hogy az egyik oldalt efemerré teszik, majd eldobják. Az alábbi X3DH ugyanez a csere, négyszer elvégezve különböző kulcspárokon, úgy, hogy az eredmény a tartalom elrejtése mellett mindkét felet hitelesíti is.
4. X3DH — kiterjesztett hármas Diffie-Hellman
TERVEZETTAz X3DH egy nehéz problémát old meg: hogyan hoz létre két ember titkosított munkamenetet, ha egyikük offline? A hagyományos Diffie-Hellman megköveteli, hogy mindkét fél online legyen. Az X3DH előre feltöltött kulcscsomagokat használ az aszinkron kulcsegyeztetéshez.
Kulcstípusok
Identitáskulcs (IK)
Hosszú távú Curve25519 kulcspár. Egyszer jön létre, kriptográfiailag azonosítja a felhasználót. Soha nem változik, hacsak a felhasználó nem regisztrál újra.
Aláírt előkulcs (SPK)
Középtávú Curve25519 kulcspár, amelyet az identitáskulcs írt alá. Rendszeresen cserélődik. Az aláírás bizonyítja, hogy az identitáskulcs tulajdonosához tartozik.
Egyszer használatos előkulcs (OPK)
Kötegekben feltöltött efemer Curve25519 kulcspárok. Mindegyiket egyszer használják, majd törlik. Extra réteg előreirányuló titkosságot ad a kezdeti üzenethez.
Efemer kulcs (EK)
Friss Curve25519 kulcspár, amelyet a küldő minden új munkamenethez létrehoz. Soha nem tárolódik a szerveren.
Az X3DH kézfogás
Amikor Alice üzenni akar Bobnak (aki lehet offline), letölti Bob előkulcs-csomagját a szerverről, és négy Diffie-Hellman számítást végez:
// Alice has: IKₐ (her identity key), EKₐ (fresh ephemeral)
// Bob published: IKᵦ, SPKᵦ, OPKᵦ
DH1 = DH(IKₐ, SPKᵦ) // her identity ↔ his signed pre-key
DH2 = DH(EKₐ, IKᵦ) // her ephemeral ↔ his identity
DH3 = DH(EKₐ, SPKᵦ) // her ephemeral ↔ his signed pre-key
DH4 = DH(EKₐ, OPKᵦ) // her ephemeral ↔ his one-time pre-key
SK = KDF(DH1 || DH2 || DH3 || DH4)
// SK is the shared secret, derived via HKDF-SHA-256
Miért négy DH művelet?
- A DH1 kölcsönös hitelesítést ad (mindkét identitáskulcs részt vesz benne)
- A DH2 biztosítja, hogy az efemer kulcs Bob identitásához kötődjön
- A DH3 az efemer kulcs révén előreirányuló titkosságot ad
- A DH4 további előreirányuló titkosságot ad. Ha nincs elérhető OPK, az X3DH pusztán a DH1–DH3 műveletekkel is működik
A DH1 az a művelet, amellyel a sealed box nem rendelkezik. Ezért nem tudja megmondani a kiszállított útvonal, kitől érkezett egy üzenet.
KDF — kulcslevezető függvény
A nyers DH kimenetek a HKDF-SHA-256 (HMAC-alapú kulcslevezető függvény) segítségével egyesülnek. A HKDF entrópiát von ki az összefűzött DH kimenetekből, és egyenletesen véletlen közös titokká terjeszti ki:
PRK = HKDF-Extract(salt="", input=DH1||DH2||DH3||DH4)
SK = HKDF-Expand(PRK, info="RailGunX3DH", length=32)
5. A Double Ratchet algoritmus
TERVEZETTMiután az X3DH létrehozta a kezdeti közös titkot, a Double Ratchet veszi át a szerepet. Két egymásba kapcsolódó „racsnit” használ, hogy minden üzenethez új, egyedi kulcsot vezessen le. A terv védi a korábbi üzeneteket, és az a célja, hogy egy friss racsnilépés után a jövőbeli üzenetek védelmét helyreállítsa.
DH racsni (aszimmetrikus)
Valahányszor megfordul a beszélgetés iránya (Alice → Bob, majd Bob → Alice), új Diffie-Hellman kulcscsere történik friss efemer kulcsokkal. Ez „racsnizza” előre a gyökérkulcsot:
dh_out = DH(my_ratchet_key, their_ratchet_key)
root_key, chain_key = KDF(root_key, dh_out)
Ez adna kompromittálódás utáni biztonságot: az a támadó, aki ellopja az aktuális állapotot, elveszíti a hozzáférést, amint új DH racsnilépés történik.
Szimmetrikus racsni (hash-lánc)
A DH racsnilépések között egy hash-alapú racsni minden üzenethez új üzenetkulcsot vezet le a lánckulcsból:
message_key = HMAC-SHA256(chain_key, 0x01)
chain_key = HMAC-SHA256(chain_key, 0x02)
A régi lánckulcs minden lépés után törlődik. Az üzenetkulcs pontosan egy üzenetet titkosít, majd törlődik. Ez az előreirányuló titkosság mechanizmusa.
A racsni előrehaladása
Szemléltetve a Double Ratchet így néz ki. Minden nyíl egy egyirányú függvény — előre lehet haladni, visszafelé soha:
Root Key Chain:
RK₀ ──DH──→ RK₁ ──DH──→ RK₂ ──DH──→ RK₃ ...
Each RK step produces a Chain Key:
↓ ↓ ↓
CK₀ CK₁ CK₂
↓ ↓ ↓
Each CK step produces Message Keys:
CK₀→MK₀ CK₁→MK₃ CK₂→MK₅
CK₀→MK₁ CK₁→MK₄ CK₂→MK₆
CK₀→MK₂ CK₂→MK₇
Minden MKₙ pontosan egy üzenetet titkosítana, majd törlődne, így az a támadó, aki megszerzi MK₄ kulcsot, nem tudná levezetni MK₃ vagy MK₅ kulcsot. A ma kiszállított sealed box útvonalon nincs üzenetenkénti kulcs, amit el lehetne lopni, és nincs lánc, amin végig lehetne menni — egyetlen hosszú távú kulcs van, amely mindent kinyit.
6. Előreirányuló titkosság — mi kellene hozzá
TERVEZETTAz előreirányuló titkosság azt jelenti, hogy a hosszú távú kulcsok kompromittálódása nem kompromittálja a korábbi munkamenetkulcsokat. A Railgunnak ez ma nincs meg. Két mechanizmus adná meg, és mindkettő a fenti tervezett protokollhoz tartozik:
Efemer kulcsok az X3DH-ban
A küldő minden új munkamenethez friss efemer kulcspárt generál, és a kézfogás után törli a privát felét. Az a támadó, aki később mindkét fél identitáskulcsát ellopja, akkor sem tudja rekonstruálni a munkamenet titkát. A sealed boxok valóban használnak friss efemer kulcsot a küldő oldalán — a csere címzetti fele viszont a címzett hosszú távú kulcsa, és pontosan ezért nem teljesül ez a tulajdonság.
Racsnikulcsok törlése
A Double Ratchet használat után törli a régi lánckulcsokat és üzenetkulcsokat. A KDF-lánc egyirányú — CKₙ ismeretében ki lehet számítani CKₙ₊₁ értékét, de CKₙ₋₁ értékét nem, így egy T időpontbeli kompromittálódás semmit sem fed fel abból, amit T előtt küldtek.
Kompromittálódás utáni biztonság
A DH racsni jövőbeli titkosságot is nyújt: az a támadó, aki kompromittálja az aktuális munkamenet-állapotot, a következő racsnilépésnél elveszíti azt, mert az olyan véletlenszerűséget vezet be, amellyel ő nem rendelkezik. Racsni nélkül egy kompromittálódott kulcs korlátlan ideig hasznos marad a támadónak.
7. Relé- és sorarchitektúra
A Railgun borítékokat továbbít, és ideiglenesen sorba is állíthatja őket offline kézbesítéshez. A relé úgy készült, hogy ne legyen szüksége az üzenetek nyílt szövegére, ma azonban két esetben nyílt szöveget kap: a csatornaüzeneteknél, amelyeket egyetlen kliens sem titkosít, és az Electron asztali buildből érkező közvetlen üzeneteknél, amelyek IPC-kezelője titkosítás helyett base64 kódolást végez. A sealed box útvonalról érkező közvetlen üzenet olyan titkosított szövegként érkezik, amelyet a relé nem tud kinyitni.
Amit a szerver lát
A szerver tárolja:
- • Nyilvános kulcscsomagokat
- • Felhasználói regisztrációs adatokat
- • Közösség-/csatorna-metaadatokat és tagságot
- • Sealed box titkosított szöveget, amelyet nem tud kinyitni (a böngészős/fejlesztői útvonalról érkező közvetlen üzenetek)
- • Az Electron asztali buildből érkező, base64 kódolású nyílt szövegű közvetlen üzeneteket
- • Csatornaüzenetek tartalmát, amíg a csatornatitkosítás ki nem kerül
- • Hitelesítési auditrekordokat
A szerver soha nem kapja meg:
- • Privát vagy identitáskulcsokat
- • A kliens munkamenet-állapotát
- • Az üzenetelőzményeid tartós másolatát
Az üzenetek valós időben, WebSocketen keresztül jutnak el az online címzettekhez. Offline eszközök esetén a borítékok a Redisben állnak sorba legfeljebb 30 napos TTL-lel, és törlődnek, amint az eszköz visszaigazolja őket. Nincs szerveroldali üzenetarchívum — a kliens előzményei a saját eszközöd helyi tárolójában élnek, így az elveszett eszköz elveszett előzményeket jelent.
8. Biztonsági tulajdonságok összefoglalása
| Tulajdonság | Mechanizmus | Állapot |
|---|---|---|
| Közvetlen üzenetek bizalmassága | Sealed boxok csak a böngészős/fejlesztői útvonalon; az Electron asztali kezelő base64 kódolást végez | Részleges |
| Közvetlen üzenetek integritása | Poly1305 címke a sealed box útvonalon; az Electron asztali útvonal hitelesítetlen base64-et küld | Részleges |
| A kulcsok az eszközön maradnak | Helyi kulcstár; egyetlen kódútvonal sem továbbít privát kulcsot | Megvalósítva |
| Offline kézbesítés | A borítékok a Redisben sorakoznak 30 napos TTL-lel, visszaigazoláskor törlődnek | Megvalósítva |
| Csatornák bizalmassága | A csoportkulcs-terjesztés nincs megvalósítva; a tartalom olvashatóan megy el | Nincs megvalósítva |
| Feladóhitelesítés | A sealed boxok felépítésüknél fogva névtelenek | Nincs megvalósítva |
| Előreirányuló titkosság | A Double Ratchetet igényli, amelyet egyetlen kliens sem példányosít | Tervezett |
| Kompromittálódás utáni helyreállás | A DH racsnit igényli, amelyet egyetlen kliens sem példányosít | Tervezett |
| Független audit | Nem állítjuk, hogy elkészült volna külső fél által végzett kriptográfiai audit | Nincs befejezve |
| Posztkvantum-garancia | Nem állítunk semmilyen általános posztkvantum biztonsági garanciát | Nincs állítva |
Honnan származik ez az oldal
A fenti állapotokat mind a kliens forráskódjából olvastuk ki, nem tervdokumentumból. A kliens repója jelenleg nem nyilvános, ezért nem kérhetjük, hogy magad ellenőrizd — és éppen ezért érdemes ezt az oldalt közzétételként mérlegelni, nem bizonyítékként. Egy-egy konkrét állítással kapcsolatos kérdéseket szívesen fogadunk a security@railgun.chat címen.