Retour à l'accueil
Cryptographie

Référence d'implémentation du chiffrement

Le chiffrement qui s'exécute est plus étroit que le chiffrement qui est écrit, et c'est précisément cet écart qui fait l'objet de cette page. Les sealed boxes de libsodium sont le seul chiffrement de messages que Railgun exécute — et elles ne s'exécutent que sur le chemin client où le module natif libsignal est absent. Sur la version de bureau Electron, où libsignal se charge bel et bien, le gestionnaire qui chiffre les messages directs reste un substitut qui encode le texte en clair en base64. Les messages de communauté et de canal ne sont chiffrés sur aucun chemin. Le protocole de type Signal décrit dans la seconde moitié de cette page est écrit et relu, et il n'est raccordé à aucun client que vous puissiez télécharger.

Ceci est une référence d'architecture, pas un audit de sécurité indépendant. Les sections 1 et 2 décrivent ce qui est livré. Les sections 4 à 7 décrivent une conception vers laquelle Railgun travaille et sont marquées Prévu d'un bout à l'autre. Rien ici ne doit être lu comme l'affirmation qu'un client Railgun applique actuellement une propriété marquée comme prévue.

Ce qui est livré aujourd'hui

CheminMécanismeÉtat
Messages directs — versions navigateur et développementsealed box libsodium — accord de clés X25519, chiffrement authentifié XSalsa20-Poly1305. Sélectionné lorsque le module natif libsignal est absent.Chiffré
Messages directs — version de bureau Electronlibsignal se charge, donc encryptDm est routé vers le gestionnaire IPC Electron — un substitut qui encode le texte en clair en base64. Le relais reçoit du texte lisible.Non chiffré
Messages de communauté et de canalEnvoyés en texte clair enveloppé en base64. Le chiffrement des canaux est écrit mais renvoie un marqueur de texte en clair.Non chiffré
X3DH + Double RatchetImplémenté sur libsignal, importé uniquement comme type. Aucun chemin d'exécution ne l'instancie.Prévu
Stockage des clés privéesLes clés sont générées et conservées sur l'appareil. Aucun chemin de code ne transmet de clé privée ni de clé d'identité.Implémenté

1. Les sealed boxes — le chiffrement qui s'exécute

Là où Railgun chiffre effectivement un message direct, il le fait avec une sealed box libsodium — la construction crypto_box_seal. Elle associe l'accord de clés X25519 au chiffrement authentifié XSalsa20-Poly1305. C'est de la cryptographie réelle et bien relue. C'est aussi une construction délibérément simple, et ses limites comptent. Quels clients y accèdent, c'est l'objet de la section 2 : les versions navigateur et développement y accèdent ; la version de bureau Electron, non.

Comment une sealed box est construite

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

Le destinataire récupère la clé publique éphémère placée en tête du message, recalcule le même secret partagé avec sa propre clé privée, et ouvre la boîte. Le tag Poly1305 fait qu'un chiffré modifié échoue à s'ouvrir au lieu de se déchiffrer en données incohérentes.

Ce que cela vous apporte

  • La confidentialité vis-à-vis du relais et de quiconque se trouve sur le réseau.
  • L'intégrité — l'altération est détectée, et non déchiffrée en silence.
  • L'anonymat de l'expéditeur : la clé éphémère ne révèle rien sur l'auteur de l'envoi.
  • Aucune mise en place de session, cela fonctionne donc quand le destinataire est hors ligne.

Ce que cela ne vous apporte pas

  • Aucune confidentialité persistante. La clé privée à long terme du destinataire ouvre tous les messages qui lui ont jamais été envoyés. Si elle fuite, les chiffrés capturés fuitent avec elle.
  • Aucune authentification de l'expéditeur. Une sealed box est anonyme par construction ; elle ne prouve rien sur l'identité de son auteur.
  • Aucune récupération après compromission. Il n'existe aucun ratchet permettant de dépasser une compromission de clé.

Ces trois manques sont exactement ce que X3DH et le Double Ratchet existent pour combler, et c'est pourquoi ce travail est en cours. Tant qu'il n'a pas abouti, le résumé honnête d'un message direct en sealed box est le suivant : solide face à un observateur passif du réseau et face au relais, faible face à un adversaire qui finit par obtenir la clé d'appareil d'un destinataire. Ce résumé ne vaut que là où ce code s'exécute réellement.

2. Ce qui n'est pas chiffré

