Come la sincronizzazione cloud facoltativa di Filarr resta zero-knowledge (uno sguardo dentro il codice)

La sincronizzazione di Filarr è zero-knowledge: i file partono già crittografati, il server conserva solo blob opachi e non vede mai la chiave in chiaro.

Mathis Belouar-Pruvot

· fondatore di Filarr · · 17 min di lettura

Hai comprato un secondo portatile, oppure vuoi le tue note di lavoro sia sul computer di casa sia su quello dell’ufficio. Nel momento in cui i tuoi file lasciano un dispositivo, ti trovi davanti alla stessa domanda silenziosa che ogni strumento cloud ti chiede di ignorare: chi può leggerli lungo il tragitto, e chi può leggerli una volta arrivati?

La risposta di Filarr è che i suoi server conservano solo dati crittografati che non possono leggere. I tuoi file si sincronizzano tra i tuoi dispositivi, ma il server che li sposta non detiene mai la tua chiave in chiaro, non vede mai un nome di file e non tocca mai un solo byte di contenuto leggibile. Questo articolo spiega esattamente come funziona, direttamente dal codice di sincronizzazione, perché «zero-knowledge» è un’affermazione che ha senso solo se puoi vedere il meccanismo che c’è dietro.

Una precisazione sul perimetro prima di cominciare: in Filarr la sincronizzazione cloud è facoltativa. Il prodotto è local-first, i tuoi file vivono crittografati sul tuo disco che tu attivi o no la sincronizzazione. Tutto ciò che segue descrive cosa succede quando scegli di attivarla.

Risposta rapida

Filarr sincronizza i tuoi file caricandoli esattamente come sono già sul tuo disco: crittografati in AES-256-GCM con la chiave del tuo account (la FEK), con un valore casuale nuovo per ogni file. La FEK non raggiunge mai il server in forma leggibile. Il Cloudflare Worker che gestisce la sincronizzazione conserva sempre e solo blob crittografati opachi sotto ID di file opachi (un hash SHA-256 del percorso locale), più un piccolo manifest a sua volta crittografato in AES-256-GCM con la tua FEK. Il server può vedere quanti blob hai e quanto sono grandi, ma non i loro nomi, la struttura delle cartelle o il contenuto. Un secondo dispositivo ottiene la tua chiave accedendo all’account, il che scarica la copia della FEK cifrata con la tua password, oppure da dispositivo a dispositivo con uno scambio di chiavi ECDH, in cui la FEK viaggia cifrata e il server inoltra solo chiavi pubbliche.

Il problema che risolve davvero

La maggior parte delle sincronizzazioni si basa sul «fidati del fornitore». Notion conserva le tue pagine in forma leggibile sui suoi server. Dropbox detiene le chiavi dei file che crittografa a riposo, il che significa che Dropbox (o chiunque riesca a farsi consegnare i dati da Dropbox) può leggerli. Anche gli strumenti che crittografano in transito di solito decrittografano all’arrivo, per poter indicizzare, visualizzare in anteprima o cercare i tuoi contenuti lato server.

Questo modello è comodo, ed è anche tutto il problema. Comodità lato server significa leggibilità lato server. Se il fornitore può generare una miniatura del tuo documento, il fornitore può leggere il tuo documento.

Il vincolo di Filarr era l’opposto: il backend di sincronizzazione doveva essere davvero incapace di leggere qualsiasi cosa conservi, pur facendo il lavoro utile di spostare byte tra i tuoi computer e risolvere i conflitti. Qui «zero-knowledge» non è un aggettivo di marketing, è un requisito di design che ha determinato dove avviene la crittografia e cosa il server ha il diritto di sapere.

Come funziona, passo dopo passo

1. La crittografia è già avvenuta, prima che esista la sincronizzazione

È l’idea chiave che rende semplice tutto il resto. In Filarr i file sono crittografati sul disco in AES-256-GCM, con la chiave del tuo account e un valore casuale nuovo per ogni file, nel momento stesso in cui entrano nella tua cassaforte. La sincronizzazione non ha aggiunto la crittografia. La eredita.

