Construire une application de partage de fichiers chiffrée de zéro
Tutoriel pas à pas pour construire une application de partage de fichiers chiffrée. Chiffrement frontend, backend sécurisé et déploiement.
Construire une application de partage de fichiers chiffrée signifie placer la cryptographie dans le navigateur, pas sur le serveur. Une stack minimale ressemble à ceci : un frontend React propulsé par Vite qui chiffre avec AES-256-GCM via la Web Crypto API, un backend Fastify qui traite chaque upload comme un blob opaque, un stockage compatible S3 (Cloudflare R2 ou Backblaze B2), et une courte URL de partage où la clé de déchiffrement se trouve après le # afin qu'elle n'atteigne jamais le serveur. Selon le RGPD et les recommandations de l'ANSSI, le chiffrement de bout en bout côté client est la seule architecture qui garantit réellement que le prestataire ne peut pas accéder aux données — une distinction juridique qui compte. Vous pouvez livrer une version fonctionnelle en environ 400 lignes de code et l'héberger pour moins de 5 € par mois.
Les décisions d'architecture qui comptent vraiment
La décision la plus importante est l'emplacement de la clé de chiffrement. Si elle touche un jour votre serveur, vous n'avez pas de chiffrement de bout en bout, vous avez du chiffrement côté serveur avec des étapes supplémentaires. Le bon schéma : générez une clé aléatoire de 256 bits dans le navigateur, chiffrez le fichier avec elle, uploadez le texte chiffré, et placez la clé dans le fragment d'URL (https://yourapp.com/f/abc123#k=base64key). Les navigateurs n'envoient jamais les fragments dans les requêtes HTTP, donc la clé reste côté client.
Deuxième décision : les uploads par chunks. Pour les fichiers de plus de 100 Mo, vous avez besoin d'uploads multipart reprenables, sinon n'importe quelle Wi-Fi instable tue le transfert. L'API multipart de S3 prend en charge des chunks minimaux de 5 Mo et jusqu'à 10 000 parties, donnant un plafond de 50 Go. Prévoyez-le dès le premier jour.
Mise en place du squelette du projet
Commencez avec deux packages : un frontend Vite + React et un backend Fastify. Le frontend gère tout le chiffrement, le backend gère le stockage et les métadonnées. Utilisez TypeScript pour avoir la sécurité de types sur ArrayBuffer et CryptoKey.
pnpm create vite@latest frontend -- --template react-ts
pnpm create fastify backend
Ajoutez ces dépendances au backend : @fastify/multipart, @aws-sdk/client-s3, @aws-sdk/s3-request-presigner, et better-sqlite3 pour les métadonnées de partage. Gardez votre schéma SQLite minimal : shares(id, object_key, size_bytes, expires_at, download_count, max_downloads). Aucun nom de fichier, aucune donnée utilisateur, aucune IP — ce qui simplifie considérablement la conformité RGPD.
Écrire le pipeline de chiffrement frontend
Générez une clé, chiffrez avec AES-256-GCM, et produisez un blob de texte chiffré plus une clé en base64 pour le fragment d'URL :
async function encryptFile(file: File) {
const key = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const plaintext = await file.arrayBuffer();
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv }, key, plaintext
);
const rawKey = await crypto.subtle.exportKey('raw', key);
const blob = new Blob([iv, new Uint8Array(ciphertext)]);
return { blob, keyBase64: toBase64(rawKey) };
}
Pour les fichiers de plus de 50 Mo, remplacez ceci par une version en streaming qui chiffre des chunks de 4 Mo et les ajoute à un ReadableStream. Le tas mémoire de Mobile Safari s'étouffe sur tout ce qui dépasse environ 400 Mo dans un seul ArrayBuffer.
Concevoir l'endpoint d'upload
Le backend ne devrait rien savoir d'utile. Acceptez un POST avec le texte chiffré, générez un identifiant aléatoire de 16 caractères URL-safe, stockez-le dans SQLite avec une date d'expiration, et streamez le corps directement vers S3 :
fastify.post('/upload', async (req, reply) => {
const id = nanoid(16);
const key = `blobs/${id}`;
const upload = new Upload({
client: s3,
params: { Bucket: 'hexa-transfers', Key: key, Body: req.raw }
});
await upload.done();
db.prepare('INSERT INTO shares VALUES (?, ?, ?, ?, 0, ?)').run(
id, key, req.headers['content-length'], Date.now() + 7*86400*1000, 10
);
return { id };
});
Expiration par défaut à 7 jours, maximum 10 téléchargements. Optez pour des valeurs par défaut agressives car l'alternative est une croissance de stockage illimitée. Utilisez un cron pour supprimer les blobs expirés chaque nuit.
Générer des liens de partage avec des clés en fragment
Une fois l'upload terminé, construisez l'URL de partage côté client :
const { id } = await uploadResponse.json();
const shareUrl = `${location.origin}/f/${id}#k=${keyBase64}`;
Ce fragment ne quitte jamais le navigateur de l'utilisateur. Quand un destinataire clique sur le lien, votre application React lit window.location.hash, extrait la clé, récupère le texte chiffré et déchiffre localement. Le serveur n'a aucun accès au texte en clair à moins de pousser du JavaScript malveillant vers l'utilisateur — ce qui est exactement pourquoi vous devriez livrer une Content Security Policy stricte et épingler les hachages des sous-ressources.
Implémenter le flux de téléchargement
Sur la page de téléchargement, récupérez le texte chiffré en flux et déchiffrez en alignement sur les chunks :
const keyRaw = base64ToBytes(location.hash.slice(3));
const key = await crypto.subtle.importKey(
'raw', keyRaw, { name: 'AES-GCM' }, false, ['decrypt']
);
const resp = await fetch(`/blob/${id}`);
const iv = new Uint8Array(await resp.body.getReader().read().then(r => r.value.slice(0, 12)));
const ct = await resp.arrayBuffer();
const pt = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ct.slice(12));
const url = URL.createObjectURL(new Blob([pt]));
Pour les grands fichiers, utilisez StreamSaver.js ou l'API File System Access pour que les octets déchiffrés aillent directement sur le disque plutôt qu'en RAM.
Déployer l'ensemble
Frontend : poussez vers Cloudflare Pages ou Vercel, les deux niveaux gratuits gèrent cela. Backend : un seul droplet DigitalOcean à 5 € faisant tourner Node 22 derrière Caddy (TLS 1.3 automatique). Stockage : Cloudflare R2 donne des frais d'egress nuls, ce qui est la fonctionnalité décisive pour le partage de fichiers à grande échelle.
Définissez des en-têtes stricts via Caddy : Strict-Transport-Security, Content-Security-Policy: default-src 'self'; script-src 'self', X-Content-Type-Options: nosniff, et Referrer-Policy: no-referrer. Ces mesures ferment les vecteurs courants pour fuiter le fragment d'URL via les référents ou les scripts injectés.
Rendre l'application conforme au RGPD par défaut
Parce que le serveur ne voit que du texte chiffré, vous avez presque rien qui se qualifie comme donnée personnelle au sens de l'Article 4 du RGPD. Néanmoins, documentez vos relations avec les sous-traitants (R2 et DigitalOcean), fixez une rétention maximale des blobs à 30 jours, et publiez un avis clair indiquant que les liens de partage contiennent des clés et doivent être transmis via des canaux fiables. Ajoutez une limitation de débit à 50 uploads par IP par heure pour décourager les abus sans journaliser les identités des utilisateurs.
HexaTransfer est construit sur cette architecture — Web Crypto dans le navigateur, stockage de blobs opaques, clés dans les fragments d'URL, aucun texte en clair sur les serveurs.
Ce qu'il faut ajouter une fois le MVP fonctionnel
Une fois le flux de base livré, les ajouts à plus haute valeur sont : la protection par mot de passe au-dessus de la clé de fragment (PBKDF2 avec 600 000 itérations), les notifications email par téléchargement via une boîte de réception éphémère, et le signalement d'abus qui permet de signaler le hachage du texte chiffré sans révéler le contenu. Évitez d'ajouter des comptes à moins d'avoir une raison spécifique — ils transforment votre application d'outil de confidentialité en responsabilité en matière de données du jour au lendemain. Livrez petit, restez opinionné, et laissez la cryptographie faire le gros du travail.
Essayez-le sur https://hexatransfer.com — gratuit, sans compte, 10 Go maximum.
Envoyez vos fichiers volumineux en toute sécurité avec le chiffrement de bout en bout
Transférez des fichiers jusqu'à 10 Go gratuitement avec le chiffrement de bout en bout. Aucun compte requis. Vos fichiers sont chiffrés dans votre navigateur avant l'envoi — personne d'autre ne peut les lire.
Envoyer un fichier