Les messages de communauté et de canal — l'essentiel de ce que la plupart des utilisateurs de Railgun écrivent réellement — ne sont chiffrés sur aucun chemin client aujourd'hui. La fonction de chiffrement des canaux existe, et elle est franche à son propre sujet : elle journalise un avertissement et renvoie le texte en clair enveloppé en base64 derrière une chaîne de marquage, afin que personne en aval ne le prenne pour un chiffré.

// What encryptChannel actually returns

logger.warn("Channel encryption not yet implemented")

ciphertext = base64("[DEVCRYPTO:PLAINTEXT]" + plaintext)

La conséquence pratique : pour les contenus de communauté et de canal, le serveur Railgun est dans la même position que n'importe quel serveur de discussion ordinaire. Il peut lire ce que vous y écrivez. La distribution de clés de groupe — une clé par canal remise aux membres dans des sealed boxes — est le correctif prévu et n'est pas livrée.

Les messages directs sur la version de bureau Electron

Le second manque concerne les messages directs sur le client que la plupart des gens utilisent, et il est facile de l'énoncer à l'envers ; voici donc la logique de sélection telle que le code l'exécute. initCrypto() demande au processus principal si libsignal s'est chargé. @signalapp/libsignal-client est une véritable dépendance et est requis au démarrage : sur une version Electron livrée, il se charge bel et bien — et c'est cette réponse qui sélectionne l'implémentation IPC Electron au lieu de celle en sealed box. Son encryptDm transmet au gestionnaire crypto:encryptDm, qui est un substitut :

// crypto:encryptDm, Electron main process

const plaintextBytes = new TextEncoder().encode(plaintext)

ciphertext: toBase64(plaintextBytes) // not encryption

Les sealed boxes de la section 1 sont donc atteintes lorsque libsignal est absent — dans le navigateur et en développement. Sur le client de bureau, un message direct parvient au relais sous forme de texte en clair encodé en base64. libsignal y est chargé pour générer une clé d'identité et un lot de préclés ; il n'établit aucune session et ne fait avancer aucun ratchet — crypto:ensureDmSession est un journal de débogage sans corps.

3. Curve25519 — la courbe sous-jacente

Les sealed boxes qui sont livrées comme la conception X3DH qui ne l'est pas reposent sur la même courbe elliptique : Curve25519, conçue par Daniel J. Bernstein pour un échange de clés Diffie-Hellman à haute vitesse. Cette section décrit la courbe elle-même, et s'applique aux deux.

L'équation de la courbe

y² = x³ + 486662x² + x

over the prime field 𝔽ₚ where p = 2²⁵⁵ − 19

Le nombre premier 2²⁵⁵ − 19 a été choisi parce qu'il permet une arithmétique modulaire extrêmement rapide. Le coefficient 486662 a été choisi comme la plus petite valeur produisant une courbe sûre dotée des propriétés de sécurité requises.

Génération des clés

1. Clé privée : générez 32 octets aléatoires à l'aide d'un CSPRNG (générateur de nombres pseudo-aléatoires cryptographiquement sûr). Bridez la clé en effaçant les 3 bits de poids le plus faible et le bit de poids le plus fort, et en positionnant l'avant-dernier bit de poids fort. Cela garantit que la clé est un multiple de 8 et se situe dans la plage valide.

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. Clé publique : calculez la multiplication scalaire de la clé privée par le point de base G = 9.

A = a · G // Scalar multiplication on Curve25519

La sécurité repose sur le problème du logarithme discret sur courbe elliptique (ECDLP) : étant donné A et G, il est calculatoirement infaisable de retrouver a. Curve25519 offre environ 128 bits de sécurité.

Échange de clés Diffie-Hellman

Deux parties (Alice et Bob) peuvent calculer un secret partagé sans jamais le transmettre :

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

Un espion qui connaît A et B (tous deux publics) ne peut pas calculer S sans résoudre l'ECDLP. C'est l'hypothèse Diffie-Hellman calculatoire (CDH).

Une sealed box est cet échange avec un côté rendu éphémère puis jeté. X3DH, ci-dessous, est ce même échange effectué quatre fois sur des paires de clés différentes, de sorte que le résultat authentifie les deux parties en plus de dissimuler le contenu.

4. X3DH — Extended Triple Diffie-Hellman

PRÉVU
Écrit sur libsignal et vérifiable dans le code source. Il est importé uniquement comme type — aucun client livré ne le construit. Considérez tout ce qui figure dans cette section comme la conception visée, pas comme le comportement actuel.

X3DH résout un problème difficile : comment deux personnes établissent-elles une session chiffrée quand l'une d'elles est hors ligne ? Le Diffie-Hellman traditionnel exige que les deux parties soient en ligne. X3DH utilise des lots de clés téléversés à l'avance pour permettre un accord de clés asynchrone.

