Guide Web Crypto API : chiffrement natif du navigateur pour développeurs
Maîtrisez l'API Web Crypto pour créer des applications de transfert chiffré. Guide complet AES-GCM, RSA-OAEP et gestion des clés dans le navigateur.
L'API Web Crypto (spécifiée dans la recommandation W3C Web Cryptography API, exposée via window.crypto.subtle) est la méthode native du navigateur pour effectuer de la cryptographie sans charger une bibliothèque crypto sur le réseau. Elle prend en charge AES-GCM, AES-CBC, AES-CTR, AES-KW, HMAC, RSA-OAEP, RSA-PSS, RSASSA-PKCS1-v1_5, ECDH, ECDSA, HKDF et PBKDF2 dans tous les navigateurs modernes (Chrome 37+, Firefox 34+, Safari 10.1+, Edge 79+). Pour les applications de transfert de fichiers, cela signifie que chaque octet de texte chiffré peut être généré côté client avant le téléversement, le navigateur fournissant une implémentation en temps constant et auditée. Ce guide parcourt les primitives importantes pour le transfert de fichiers chiffré et les pièges qui font trébucher chaque première implémentation.
SubtleCrypto est basé sur les Promises et asynchrone
Chaque méthode sur crypto.subtle retourne une Promise. C'est délibéré : les opérations cryptographiques peuvent être déléguées à du matériel ou à des threads d'arrière-plan, forcer l'async empêche donc l'API d'être mal utilisée de façons qui bloqueraient le thread principal. Forme du code :
const key = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true, // extractable
["encrypt", "decrypt"]
);
Le deuxième argument (true) marque la clé comme extractable, ce qui signifie qu'elle peut ensuite être exportée via crypto.subtle.exportKey(). Pour les clés à longue durée de vie, mettez-le à false pour garder les octets bruts inaccessibles depuis JavaScript. Pour les clés que vous devez sérialiser dans un fragment d'URL (le schéma HexaTransfer), mettez-le à true.
Le troisième argument est un tableau d'usages de clé. Une clé générée avec ["encrypt"] ne peut pas être utilisée pour déchiffrer, même si AES-GCM est symétrique. Cette séparation empêche qu'un flux de chiffrement compromis soit utilisé abusivement pour déchiffrer des données historiques.
AES-GCM pour le chiffrement symétrique de fichiers
AES-GCM est le cheval de travail pour le contenu des fichiers. Il fournit un chiffrement authentifié avec données associées (AEAD) : texte chiffré plus tag d'authentification plus données associées optionnelles authentifiées mais non chiffrées. Pour le transfert de fichiers, utilisez une clé de 256 bits et un nonce de 96 bits conformément aux recommandations NIST SP 800-38D.
const iv = crypto.getRandomValues(new Uint8Array(12)); // nonce 96 bits
const ciphertext = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
plaintext
);
La sortie inclut un tag d'authentification GCM de 128 bits ajouté au texte chiffré. Le déchiffrement vérifie automatiquement le tag et lève une exception s'il ne correspond pas. Ne réutilisez jamais un nonce avec la même clé ; la sécurité de GCM s'effondre catastrophiquement sur la réutilisation de nonce (les attaquants peuvent récupérer la clé d'authentification). Pour le transfert de fichiers où chaque fichier obtient une clé fraîche, les nonces aléatoires sont sûrs ; pour les clés à longue durée de vie, utilisez un compteur.
PBKDF2 pour les clés dérivées d'un mot de passe
Quand les utilisateurs saisissent un mot de passe pour protéger un fichier, vous ne pouvez pas utiliser le mot de passe directement comme clé AES. Faites-le passer par PBKDF2 d'abord :
const passwordKey = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(password),
"PBKDF2",
false,
["deriveKey"]
);
const aesKey = await crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: crypto.getRandomValues(new Uint8Array(16)),
iterations: 600000,
hash: "SHA-256",
},
passwordKey,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
Les recommandations OWASP 2023 sur le hachage des mots de passe préconisent au minimum 600 000 itérations pour PBKDF2-SHA-256. Tout ce qui est en dessous de 310 000 est en dessous des meilleures pratiques actuelles. Le sel doit être aléatoire et stocké avec le texte chiffré (il n'est pas secret, mais doit être unique).
Pour du nouveau code en 2026, envisagez Argon2id plutôt que PBKDF2. Argon2 n'est pas encore dans l'API Web Crypto, mais des bibliothèques comme argon2-browser ou @noble/hashes fournissent des implémentations JavaScript/WASM. Argon2id résiste bien mieux aux attaques GPU que PBKDF2.
RSA-OAEP pour l'encapsulation de clés
Pour les scénarios où vous souhaitez chiffrer la clé AES d'un fichier avec la clé publique d'un destinataire, utilisez RSA-OAEP. Générez des clés :
const keyPair = await crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 4096,
publicExponent: new Uint8Array([1, 0, 1]), // 65537
hash: "SHA-256",
},
true,
["encrypt", "decrypt"]
);
Utilisez modulusLength 4096 pour les nouvelles clés ; 2048 est acceptable mais commencera à être déprécié à mesure que les délais quantiques se précisent. RSA-OAEP ne chiffre que de petites charges utiles (au plus modulusLength/8 - 2*hashLength - 2 octets), donc encapsulez une clé AES de 256 bits plutôt que de chiffrer directement le contenu du fichier.
Pour les applications sensibles aux performances, ECDH avec P-256 ou P-384 est une meilleure alternative à RSA. La génération de clé est un ordre de grandeur plus rapide et les tailles de clé sont bien plus petites.
Streaming pour les gros fichiers
Un fichier de 2 Go ne tient pas confortablement dans un ArrayBuffer de navigateur. Chrome, Firefox et Safari vous permettent tous de lire des fichiers avec File.stream() retournant un ReadableStream, puis de les traiter par morceaux. L'API Web Crypto elle-même n'a pas encore de méthodes de chiffrement/déchiffrement en streaming (c'est une lacune dans la spec), donc deux solutions de contournement :
- Diviser en morceaux (64 Ko ou 1 Mo) et chiffrer chacun avec un nonce unique. Le destinataire concatène dans l'ordre. Cela perd le vrai AEAD sur le fichier entier mais fonctionne dans la plupart des cas.
- Utiliser une bibliothèque crypto WASM (libsodium.js, @noble/ciphers avec backend WASM) qui supporte les modes AEAD en streaming comme XChaCha20-Poly1305 ou AES-GCM-SIV.
Pour les transferts de moins de quelques centaines de mégaoctets, AES-GCM en mémoire tampon fonctionne bien et est bien plus simple. Au-delà, le streaming devient nécessaire pour éviter la pression mémoire.
Export, import de clés et fragments d'URL
Pour les flux de type HexaTransfer où la clé voyage dans le fragment d'URL :
const rawKey = await crypto.subtle.exportKey("raw", aesKey);
const keyBase64 = btoa(String.fromCharCode(...new Uint8Array(rawKey)));
// URL de partage comme https://example.com/fichier/abc123#key=keyBase64
Les fragments d'URL ne sont jamais envoyés aux serveurs dans les requêtes HTTP (les navigateurs les suppriment). La clé reste ainsi côté client même si l'utilisateur partage un lien. Côté destinataire :
const keyBase64 = window.location.hash.slice(5); // supprimer "#key="
const rawKey = Uint8Array.from(atob(keyBase64), c => c.charCodeAt(0));
const key = await crypto.subtle.importKey(
"raw", rawKey, "AES-GCM", false, ["decrypt"]
);
Utilisez l'encodage base64url (remplacez + par -, / par _, supprimez le rembourrage) pour éviter les problèmes d'encodage d'URL.
Pièges courants
Utiliser Math.random() pour les sels ou les IV. Math.random() n'est pas cryptographiquement sûr. Utilisez toujours crypto.getRandomValues().
Réutiliser des IV avec la même clé. Les propriétés de sécurité de GCM s'effondrent complètement sur la réutilisation de nonce. Les nonces aléatoires de 96 bits entrent en collision après ~2^48 chiffrements sous la même clé (borne d'anniversaire). Pour le transfert de fichiers où chaque fichier a sa propre clé, c'est sûr ; pour les clés à longue durée de vie, utilisez un compteur.
Oublier HTTPS. crypto.subtle n'est disponible que dans les contextes sécurisés (HTTPS ou localhost). Sur une origine non sécurisée, crypto.subtle est indéfini.
Stocker des clés extractables dans IndexedDB sans protection. Si vous devez persister des clés, encapsulez-les (par ex. avec une clé dérivée d'une phrase secrète) avant de les stocker. Ne stockez jamais des clés AES brutes dans localStorage, accessible à tout script sur l'origine.
Faire confiance aux mots de passe fournis par l'utilisateur sans PBKDF2. Un mot de passe brut converti en octets UTF-8 n'est pas une clé de 256 bits. Dérivez toujours.
Ne pas vérifier les tags d'authentification. crypto.subtle.decrypt() le fait automatiquement pour AES-GCM, mais si vous implémentez des protocoles personnalisés par-dessus, ne sautez pas cette vérification.
Nuances de compatibilité entre navigateurs
Tous les navigateurs majeurs supportent Web Crypto sur HTTPS. Quelques particularités :
- Le PBKDF2 de Safari était plus lent que Chrome/Firefox pendant des années ; l'écart s'est comblé dans Safari 15.
- Firefox applique une validation des entrées plus stricte ; le code qui fonctionne dans Chrome peut lever une
OperationErrordans Firefox. Testez dans les deux. - Web Crypto dans les service workers fonctionne mais nécessite que la portée d'enregistrement soit HTTPS.
- Node.js fournit
require("crypto").webcryptoavec une API compatible depuis Node 15, utile pour du code crypto isomorphe.
Quand utiliser une bibliothèque à la place
Web Crypto couvre bien les bases mais manque de primitives modernes comme ChaCha20-Poly1305, Argon2, X25519 et Ed25519 (bien qu'Ed25519 arrive). Pour celles-là, libsodium.js (via WASM) ou @noble/ciphers / @noble/curves (JavaScript pur, audités) sont les principales options. HexaTransfer utilise les primitives Web Crypto directement pour AES-GCM et PBKDF2 car elles couvrent le chemin de transfert de fichiers sans dépendances.
Pour un flux de transfert chiffré complet, Web Crypto vous y amène en moins de 100 lignes de code : générer une clé AES, la dériver ou la rendre aléatoire, chiffrer le fichier, téléverser le texte chiffré, partager le lien avec la clé dans le fragment, le destinataire importe la clé et déchiffre. C'est tout.
Essayez sur hexatransfer.com — gratuit, sans compte, jusqu'à 10 Go.
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