Quando il servizio di sincronizzazione legge un file per caricarlo, legge dal disco i byte già crittografati e li invia così come sono. Non c’è nessun passaggio di decrittografia e ricrittografia, nessuna area di transito in chiaro. Il commento in cima al servizio di sincronizzazione lo dice chiaramente: «Files on disk are already encrypted with FEK, uploaded as-is» (i file sul disco sono già crittografati con la FEK e vengono caricati così come sono). Il server riceve testo cifrato perché il testo cifrato è l’unica cosa che sia mai esistita fuori dalla memoria dell’app.

2. I nomi dei file diventano ID opachi

Un blob di testo cifrato è inutile per un attaccante, ma un nome di file come «lettera-di-dimissioni-definitiva.pdf» non lo è. Per questo Filarr non invia mai i nomi dei file.

Quando un file cambia, il servizio di sincronizzazione ricava un identificatore calcolando l’hash del suo percorso locale: crypto.createHash('sha256').update(localPath).digest('hex').slice(0, 32). Il commento nel codice è esplicito sull’intento: «Derive opaque fileId from local path, never expose real file names» (ricavare un fileId opaco dal percorso locale, non esporre mai i veri nomi dei file). Le cartelle sono indicate dai loro UUID, che a loro volta non hanno alcun significato. Sul server i tuoi file vivono in un percorso come users/{userId}/profiles/{profileId}/files/{fileId}/chunk_0.enc, in cui ogni segmento è un ID casuale o un hash opaco.

3. I file vengono divisi in blocchi

Prima del caricamento, i file vengono divisi in blocchi da 4 MB (CHUNK_SIZE = 4 * 1024 * 1024). Ogni blocco è conservato come oggetto .enc separato in Cloudflare R2. La suddivisione in blocchi mantiene i singoli caricamenti abbastanza piccoli da poter essere ritentati a basso costo su una connessione instabile, e fa sì che un file grande modificato non imponga un unico trasferimento monolitico. I caricamenti avvengono in piccoli lotti paralleli (tre alla volta), con dietro una coda di ritentativi persistente, così una connessione caduta riprende invece di ricominciare da capo.

4. Il manifest: come i dispositivi si mettono d’accordo sulla realtà

Ecco la parte davvero interessante, perché è lì che un sistema zero-knowledge di solito perde informazioni. Per sincronizzarsi, due dispositivi devono mettersi d’accordo su quali file esistono, quali sono cambiati e quale ha vinto un conflitto. Questo coordinamento normalmente richiede che il server capisca la struttura dei tuoi file. Il server di Filarr non la capisce.

Ci sono due manifest, e la distinzione conta.

Il manifest locale (sync-manifest.json) vive sul tuo disco e non è crittografato, ma è volutamente privo di qualsiasi cosa sensibile. Contiene checksum SHA-256, dimensioni in byte, stati di sincronizzazione e ID di file opachi. Nessun nome di file, nessun contenuto. Il campo localPath, che associa un ID a un percorso reale, esiste solo in questa copia locale e viene rimosso prima che qualsiasi cosa parta verso il cloud.

Il manifest cloud (manifest.enc su R2) è la fonte di verità condivisa tra i dispositivi, ed è crittografato con la tua FEK in AES-256-GCM (encryptWithFEK). Il suo formato sul disco è un breve marcatore («fek:») seguito da un IV di 12 byte e dal testo cifrato con il suo tag di autenticazione GCM. Il server lo conserva come un blob opaco in più. Quando un secondo dispositivo vuole conoscere lo stato delle cose, scarica questo blob e lo decrittografa in locale, perché ha la FEK. Il server non può farlo, mai.

Tutta la logica di unione (quale file è più recente, quale è cambiato da entrambe le parti ed è quindi un conflitto) gira sul tuo dispositivo, sul manifest decrittografato, in mergeWithRemote. L’unico compito del server attorno al manifest è il controllo delle versioni.

5. Locking ottimistico, perché due dispositivi non si sovrascrivano a vicenda

Ogni profilo ha un contatore manifest_version conservato nel database D1 del Worker. Quando un dispositivo vuole caricare un nuovo manifest, dice al server quale versione ritiene sia quella attuale. Se la versione del server non corrisponde, restituisce un conflitto 409 e il caricamento viene rifiutato (if (profileSync.manifest_version !== body.version) return ... 409). Il dispositivo allora riscarica, riunisce e riprova al ciclo successivo.