Types de clés

Clé d'identité (IK)

Paire de clés Curve25519 à long terme. Générée une seule fois, elle identifie l'utilisateur de manière cryptographique. Elle ne change jamais, sauf si l'utilisateur se réinscrit.

Préclé signée (SPK)

Paire de clés Curve25519 à moyen terme, signée par la clé d'identité. Renouvelée périodiquement. La signature prouve qu'elle appartient au détenteur de la clé d'identité.

Préclé à usage unique (OPK)

Paires de clés Curve25519 éphémères téléversées par lots. Chacune est utilisée une fois puis supprimée. Elle fournit une couche supplémentaire de confidentialité persistante pour le message initial.

Clé éphémère (EK)

Une nouvelle paire de clés Curve25519 générée par l'expéditeur pour chaque nouvelle session. Jamais stockée côté serveur.

La poignée de main X3DH

Quand Alice veut écrire à Bob (qui peut être hors ligne), elle récupère le lot de préclés de Bob auprès du serveur et effectue quatre calculs Diffie-Hellman :

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

Pourquoi quatre opérations DH ?

  • DH1 fournit l'authentification mutuelle (les deux clés d'identité sont impliquées)
  • DH2 garantit que la clé éphémère est liée à l'identité de Bob
  • DH3 fournit la confidentialité persistante via la clé éphémère
  • DH4 fournit une confidentialité persistante supplémentaire. Si aucune OPK n'est disponible, X3DH fonctionne quand même avec les seules DH1–DH3

DH1 est l'opération dont une sealed box ne dispose pas. C'est pourquoi le chemin livré ne peut pas vous dire de qui provient un message.

KDF — fonction de dérivation de clés

Les sorties DH brutes sont combinées à l'aide de HKDF-SHA-256 (fonction de dérivation de clés fondée sur HMAC). HKDF extrait l'entropie des sorties DH concaténées et l'étend en un secret partagé uniformément aléatoire :

PRK = HKDF-Extract(salt="", input=DH1||DH2||DH3||DH4)

SK = HKDF-Expand(PRK, info="RailGunX3DH", length=32)

5. L'algorithme Double Ratchet

PRÉVU
Aucune version publiée de Railgun ne fait avancer de ratchet de clés aujourd'hui. Sur le chemin en sealed box, chaque message direct est chiffré vers la même clé de destinataire à long terme ; sur le chemin de bureau Electron, il n'est pas chiffré du tout.

Une fois que X3DH a établi un secret partagé initial, le Double Ratchet prend le relais. Il utilise deux "ratchets" imbriqués pour dériver une nouvelle clé unique pour chaque message. La conception protège les messages antérieurs et vise à rétablir la protection des messages futurs après une nouvelle étape de ratchet.

Ratchet DH (asymétrique)

Chaque fois que le sens de la conversation change (Alice → Bob, puis Bob → Alice), un nouvel échange de clés Diffie-Hellman a lieu avec de nouvelles clés éphémères. Cela fait avancer la clé racine d'un cran :

dh_out = DH(my_ratchet_key, their_ratchet_key)

root_key, chain_key = KDF(root_key, dh_out)

C'est ce qui apporterait la sécurité post-compromission : un attaquant qui dérobe l'état courant perd l'accès dès qu'une nouvelle étape de ratchet DH se produit.

Ratchet symétrique (chaîne de hachage)

Entre les étapes de ratchet DH, un ratchet fondé sur le hachage dérive une nouvelle clé de message à partir de la clé de chaîne, pour chaque message :

message_key = HMAC-SHA256(chain_key, 0x01)

chain_key = HMAC-SHA256(chain_key, 0x02)

L'ancienne clé de chaîne est supprimée après chaque étape. La clé de message chiffre exactement un message, puis est supprimée. C'est le mécanisme de confidentialité persistante.

Progression du ratchet

Représenté visuellement, le Double Ratchet ressemble à ceci. Chaque flèche est une fonction à sens unique — on peut avancer, jamais reculer :

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₇

Chaque MKₙ chiffrerait exactement un message puis serait supprimée, de sorte qu'un attaquant qui obtient MK₄ ne pourrait dériver ni MK₃ ni MK₅. Sur le chemin en sealed box livré aujourd'hui, il n'y a aucune clé par message à dérober et aucune chaîne à parcourir — il y a une clé à long terme qui ouvre tout.

6. Confidentialité persistante — ce qu'il faudrait

PRÉVU

La confidentialité persistante signifie que la compromission de clés à long terme ne compromet pas les clés de session passées. Railgun ne l'a pas aujourd'hui. Deux mécanismes l'apporteraient, et tous deux relèvent du protocole prévu décrit plus haut :

