Aller au contenu
HexaTransfer
Retour au blog
Chiffrement et securite

Guide d'implémentation AES-GCM : chiffrement authentifié bien fait

Implémentez correctement le chiffrement AES-GCM dans votre application web. Gestion des nonces, des clés et pièges courants à éviter.

AES-GCM (Galois/Counter Mode) combine le chiffrement AES-CTR avec l'authentification GHASH pour produire un chiffrement authentifié avec données associées (AEAD). Une implémentation correcte utilise une clé de 256 bits, un nonce de 96 bits (12 octets) unique par clé (jamais réutilisé), un tag d'authentification de 128 bits, et optionnellement des données associées authentifiées (AAD) vérifiées mais non chiffrées. NIST SP 800-38D spécifie la construction exacte. Ratez n'importe lequel de ces points, surtout la réutilisation de nonce, et la sécurité de GCM s'effondre : une seule paire (clé, nonce) répétée permet aux attaquants de récupérer la clé d'authentification et de forger des textes chiffrés arbitraires. Ce guide couvre la manière correcte d'utiliser AES-GCM dans les contextes navigateur, Node et serveur.

Ce que GCM garantit réellement

Deux propriétés :

Confidentialité : le texte en clair ne peut pas être récupéré sans la clé. La couche de chiffrement en mode CTR d'AES-GCM fournit cette garantie.

Intégrité et authenticité : toute modification du texte chiffré, du nonce ou des données associées provoque l'échec du déchiffrement. GHASH produit un tag de 128 bits vérifié en temps constant lors du déchiffrement.

Ce que GCM ne garantit pas : la non-répudiation (c'est symétrique, donc quiconque a la clé peut produire des textes chiffrés valides), la protection contre la rejouabilité (c'est une préoccupation de couche supérieure), ou l'ordonnancement (pour les flux, vous devez enchaîner d'une façon ou d'une autre).

L'insight clé : GCM reste sécurisé uniquement quand les nonces sont uniques par clé. Pas « principalement uniques », pas « généralement uniques », réellement uniques. La preuve de sécurité s'effondre sur la réutilisation.

Gestion des nonces : ce qui compte le plus

Un nonce de 96 bits peut être généré de deux façons :

Aléatoire : crypto.getRandomValues(new Uint8Array(12)). Avec des nonces aléatoires de 96 bits sous une seule clé, les collisions par borne d'anniversaire apparaissent autour de 2^48 chiffrements. NIST suggère une marge de sécurité, donc limitez les utilisations à 2^32 par clé.

Compteur : incrémentez un entier de 96 bits. Garantit l'unicité jusqu'à 2^96 messages. Nécessite un état monotonique fiable, difficile dans les systèmes distribués.

Pour le transfert de fichiers avec une clé fraîche par fichier, les nonces aléatoires sont parfaitement sûrs — vous n'atteindrez jamais 2^32 chiffrements avec une clé. Pour le chiffrement par morceaux sous une seule clé de fichier, utilisez un compteur où le nonce encode l'index du morceau :

const nonce = new Uint8Array(12);
new DataView(nonce.buffer).setUint32(0, messageId);
new DataView(nonce.buffer).setBigUint64(4, BigInt(chunkIndex));

Le cas désastreux : plusieurs processus chiffrant sous la même clé partagée avec des nonces aléatoires, atteignant des millions de chiffrements par seconde. Les collisions d'anniversaire deviennent probables. Si vous devez partager des clés entre processus, utilisez un compteur coordonné avec un préfixe d'ID de processus.

N'utilisez pas des nonces de 64 bits

AES-GCM supporte des longueurs de nonce variables, mais seuls les nonces de 96 bits utilisent la construction optimisée spécifiée dans NIST 800-38D. D'autres longueurs (typiquement 64 ou 128 bits) déclenchent une étape de pré-traitement GHASH qui réduit les performances et augmente la complexité. L'API Web Crypto accepte des IV non-96 bits mais la spec recommande 96. Utilisez simplement 96.

Longueur du tag : ne la réduisez pas

Le tag de GCM peut aller jusqu'à 128 bits. Certaines specs permettent une troncation à 96, 64, voire 32 bits. Évitez ça. Les tags tronqués facilitent les attaques par falsification, et les économies (4 à 12 octets par message) sont négligeables pour le transfert de fichiers. AES-GCM de Web Crypto utilise par défaut des tags de 128 bits via le paramètre tagLength (valeur par défaut 128). Laissez-le tranquille.

Données associées (AAD)

L'AAD est un ensemble de données authentifiées mais non chiffrées. Utilisez-le pour les métadonnées que vous souhaitez lier au texte chiffré : nom de fichier, type de contenu, horodatage d'expiration, ID de l'émetteur.

await crypto.subtle.encrypt(
  {
    name: "AES-GCM",
    iv: nonce,
    additionalData: new TextEncoder().encode(JSON.stringify({
      filename: "rapport.pdf",
      contentType: "application/pdf",
      expires: 1712345678,
    })),
  },
  key,
  plaintext
);

Si un attaquant modifie l'AAD, le déchiffrement échoue. Cela empêche les attaques par substitution où quelqu'un remplace le nom de fichier sur un texte chiffré stocké sans être détecté. Le destinataire doit connaître l'AAD exact pour déchiffrer, donc stockez-le avec le texte chiffré.

Génération et dérivation de clés

Pour les clés par fichier :

const key = await crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 },
  true,
  ["encrypt", "decrypt"]
);

256 bits est la valeur par défaut en 2026. AES à 128 bits est encore sécurisé mais a moins de marge post-quantique (l'algorithme de Grover divise par deux la force effective).

Pour les clés dérivées d'un mot de passe :

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"]
);