Ecco il trucco elegante: il server può imporre «un solo scrittore vince per ogni versione» usando nient’altro che un numero intero. Non ha mai bisogno di capire il contenuto del manifest per impedire a due dispositivi di corrompere l’uno lo stato dell’altro. Coordinamento senza comprensione.

6. Upload e download passano da un proxy a token

I Worker non possono generare direttamente i classici URL prefirmati in stile S3, quindi Filarr usa uno schema a token. Il dispositivo fa una richiesta autenticata per uno slot di caricamento, il Worker controlla la tua quota di spazio, conserva in KV un breve record di intenzione (TTL di 15 minuti) indicizzato da un token casuale e restituisce un URL con il token. Il dispositivo invia quindi con un PUT il blocco crittografato a quell’URL, e il Worker lo trasferisce in R2 e incrementa il tuo contatore di utilizzo. Il token è l’autorizzazione per quella singola operazione, e viene eliminato all’uso. I byte che passano sono, ancora una volta, testo cifrato già crittografato.

7. Portare la chiave su un secondo dispositivo senza mai inviarla in chiaro

Tutto quanto sopra presuppone che entrambi i dispositivi abbiano la FEK. Il modo più semplice per portarla sul nuovo dispositivo è accedere all’account: il server invia la copia della FEK cifrata con la tua password, e il dispositivo la decifra in locale. L’altro modo salta la password: abbini il nuovo dispositivo a uno già sbloccato. Allora come fa l’abbinamento a far passare la chiave senza che il server la veda mai?

L’abbinamento usa uno scambio di chiavi ECDH effimero sulla curva P-256. Il flusso, da pairingService.ts:

Il dispositivo A (quello che ha già la tua cassaforte) genera una coppia di chiavi ECDH effimera e un codice casuale di 6 cifre. Il dispositivo B genera la propria coppia di chiavi ECDH. Ogni dispositivo pubblica la sua chiave pubblica sul server, recupera quella dell’altro e ricava in modo indipendente lo stesso segreto condiviso da 256 bit. La chiave che cifra la FEK è derivata da quel segreto condiviso e da un segreto di 32 byte che il dispositivo A inserisce nel codice QR e non invia mai al server; in modalità manuale, senza codice QR, entrambi gli schermi mostrano sei cifre di controllo derivate dallo scambio, e confermi che coincidono prima che qualsiasi cosa venga cifrata. Il dispositivo A cifra quindi la FEK con quella chiave usando AES-GCM e carica la chiave cifrata. Il dispositivo B la scarica, la decifra e la conserva in locale, cifrata di nuovo con la propria password.

La FEK attraversa la rete una sola volta, e solo in una forma che richiede il segreto condiviso per essere aperta. Il commento enuncia la garanzia: «The FEK never leaves a device in cleartext» (la FEK non lascia mai un dispositivo in chiaro). Il server inoltra chiavi pubbliche e un blob cifrato. Non ha mai il segreto condiviso, quindi non ha mai la FEK.

La sessione di abbinamento è rafforzata: il codice di 6 cifre scade dopo 5 minuti, e il server limita il recupero della chiave pubblica a 3 tentativi prima di invalidare del tutto la sessione, il che smorza la forza bruta sul codice.

8. Dove riposa davvero la FEK

Su ogni dispositivo la FEK è conservata cifrata. È crittografata da una KEK (key-encryption key, chiave di crittografia della chiave) derivata dalla tua password con PBKDF2-SHA512 a 600.000 iterazioni (ben oltre le 210.000 raccomandate dall’OWASP per PBKDF2-SHA512), e conservata a parte nel portachiavi del sistema tramite il safeStorage di Electron. La tua password deriva la KEK, la KEK apre la FEK e la FEK decrittografa i tuoi file. Il server conserva una copia della FEK cifrata, così che un nuovo dispositivo possa accedere, ma mai la FEK in chiaro.

Scelte di design e relativi compromessi

Crittografare una volta al confine della cassaforte, non al confine della sincronizzazione. Rendendo i file testo cifrato sul disco fin dall’inizio, la sincronizzazione diventa un semplice tubo e la proprietà zero-knowledge ne discende gratis. Il compromesso è che il server non può letteralmente aiutarti con nulla che dipenda dal contenuto: niente ricerca lato server, niente miniature, niente anteprima web. È una vera perdita di funzionalità, ed è il costo onesto della garanzia.

