Tes fichiers sur tous tes appareils, sans que le serveur puisse les lire : comment marche la sync zero-knowledge de Filarr
Comment Filarr synchronise tes fichiers sur tous tes appareils sans que le serveur puisse les lire : FEK, blobs chiffrés, manifest chiffré et transfert de cle ECDH expliques.
Mathis Belouar-Pruvot
· fondateur de Filarr · · 17 min de lecture
Tu bosses sur ton laptop le matin, tu reprends sur ta tour le soir, et tout est là : tes notes, tes dossiers, la structure exacte de ton workspace. C'est le boulot de base d'une sync cloud, et Filarr le fait. La différence, c'est ce que le serveur voit pendant ce trajet : rien de lisible. Ni le contenu de tes fichiers, ni leurs noms, ni le texte de tes notes. Juste des blocs de données chiffrés qu'il stocke et redonne sans jamais pouvoir les ouvrir.
Cet article explique comment, concrètement, la sync optionnelle de Filarr reste "zero-knowledge" : ce qui transite, ce que le backend peut observer, comment tes appareils s'échangent la clé sans jamais la confier en clair au serveur, et où sont les limites honnêtes de ce modèle. C'est un deep-dive orienté implémentation, avec les fichiers et les paramètres réels du code.
Réponse courte. La sync cloud de Filarr est optionnelle et zero-knowledge : tes fichiers sont déjà chiffrés sur ton disque en AES-256-GCM, sous une clé de compte (la FEK) qui ne quitte jamais tes appareils en clair, avec une valeur aléatoire nouvelle pour chacun, et ils sont envoyés tels quels vers le stockage Cloudflare R2. Le serveur ne reçoit que des blobs chiffrés opaques, découpés en chunks de 4 Mo, rangés sous des identifiants dérivés d'un hash SHA-256 (les vrais noms de fichiers ne partent jamais). Même l'index qui décrit ton workspace (le manifest) est chiffré avec la FEK avant l'upload. Pour qu'un deuxième appareil puisse déchiffrer, il récupère la FEK soit en se connectant (le serveur garde une copie de la FEK emballée par ton mot de passe), soit d'appareil à appareil via un échange de clés ECDH (courbe P-256) : le serveur relaie alors une version chiffrée de la FEK qu'il ne peut pas ouvrir.
Le problème : synchroniser sans faire confiance au serveur
La sync classique repose sur un pacte implicite : tu confies tes données à un serveur, il te les redistribue sur tes appareils, et tu lui fais confiance pour ne pas les lire. Notion stocke tes notes en clair sur ses serveurs. La plupart des services de sync "chiffrés" chiffrent en transit (TLS) et parfois au repos, mais avec une clé que le fournisseur détient : il peut techniquement tout déchiffrer, et un incident, une demande d'une autorité judiciaire ou un employé malveillant suffit à exposer tout le contenu.
Le modèle zero-knowledge inverse ce pacte. Le serveur doit rester utile (stocker, versionner, redistribuer) tout en étant structurellement incapable de lire ce qu'il stocke. Ça pose trois questions concrètes que le code de Filarr doit résoudre :
- Comment envoyer un fichier sans que le serveur puisse en voir le contenu ni le nom ?
- Comment décrire l'organisation du workspace (arborescence, métadonnées) sans révéler cette structure au serveur ?
- Comment donner la clé de déchiffrement à un deuxième appareil sans jamais la donner au serveur qui sert d'intermédiaire ?
Filarr répond aux trois. Voyons comment, étape par étape.
Comment ça marche, étape par étape
1. Tout est déjà chiffré avant de penser à la sync
Le point de départ, c'est que la sync n'a rien à chiffrer : le disque local l'est déjà. Filarr repose sur une clé unique, la FEK (File Encryption Key), une clé AES-256 générée aléatoirement à la première connexion (crypto.subtle.generateKey dans hybridCrypto.ts). Chaque fichier stocké localement est chiffré en AES-256-GCM sous cette FEK, avec un IV aléatoire de 12 octets ; les gros fichiers importés sur le bureau reçoivent en plus une clé propre, dérivée de la FEK par HKDF.
Cette FEK n'est jamais stockée en clair sur le disque. Elle est "wrappée" (chiffrée) par une KEK (Key Encryption Key) dérivée de ton mot de passe de vault via PBKDF2-SHA-512, 600 000 itérations (bien au-delà des 210 000 que l'OWASP recommande pour PBKDF2-SHA512). Une copie indépendante de la FEK est aussi wrappée par une clé dérivée de ta phrase de récupération de 24 mots (BIP-39), pour le cas où tu perds ton mot de passe. Le serveur garde aussi ces deux copies wrappées, pour qu'un nouvel appareil puisse s'ouvrir avec ton mot de passe.
Conséquence directe pour la sync : quand un fichier part vers le cloud, il est déjà un blob chiffré. Le module de sync (syncService.ts) le lit sur le disque et l'envoie tel quel, sans étape de chiffrement supplémentaire côté réseau. Le commentaire dans le code est explicite : "Files on disk are already encrypted with FEK, uploaded as-is."
2. Les noms de fichiers ne partent jamais
Un point souvent oublié dans le zero-knowledge : le contenu peut être chiffré mais les noms de fichiers, eux, fuitent des informations ("impots_2025.pdf", "lettre_demission.md"). Filarr évite ça en n'utilisant jamais le vrai nom côté serveur.
Quand un fichier change, syncService.ts calcule un identifiant opaque : sha256(chemin local) tronqué à 32 caractères hexadécimaux. Le commentaire du code le dit noir sur blanc : "Derive opaque fileId from local path, never expose real file names." C'est cet identifiant, pas le nom, qui sert de clé de rangement.
Côté Worker Cloudflare (sync.ts), les chunks sont rangés sous une arborescence purement technique :
users/{userId}/profiles/{profileId}/files/{fileId}/chunk_0.enc
Le serveur voit donc un utilisateur, un profil (un UUID), un identifiant de fichier haché et un numéro de chunk. Il ne voit ni "impots_2025.pdf", ni le dossier "Comptabilité". Les fichiers de plus de 4 Mo sont découpés en chunks de 4 Mo (constante CHUNK_SIZE), chacun uploadé séparément avec l'extension .enc.
3. Le manifest : décrire le workspace sans le révéler
Si le serveur ne connaît que des identifiants hachés, comment un deuxième appareil reconstruit-il l'arborescence, retrouve-t-il quel chunk correspond à quel fichier, dans quel dossier ? Grâce au manifest : un index JSON qui liste les fichiers, leurs chunks, leurs checksums SHA-256, leurs chemins locaux, plus les métadonnées de profil.
Ce manifest est évidemment sensible, puisqu'il contient les vrais chemins et la structure. Il est donc chiffré avant de partir, avec la même FEK, via encryptWithFEK dans storageService.ts. Le format est un marqueur fek: suivi de IV(12) || ciphertext+tag en AES-256-GCM. Le Worker le stocke sous users/{userId}/profiles/{profileId}/manifest.enc et le renvoie en base64 sans jamais l'ouvrir. Là encore, le serveur ne manipule qu'un blob opaque.
Le manifest chiffré transporte aussi la clé de chiffrement des métadonnées de StorageService, ce qui permet à un nouvel appareil, une fois qu'il a la FEK, de déchiffrer non seulement les fichiers mais aussi les metadata.json de chaque dossier et le notes.enc à la racine. Tout ça est imbriqué à l'intérieur du blob protégé par la FEK.
4. Le trajet réseau : un proxy à jetons, jamais de clé en clair
Filarr n'expose pas R2 directement au client. Le flux d'upload (syncR2Client.ts côté Electron, sync.ts côté Worker) suit un motif proxy à deux temps :
- L'appareil fait un appel authentifié à
/sync/presign/uploaden donnantprofileId,fileId,chunkIndexet la taille. Le Worker vérifie le quota de stockage, génère un jeton aléatoire (crypto.randomUUID), stocke l'intention d'upload (userId, clé R2, taille) dans le KV avec une durée de vie de 15 minutes, et renvoie une URL contenant ce jeton. - L'appareil envoie le blob chiffré en PUT sur
/sync/upload/{token}. Le Worker relit l'intention depuis le KV, écrit le blob dans R2, met à jour le compteur de stockage en D1, puis supprime le jeton.
Le download est symétrique avec un jeton à usage unique. À aucun moment le client ne transmet une clé de chiffrement au serveur : ce qui circule, ce sont des octets déjà chiffrés et des jetons de coordination qui ne disent rien du contenu. Le commentaire d'en-tête du Worker résume l'invariant : "All file content is opaque encrypted blobs, zero knowledge."
5. Cohérence multi-appareils : verrouillage optimiste
Quand deux appareils synchronisent le même profil, il faut éviter qu'ils s'écrasent mutuellement le manifest. Filarr utilise un verrouillage optimiste basé sur un numéro de version stocké en D1 (manifest_version). Pour uploader un nouveau manifest, l'appareil annonce la version qu'il pense être à jour ; si le serveur a une version plus récente, il répond par un conflit 409 et l'appareil re-fusionne au cycle suivant. Ce mécanisme ne demande au serveur de comprendre que des entiers de version, jamais le contenu du manifest.
Les conflits de fichiers, eux, sont résolus côté client par une stratégie de fork : la version locale est copiée avec un suffixe _conflict_{timestamp} et l'utilisateur tranche. Le serveur n'arbitre rien, parce qu'il ne peut rien lire.
6. Le moment critique : donner la clé à un deuxième appareil
C'est ici que le zero-knowledge se joue vraiment. Ton laptop a la FEK. Ta tour ne l'a pas. Pour que la sync ait un sens, il faut transférer la FEK. Le plus simple est de te connecter sur ta tour : le serveur lui envoie la copie de la FEK wrappée par ton mot de passe, et elle la déballe sur place. L'autre voie évite de retaper le mot de passe : le pairage avec un appareil déjà déverrouillé. Dans les deux cas, il ne faut surtout pas confier la FEK en clair au serveur, sinon tout l'édifice s'effondre.
Pour le pairage, Filarr utilise un échange de clés Diffie-Hellman sur courbe elliptique (ECDH, courbe P-256), implémenté dans pairingService.ts. Le déroulé :
- L'appareil A (celui qui a la FEK) génère une paire de clés ECDH éphémère et un code de pairage à 6 chiffres. Il publie sur le serveur, indexé par le code, un engagement sur sa clé publique (son empreinte SHA-256), pas encore la clé elle-même.
- L'appareil B saisit le code ou scanne le QR code, génère sa propre paire ECDH et publie sa clé publique.
- A révèle alors sa clé publique, et chaque appareil calcule de son côté le même secret partagé de 256 bits via
deriveBits. C'est la magie de Diffie-Hellman : les deux appareils arrivent au même secret sans jamais l'avoir transmis. - A chiffre (wrappe) la FEK en AES-256-GCM avec une clé dérivée de ce secret partagé et d'un secret de 32 octets que A place dans le QR code sans jamais l'envoyer au serveur. En mode manuel, sans QR code, les deux écrans affichent d'abord six chiffres de contrôle tirés de l'échange, et tu confirmes qu'ils coïncident. A uploade ensuite le résultat.
- B télécharge la FEK wrappée et la déchiffre avec la clé qu'il a lui-même calculée.
Le serveur relaie deux clés publiques et un blob de FEK chiffrée. Il ne possède aucune des deux clés privées ECDH (elles restent en mémoire sur chaque appareil), donc il ne peut pas reconstituer le secret partagé, donc il ne peut pas déballer la FEK. S'il substituait ses propres clés publiques, il lui manquerait encore le secret du QR code, et en mode manuel les six chiffres de contrôle ne coïncideraient plus. La première version du protocole n'avait pas cette protection : le code à 6 chiffres, que le serveur connaît, servait seul de sel. Le commentaire du service est catégorique : "The FEK never leaves a device in cleartext."
Le pairage est durci : le code à 6 chiffres expire après 5 minutes, et la récupération de la clé publique est limitée à 3 tentatives par code (rateLimit dans le Worker), au-delà la session est invalidée. Ça réduit la fenêtre pour deviner un code par force brute.
Une fois la FEK reçue, l'appareil B la re-wrappe localement avec son propre mot de passe de vault et la stocke, puis déclenche une première sync qui télécharge les manifests, reconstruit les profils et rapatrie les fichiers.
Choix de conception et trade-offs (honnêtes)
Le serveur proxifie au lieu d'utiliser de vraies URL présignées S3. Le code note que le binding R2 des Workers ne supporte pas nativement les URL présignées, d'où l'approche par jeton où le Worker relaie l'octet-stream. Trade-off : le Worker "touche" les octets au passage. Mais comme ces octets sont déjà du ciphertext, ça ne casse pas le modèle zero-knowledge ; ça ajoute juste un saut réseau et un peu de charge sur le Worker.
Le fileId est un hash déterministe du chemin. Avantage : pas de table de correspondance à maintenir, et le même fichier produit toujours le même identifiant, ce qui simplifie la déduplication et le suivi. Trade-off : le serveur voit un identifiant stable par fichier, donc il peut observer qu'un "certain fichier" est modifié souvent, même sans jamais savoir lequel. C'est un compromis assumé entre simplicité et minimisation absolue des métadonnées.
Une seule FEK par compte, pas une clé rotative par version. Ça rend le changement de mot de passe instantané (on ne re-chiffre que la FEK, jamais les fichiers, cf. rewrapFEK) et le pairage simple. Trade-off : si la FEK était compromise, elle donnerait accès à tout le contenu du compte. La défense repose donc entièrement sur le fait que la FEK ne sort jamais en clair et n'est stockée côté serveur que wrappée.
Zero-knowledge veut dire zéro filet de sécurité côté serveur. Puisque le serveur ne détient ta clé que wrappée, il ne peut pas t'aider à récupérer tes données si tu perds à la fois ton mot de passe et ta phrase de 24 mots. C'est le prix direct du modèle. Filarr le compense avec la phrase de récupération BIP-39, mais c'est à toi de la garder.
Threat model : ce que ça protège, et contre quoi
Le modèle protège efficacement contre :
- Une compromission du stockage cloud. Si quelqu'un exfiltre le bucket R2, il obtient des chunks
.encet desmanifest.encinexploitables sans la FEK, qui n'y est pas. - Un accès interne côté fournisseur. Un employé de la plateforme, une demande d'une autorité judiciaire ou une erreur de configuration exposent au pire des blobs chiffrés, la copie wrappée de la FEK et des métadonnées techniques (tailles, nombre de chunks, horodatages).
- Une interception réseau. Le trafic passe par TLS, et même sans TLS le contenu serait déjà chiffré au repos par la FEK.
- Le relais de la clé pendant le pairage. Le serveur intermédiaire ne peut pas déballer la FEK, grâce à l'ECDH et au secret du QR code (ou aux six chiffres de contrôle en mode manuel).
Le modèle ne protège pas contre :
- Un appareil compromis. Si un attaquant contrôle ta machine pendant que le vault est déverrouillé, la FEK est en mémoire et il peut lire en clair. Le zero-knowledge protège les données en transit et au repos côté serveur, pas un endpoint déjà infecté.
- Un serveur modifié pendant la transition. Depuis le 16 octobre 2026, une app à jour se connecte avec une clé dérivée de ton mot de passe, qui ne permet pas de déballer la FEK. Pendant environ deux mois, un compte qui ne s'est pas encore reconnecté, ou une ancienne version de l'app, envoie encore le mot de passe lui-même (sous HTTPS, comparé à une empreinte bcrypt) : un serveur piraté ou modifié pourrait le capter, puis déballer la copie de la FEK qu'il garde.
- Un mot de passe de vault faible. La KEK est dérivée de ton mot de passe ; PBKDF2-SHA-512 à 600 000 itérations ralentit fortement une attaque par force brute, mais un mot de passe trivial reste un mot de passe trivial.
- L'analyse de métadonnées. Le serveur voit combien de fichiers tu as, leur taille approximative (au chunk près), et quand tu synchronises. Il ne voit pas ce qu'ils sont.
- Un code de pairage intercepté en temps réel. Le canal de pairage est durci (expiration, limite de 3 tentatives), mais le modèle suppose que tu transfères le code à 6 chiffres vers ton propre appareil dans la fenêtre de 5 minutes.
Limites connues
- Métadonnées visibles. Taille des chunks, nombre de fichiers, fréquence de sync et usage de stockage sont observables côté serveur. C'est inhérent à un service de sync qui doit gérer des quotas et du versioning.
- Le proxy Worker voit passer le ciphertext. Choix pragmatique lié aux limites du binding R2, sans impact sur la confidentialité du contenu, mais à connaître.
- Pas de récupération assistée. Perte du mot de passe et de la phrase de 24 mots égale perte définitive. Le revers direct de l'absence de clé en clair côté serveur.
- Une seule FEK par compte. Pas de rotation de clé par fichier ni de révocation granulaire par appareil dans le flux de sync actuel.
- Écosystème jeune. Filarr est une application de 2026, avec un mobile en cours et une communauté plus petite que celle d'un Obsidian. Le modèle crypto est solide et le code du client desktop est public (BSL 1.1, « source available »), ce qui permet d'en auditer le chiffrement local (le code de la sync, lui, n'est pas publié), mais c'est un logiciel jeune : la meilleure garantie reste la vérifiabilité du code, pas l'ancienneté.
FAQ technique
Le serveur peut-il déchiffrer mes fichiers s'il est contraint légalement de le faire ? Pas à partir de ce qu'il stocke. Le serveur ne détient ni la FEK en clair, ni la KEK, ni les clés privées ECDH : seulement la FEK wrappée par ton mot de passe et par ta phrase de récupération. Il ne peut transmettre que des blobs chiffrés, cette copie wrappée et des métadonnées techniques. La limite, pendant la transition : un serveur modifié pour capter le mot de passe d'un compte qui l'envoie encore pourrait ensuite déballer la clé de ce compte.
Où est stockée la FEK, exactement ? En clair, uniquement en mémoire de l'appareil quand le vault est déverrouillé. Sur le disque, elle existe seulement sous forme wrappée : chiffrée par une KEK dérivée du mot de passe (PBKDF2-SHA-512, 600k itérations), et dans une copie de secours wrappée par la phrase BIP-39. Le serveur garde aussi ces deux versions wrappées, pour qu'un nouvel appareil puisse s'ouvrir avec ton mot de passe ou ta phrase ; pendant le pairage, une autre version transite, chiffrée par une clé éphémère que le serveur ne connaît pas.
Comment un deuxième appareil obtient-il la clé sans passer par le serveur en clair ? Soit en se connectant, ce qui télécharge la copie de la FEK wrappée par ton mot de passe, soit via un échange ECDH sur courbe P-256. Dans ce second cas, les deux appareils calculent chacun de leur côté le même secret partagé de 256 bits à partir de la clé publique de l'autre et de leur propre clé privée. La FEK est chiffrée avec une clé dérivée de ce secret avant d'être relayée. Le serveur ne voit que des clés publiques et un blob chiffré.
Les noms de mes fichiers et dossiers fuient-ils vers le cloud ?
Non. Les fichiers sont rangés sous un identifiant sha256(chemin) tronqué à 32 caractères hex, et les vrais chemins ne vivent que dans le manifest, lui-même chiffré avec la FEK avant tout upload. Le serveur voit des UUID de profil et des identifiants hachés, pas des noms lisibles.
La sync est-elle obligatoire ? Non. Filarr est local-first : gratuit pour toujours en usage local, 100% hors-ligne, et la sync cloud est une option, gratuite jusqu'à 5 Go, puis à partir de 4 euros par mois. Le cloud "ne fait que suivre" ; il n'est jamais le produit.
Puis-je utiliser mon propre stockage plutôt que celui de Filarr ? Le backend de sync repose sur Cloudflare R2, et le modèle est compatible avec un bucket S3-compatible que tu fournis (BYOS). Comme tout ce qui est envoyé est déjà chiffré côté client, changer de destination de stockage ne change rien au fait que le contenu reste opaque pour l'hébergeur.
En quoi c'est différent de la sync chiffrée d'Obsidian ou d'un cloud classique ? La différence tient à qui détient la clé. Dans un modèle zero-knowledge, la clé ne quitte jamais tes appareils en clair et le serveur ne stocke que des données chiffrées qu'il ne peut pas lire, y compris l'arborescence. Filarr combine ça avec un chiffrement par fichier natif et un transfert de clé par ECDH, là où beaucoup de solutions chiffrent avec une clé que le fournisseur peut, en théorie, utiliser.