Stockez le sel avec le texte chiffré. Il n'est pas secret ; il doit juste être unique par mot de passe.

Le chemin de code critique

Une fonction de chiffrement minimale :

async function encrypt(key, plaintext, aad = new Uint8Array()) {
  const nonce = crypto.getRandomValues(new Uint8Array(12));
  const ciphertext = new Uint8Array(
    await crypto.subtle.encrypt(
      { name: "AES-GCM", iv: nonce, additionalData: aad },
      key,
      plaintext
    )
  );
  return { nonce, ciphertext, aad };
}

Déchiffrement avec gestion correcte des erreurs :

async function decrypt(key, { nonce, ciphertext, aad }) {
  try {
    return await crypto.subtle.decrypt(
      { name: "AES-GCM", iv: nonce, additionalData: aad },
      key,
      ciphertext
    );
  } catch (e) {
    // Échec d'authentification
    throw new Error("Déchiffrement échoué : texte chiffré altéré ou mauvaise clé");
  }
}

L'appel decrypt lève OperationError sur une incompatibilité de tag, un texte chiffré trop court, ou une mauvaise clé. Traitez toute exception comme un échec d'intégrité ; ne tentez pas de distinguer.

Gros fichiers par morceaux

Pour les fichiers de plus de quelques centaines de mégaoctets, découpez-les pour éviter la pression mémoire :

async function encryptChunks(key, file, chunkSize = 1024 * 1024) {
  const chunks = [];
  let chunkIndex = 0;
  for (let offset = 0; offset < file.size; offset += chunkSize) {
    const chunk = await file.slice(offset, offset + chunkSize).arrayBuffer();
    const nonce = new Uint8Array(12);
    new DataView(nonce.buffer).setBigUint64(4, BigInt(chunkIndex++));
    const ct = await crypto.subtle.encrypt(
      { name: "AES-GCM", iv: nonce }, key, chunk
    );
    chunks.push(new Uint8Array(ct));
  }
  return chunks;
}

Mise en garde : AES-GCM par morceaux ne détecte pas la troncation. Un attaquant pourrait supprimer des morceaux de fin et chaque morceau restant se déchiffre correctement. En défense, incluez le nombre total de morceaux dans l'AAD de chaque morceau, ou utilisez crypto_secretstream de libsodium qui gère cela.

Déchiffrement côté serveur (Node.js)

Le module crypto de Node peut déchiffrer des données chiffrées dans le navigateur :

const { createDecipheriv } = require('crypto');

function decrypt(key, nonce, ciphertextWithTag) {
  const tag = ciphertextWithTag.slice(-16);
  const ct = ciphertextWithTag.slice(0, -16);
  const decipher = createDecipheriv('aes-256-gcm', key, nonce);
  decipher.setAuthTag(tag);
  return Buffer.concat([decipher.update(ct), decipher.final()]);
}

Web Crypto ajoute le tag de 128 bits au texte chiffré ; l'API de Node s'attend à les recevoir séparément. Séparez-les en conséquence.

Chiffres de performance

Sur du matériel typique 2024-2026 avec AES-NI :

  • Natif (OpenSSL, AES-NI) : 3-5 Go/s par cœur
  • Web Crypto (navigateur avec accélération matérielle) : 1-2 Go/s
  • libsodium.js WASM AES-GCM : 400-800 Mo/s
  • JS pur (@noble/ciphers) : 50-150 Mo/s

Pour un fichier de 1 Go, le chiffrement Web Crypto prend 0,5 à 1 seconde. Le JavaScript pur prend 7 à 20 secondes. Choisissez les implémentations en tenant compte de cette réalité ; pour l'UX de transfert de gros fichiers, Web Crypto est le choix pratique.

Récapitulatif des pièges courants

  • Réutilisation de nonce : catastrophique. Le premier mode d'échec.
  • Utiliser Math.random() au lieu de crypto.getRandomValues().
  • Oublier d'authentifier les métadonnées associées avec AAD.
  • Utiliser le mode CBC « parce que c'est ce qu'on connaît ». CBC nécessite un MAC séparé pour égaler l'intégrité de GCM ; une construction HMAC-CBC correcte est complexe à bien mettre en œuvre, GCM évite le piège.
  • Attraper silencieusement les erreurs de déchiffrement et retourner du gibberish. Toujours échouer bruyamment.
  • Implémenter son propre GCM. Utilisez Web Crypto, libsodium ou node:crypto. L'implémentation GHASH comporte des pièges par canal auxiliaire qu'il a fallu des années aux experts pour bien gérer.

HexaTransfer utilise AES-256-GCM de Web Crypto avec des nonces aléatoires de 96 bits, des tags de 128 bits, et pas d'AAD car la clé est par fichier et le nom de fichier est stocké séparément dans des métadonnées protégées par AEAD. Simple, correct, rapide.

Quand choisir autre chose

AES-GCM est optimal pour le transfert de fichiers, mais envisagez des alternatives dans des cas spécifiques :

  • XChaCha20-Poly1305 : les nonces de 192 bits rendent la sécurité par nonce aléatoire triviale à n'importe quelle échelle. Légèrement plus lent sur du matériel avec AES-NI, plus rapide sur les anciens ARM sans AES-NI. libsodium le fournit.
  • AES-GCM-SIV : résistant à l'utilisation incorrecte ; la réutilisation de nonce ne fuit pas la clé, elle révèle seulement si les textes en clair étaient égaux. Utile quand vous ne pouvez pas garantir l'unicité des nonces.

Pour la plupart des charges de travail de transfert de fichiers dans une stack web standard, AES-256-GCM avec une clé fraîche par fichier et des nonces aléatoires de 96 bits est le bon choix et le plus simple à implémenter correctement.

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