Unione lato client invece che lato server. Eseguire l’unione sul dispositivo mantiene il server all’oscuro, ma significa che la risoluzione dei conflitti dipende dagli orologi dei dispositivi e dal fatto che i manifest vengano scaricati nell’ordine giusto. L’algoritmo di unione si basa sui timestamp updatedAt e syncedAt, e quando entrambe le parti sono cambiate dall’ultima sincronizzazione non sceglie in silenzio un vincitore: crea un ramo, scrivendo una copia _conflict_{timestamp} così che non si perda nulla. Più sicuro, ma può lasciarti con dei duplicati da riconciliare a mano.

Un proxy a token invece della prefirma diretta verso R2. Far passare i byte dal Worker aggiunge un salto e un po’ di latenza rispetto a un vero URL prefirmato diretto verso lo storage. In cambio, il controllo della quota e dell’accesso resta centralizzato e semplice, e poiché il contenuto è già crittografato, il salto in più non vede nulla.

Blocchi da 4 MB. Blocchi piccoli rendono i ritentativi economici e il parallelismo facile, al costo di più oggetti e più andate e ritorno per i file grandi. Per uno spazio di lavoro di note e documenti, è l’orientamento giusto.

Modello di minaccia: cosa protegge e cosa no

Cosa ottiene un attaccante che copia i dati del server (o che ottiene dal fornitore che glieli trasmetta): una pila di blob .enc opachi, un manifest crittografato che non può aprire, ID di file opachi, un contatore di versione e la copia cifrata della tua FEK. Senza la tua password o la tua frase di recupero, non può leggere i tuoi file, le tue note, i nomi dei tuoi file o la struttura delle tue cartelle. Un attaccante che controlla il server mentre lo usi non può prendere la tua password dall’accesso di un’app aggiornata: dal 16 ottobre 2026 l’accesso invia una chiave derivata dalla tua password, non la password, e quella chiave non può aprire la FEK. Durante una transizione di circa due mesi, però, un account che non si è ancora ricollegato, o una vecchia versione dell’app, invia ancora la password stessa (via HTTPS), e un server modificato potrebbe intercettarla e aprire quella copia.

Cosa può ancora osservare lo stesso attaccante (siamo onesti su questo): i metadati. In particolare il numero dei tuoi file, la dimensione di ogni blob, il numero di blocchi, il tuo utilizzo totale dello spazio e i momenti delle tue sincronizzazioni. Le dimensioni dei file e la frequenza delle modifiche possono rivelare degli schemi. È il limite classico di qualsiasi archiviazione crittografata che conserva le dimensioni reali degli oggetti, e Filarr non aggiunge riempimento né raggruppa per nasconderlo. Se il tuo modello di minaccia include un avversario che analizza traffico e dimensioni, sappi che questo livello non è pensato per sconfiggerlo.

La finestra dell’abbinamento merita un’avvertenza specifica. Durante l’abbinamento il server inoltra entrambe le chiavi pubbliche, quindi un server malevolo potrebbe tentare un man-in-the-middle sullo scambio ECDH sostituendo le proprie chiavi pubbliche. Nella prima versione del protocollo questo attacco funzionava: il codice di 6 cifre, che faceva da sale per la chiave di cifratura, è noto anche al server. La versione attuale lo chiude: la chiave di cifratura dipende anche dal segreto trasportato dal codice QR, che nessuna route del server riceve mai, il dispositivo A si impegna sulla sua chiave pubblica prima di vedere quella di B, e in modalità manuale le sei cifre di controllo mostrate su entrambi gli schermi rivelano una chiave sostituita. Vale la pena nominarlo invece di far finta che non esista.

Limiti noti

  • I metadati non sono nascosti. Dimensioni dei blob, numero di file e tempi di sincronizzazione sono visibili al server. Contenuti e nomi no.
  • Lo zero-knowledge taglia in entrambe le direzioni. Poiché il server detiene la tua chiave solo in forma cifrata, non esiste un ripristino «password dimenticata» che recuperi i tuoi dati. Se perdi la password e la tua frase di recupero di 24 parole, il testo cifrato è irrecuperabile. È il prezzo di un server incapace di leggerti.
  • Nessuna funzione lato server sui tuoi contenuti. Ricerca, anteprime e accesso web ai contenuti dei file non possono esistere lato server, per progettazione.
  • L’abbandono dell’accesso con password non è finito. Dal 16 ottobre 2026 un’app aggiornata accede con una chiave derivata. Per circa due mesi, un account che non si è ancora ricollegato, o una vecchia versione dell’app, invia ancora la password (confrontata con un hash bcrypt, non conservata), che un server compromesso potrebbe intercettare, come descritto sopra.
  • Manifest legacy. I manifest più vecchi crittografati con una chiave locale del dispositivo vengono rilevati e migrati automaticamente, il che avviene in modo trasparente ma ricorda che il formato si è evoluto.