Clés éphémères dans X3DH

L'expéditeur génère une nouvelle paire de clés éphémères pour chaque nouvelle session et supprime la moitié privée après la poignée de main. Un attaquant qui dérobe plus tard les clés d'identité des deux parties ne peut toujours pas reconstituer le secret de session. Les sealed boxes utilisent bien une clé éphémère fraîche du côté de l'expéditeur — mais la moitié de l'échange du côté du destinataire est sa clé à long terme, et c'est précisément pour cela que la propriété ne tient pas.

Suppression des clés de ratchet

Le Double Ratchet supprime après usage les anciennes clés de chaîne et clés de message. La chaîne KDF est à sens unique — à partir de CKₙ, on peut calculer CKₙ₊₁ mais pas CKₙ₋₁, si bien qu'une compromission à l'instant T ne révèle rien de ce qui a été envoyé avant T.

Sécurité post-compromission

Le ratchet DH apporte aussi la confidentialité future : un attaquant qui compromet l'état de session courant le perd à l'étape de ratchet suivante, qui introduit de l'aléa dont il ne dispose pas. Sans ratchet, une clé compromise reste indéfiniment utile à l'attaquant.

7. Architecture du relais et de la file d'attente

Railgun relaie des enveloppes et peut les mettre temporairement en file d'attente pour une remise hors ligne. Le relais est conçu de façon à ne pas avoir besoin du texte en clair des messages, mais aujourd'hui il reçoit du texte en clair dans deux cas : les messages de canal, qu'aucun client ne chiffre, et les messages directs issus de la version de bureau Electron, dont le gestionnaire IPC encode en base64 au lieu de chiffrer. Un message direct venu du chemin en sealed box arrive sous forme de chiffré que le relais ne peut pas ouvrir.

Ce que voit le serveur

Le serveur détient :

  • Des lots de clés publiques
  • Les informations d'inscription des utilisateurs
  • Les métadonnées et l'appartenance aux communautés et aux canaux
  • Du chiffré en sealed box qu'il ne peut pas ouvrir (messages directs issus du chemin navigateur/développement)
  • Du texte en clair de messages directs encodé en base64, issu de la version de bureau Electron
  • Le contenu des messages de canal, jusqu'à la livraison du chiffrement des canaux
  • Les enregistrements d'audit d'authentification

Le serveur ne reçoit jamais :

  • Les clés privées ou d'identité
  • L'état de session du client
  • Une copie durable de votre historique de messages

Les messages sont relayés en temps réel aux destinataires en ligne via WebSocket. Pour les appareils hors ligne, les enveloppes sont mises en file d'attente dans Redis avec une TTL maximale de 30 jours et supprimées dès que l'appareil en accuse réception. Il n'existe aucune archive de messages côté serveur — l'historique du client réside dans le stockage local de votre propre appareil : un appareil perdu est un historique perdu.

8. Récapitulatif des propriétés de sécurité

PropriétéMécanismeÉtat
Confidentialité des messages directsSealed boxes sur le seul chemin navigateur/développement ; le gestionnaire de bureau Electron encode en base64Partiel
Intégrité des messages directsTag Poly1305 sur le chemin en sealed box ; le chemin de bureau Electron envoie du base64 non authentifiéPartiel
Les clés restent sur l'appareilMagasin de clés local ; aucun chemin de code ne transmet de clés privéesImplémenté
Remise hors ligneEnveloppes mises en file d'attente dans Redis avec une TTL de 30 jours, supprimées à l'accusé de réceptionImplémenté
Confidentialité des canauxLa distribution de clés de groupe n'est pas implémentée ; le contenu est envoyé en clairNon implémenté
Authentification de l'expéditeurLes sealed boxes sont anonymes par constructionNon implémenté
Confidentialité persistanteNécessite le Double Ratchet, qu'aucun client n'instanciePrévu
Récupération après compromissionNécessite le ratchet DH, qu'aucun client n'instanciePrévu
Audit indépendantAucun audit cryptographique achevé par un tiers n'est revendiquéNon réalisé
Garantie post-quantiqueAucune garantie générale de sécurité post-quantique n'est revendiquéeNon revendiqué

D'où vient cette page

Chaque état ci-dessus a été relevé dans le code source du client, et non dans un document de conception. Le dépôt du client n'est pas public à ce jour, nous ne pouvons donc pas vous demander de le vérifier vous-même — ce qui est une raison de considérer cette page comme une divulgation, et non comme une preuve. Les questions portant sur une affirmation précise sont bienvenues à security@railgun.chat.