Una nota sulle licenze per i più curiosi dal punto di vista tecnico: il client desktop di Filarr è pubblicato sotto BSL 1.1 («source available»), quindi il modo in cui crittografa i tuoi file sul disco è ispezionabile. Il codice descritto in questo articolo no: sync.ts, syncService.ts e pairingService.ts appartengono ai moduli cloud, che non sono pubblicati. Le citazioni qui sopra vengono da quel codice privato.

FAQ tecniche

D: Il contenuto crittografato viene mai decrittografato sul server, anche solo per un attimo? No. I file sono già testo cifrato AES-256-GCM sul disco prima che la sincronizzazione parta, e vengono caricati byte per byte in quella forma. Il Worker trasferisce i blob in R2 senza mai detenere una chiave. Non c’è nessun passaggio di decrittografia, da nessuna parte, lato server.

D: Come fa un secondo dispositivo a coordinare la sincronizzazione se il server non può leggere il manifest? Il manifest cloud è a sua volta crittografato con la tua FEK. Ogni dispositivo lo scarica, lo decrittografa in locale ed esegue tutta l’unione sul dispositivo. Il server impone soltanto un contatore di versione intero (locking ottimistico) per evitare che due dispositivi si sovrascrivano a vicenda, cosa che può fare senza capirne il contenuto.

D: Come arriva la chiave di crittografia sul mio nuovo dispositivo senza toccare il server in chiaro? O accedendo all’account, il che scarica la copia della FEK cifrata con la tua password, o tramite uno scambio ECDH effimero sulla curva P-256. Nel secondo caso entrambi i dispositivi ricavano un segreto condiviso, la FEK viene cifrata in AES-GCM con una chiave derivata da quel segreto e viene inoltrato solo il blob cifrato. Il server non conosce mai la chiave di cifratura, quindi non conosce mai la FEK. Il codice di abbinamento scade in 5 minuti ed è limitato a 3 tentativi.

D: Filarr può leggere i miei file se riceve una richiesta di un’autorità? Non a partire da ciò che conserva. Filarr detiene la tua chiave solo in forma cifrata, con la tua password e con la tua frase di recupero. Il server conserva blob opachi e un manifest crittografato che non può aprire, quindi non c’è nulla di leggibile da trasmettere. Il limite è quello indicato sopra: durante la transizione, un server modificato per intercettare la password di un account che la invia ancora potrebbe poi aprire la chiave di quell’account. Il rovescio della medaglia è che Filarr non può nemmeno recuperare i tuoi dati se perdi sia la password sia la frase di recupero.

D: Posso usare il mio storage invece di quello di Filarr? Sì. La sincronizzazione supporta il BYOS (bring your own storage, porta il tuo storage) con qualsiasi bucket compatibile S3, così i blob crittografati finiscono in uno storage che controlli tu, con le stesse proprietà zero-knowledge.

D: Quali metadati può ancora dedurre il server su di me? Il numero di file, le dimensioni dei singoli blob, il numero di blocchi, lo spazio totale usato e i timestamp di sincronizzazione. Non può dedurre nomi di file, nomi di cartelle o contenuti. Se nascondere dimensioni e tempi è importante per te, tieni presente che questo livello non li riempie né li offusca.

Se vuoi verificare tu stesso una parte di tutto questo, il codice del client desktop è pubblico (BSL 1.1): mostra come i tuoi file vengono crittografati sul disco. I tre file citati qui (infra/cloudflare-worker/src/sync.ts per il lato server, electron/sync/syncService.ts per l’orchestrazione sul dispositivo ed electron/pairingService.ts per lo scambio di chiavi) appartengono ai moduli cloud privati, non al repository pubblico.