04 / 04 Concevoir l'échec
  1. ← Chiffrement authentifié : pourquoi chiffrer ne suffit jamais
  2. 01 Chiffrer ne suffit pas
  3. 02 Le nonce, le détail qui fait tout s'effondrer
  4. 03 Lier le chiffré à son contexte
  5. 04 Concevoir l'échec
Chiffrement authentifié : pourquoi chiffrer ne suffit jamais · 04 / 04

Concevoir l'échec

Authentifier avant de parser, erreur opaque, frontières de confiance : comment la manière dont un système échoue décide de sa sécurité.

Dans le module 1, tu as vu qu’un oracle de padding Oracle de padding Attaque exploitant tout signal (message d'erreur, temps de réponse, comportement observable) qui révèle si le rembourrage d'un bloc chiffré est valide. En itérant les modifications du chiffré et en observant les réponses, un attaquant peut déchiffrer le message sans connaître la clé. C'est l'une des raisons historiques majeures pour lesquelles le chiffrement seul sans authentification est dangereux. permettait à un adversaire de reconstruire un texte clair octet par octet, sans jamais connaître la clé, simplement en observant si le serveur acceptait ou rejetait un message. Ce que l’adversaire exploitait n’était pas l’algorithme. C’était le comportement du système à l’échec : deux réponses distinctes, selon la cause du rejet. Cette distinction observable était l’oracle.

L’AEAD élimine les oracles de padding en retirant à l’adversaire toute information sur le résultat du déchiffrement. Mais un système qui utilise correctement un algorithme AEAD peut quand même recréer un oracle, par sa façon d’échouer, de parser, ou de valider les octets entrants. Les erreurs de conception à ce niveau annulent les garanties cryptographiques.

Ce module examine les règles de conception qui ferment ces failles. C’est le module final du cours : les principes qui suivent s’appuient sur l’intégrité et la confidentialité du module 1, l’unicité du nonce du module 2, et la liaison au contexte du module 3.

À la fin de ce module, tu sauras expliquer pourquoi l’ordre d’opérations (vérifier avant de parser) est une propriété de sécurité, définir ce qu’est une erreur opaque et pourquoi elle est nécessaire, appliquer le principe de frontière de confiance à un flux de traitement, et identifier les cas où un système bien chiffré reste vulnérable par sa logique d’échec.

Authentifier avant de parser

La règle est absolue : vérifier le tag avant de toucher un seul octet du texte chiffré.

L’ordre paraît évident une fois énoncé, mais les violations sont courantes. Un système qui parse les octets chiffrés avant d’avoir vérifié leur authenticité expose sa logique de parsing à des données entièrement contrôlées par l’adversaire. Chaque branchement conditionnel dans ce parseur devient une fuite potentielle : le comportement du système (temps de réponse, message d’erreur, code de retour) peut varier selon la structure des octets, et cette variation est observable.

Prenons un exemple. Un service reçoit une requête API chiffrée dont le premier champ est une longueur de payload. Si le service lit ce champ et alloue un buffer de la taille annoncée avant de vérifier le tag, un adversaire peut envoyer une valeur de longueur arbitraire et observer si l’allocation réussit ou échoue. Le parseur a traité des données non authentifiées. L’adversaire a obtenu une information.

Le même raisonnement s’applique à toute logique conditionnelle exécutée avant la vérification : décodage d’un format, lecture d’un champ de version, validation d’un identifiant. Si ces opérations s’effectuent sur des octets non authentifiés, elles participent à l’oracle.

La vérification du tag est une précondition, pas une étape parmi d’autres dans le traitement.

Un schéma AEAD correct applique déjà ce principe dans son opération open : le tag est vérifié en premier, et si la vérification échoue, aucun octet de texte clair n’est retourné. La règle s’étend à la couche applicative : même après l’open réussi, le texte clair obtenu doit être traité comme une donnée de source externe jusqu’à ce que son contenu soit validé.

Parse, don’t validate

Une fois le tag vérifié, les octets du texte clair sont authentiques, mais ils peuvent encore être malformés. La règle “parse, don’t validate” dit : transformer les octets bruts en valeurs bien typées à la frontière d’entrée, une seule fois, de sorte que le reste du code ne puisse pas recevoir de données malformées.

La distinction est précise. Valider, c’est vérifier qu’une donnée satisfait une condition (par exemple : “ce champ est un entier positif”) et continuer à manipuler la donnée brute si la condition est vraie. Parser, c’est construire une représentation typée depuis les octets bruts, et retourner une erreur si la construction échoue.

L’avantage du parsing est structurel : une fois la frontière franchie, les types garantissent la cohérence des données. Un champ “longueur” qui n’existe qu’en tant qu’entier non signé borné ne peut pas être négatif ailleurs dans le code, par construction du type, pas par vérification répétée.

Ce principe s’applique à toute couche qui reçoit des octets de l’extérieur : déchiffrement de tokens, lecture de paquets réseau, décodage de messages. La frontière de parsing coïncide avec la frontière de confiance. Ce qui passe cette frontière est utilisable. Ce qui échoue à la passer est rejeté, sans traitement partiel.

L’erreur opaque

Un système qui échoue de plusieurs façons observables est un oracle. La conséquence de conception est directe : un seul mode d’échec, sans information exploitable.

Ce que cela signifie concrètement :

L’erreur opaque s’applique aussi aux journaux internes : un log détaillé accessible via une interface externe (un endpoint de métriques, une page de diagnostic) peut devenir une fuite si l’adversaire peut le consulter. Les journaux internes peuvent être précis ; ce qui est retourné au client ne doit pas l’être.

Les frontières de confiance

Une frontière de confiance est le point où des octets non contrôlés entrent dans le système. Tout ce qui vient de l’extérieur de cette frontière est hostile par défaut.

Dans un système qui utilise l’AEAD, la frontière de confiance est l’opération open : ce qui arrive avant l’open est non authentifié, ce qui sort de l’open réussi est authentifié. Mais cette frontière a des implications pratiques sur la conception du système.

Valider dans les deux sens. Le seal doit vérifier que les champs inclus dans l’AAD sont bien formés, pas seulement les passer tels quels. Si l’émetteur inclut dans l’AAD un identifiant de destinataire sans valider qu’il correspond à un destinataire existant, le chiffré peut être forgé avec un identifiant invalide et le destinataire ne détectera l’erreur qu’après le déchiffrement. La validation des champs de contexte au moment du seal réduit la surface exploitable dès l’émission.

Traiter tout ce qui vient de l’extérieur comme hostile. Cela inclut les octets chiffrés (évident), mais aussi les métadonnées qui accompagnent le chiffré : un en-tête HTTP, un identifiant de session transmis séparément, un paramètre d’URL. Ces valeurs externes doivent être validées indépendamment, avant d’être utilisées pour sélectionner la clé, l’algorithme ou le destinataire.

La couche cryptographique n’est pas la seule ligne de défense. L’AEAD garantit qu’un message authentifié n’a pas été altéré. Elle ne garantit pas que le message a été construit correctement par un émetteur légitime. Un adversaire interne (un composant compromis, un émetteur qui forge un contexte valide mais malveillant) peut produire un chiffré authentique avec un contenu malveillant. Les validations applicatives après l’open restent nécessaires.

Ordre correct contre ordre incorrect

À gauche : la vérification du tag précède tout parsing. L'erreur est opaque. À droite : le parseur traite des octets non authentifiés avant la vérification, créant un oracle observable.

Le diagramme illustre la différence structurelle. Dans l’ordre correct, l’adversaire ne peut pas influencer le comportement du parseur, car le parseur ne reçoit que des données dont l’authenticité a déjà été établie. Dans l’ordre incorrect, chaque branchement du parseur est potentiellement observable par l’adversaire, qui contrôle entièrement les octets présentés.

Le pattern à retenir

Ne touche aucun octet que tu n’as pas authentifié. Un seul mode d’échec, sans détail exploitable.

Ces deux règles se renforcent mutuellement. La première ferme l’oracle que le parseur pourrait créer. La seconde ferme l’oracle que le mécanisme d’erreur pourrait créer. Ensemble, elles font en sorte que le comportement observable du système à l’échec soit constant : un rejet, une seule façon, sans information sur la cause.

Quiz

Quiz
  1. 1. Un service reçoit un message chiffré avec AES-GCM. Avant de vérifier le tag, il lit le premier champ du chiffré pour allouer un buffer. Pourquoi est-ce une faille ?

  2. 2. Dans le module 2, tu as vu que la comparaison de tags doit s'effectuer en temps constant. Quelle propriété générale ce principe illustre-t-il pour la conception des échecs ?

  3. 3. Un système scelle un token avec AES-GCM en incluant dans l'AAD l'identifiant de l'utilisateur et la direction du message (module 3). Lors du open, il retourne des messages d'erreur distincts selon que le tag est invalide ou que l'AAD ne correspond pas. Quel est le problème ?

  4. 4. En réunissant les quatre modules du cours : un système utilise ChaCha20-Poly1305, génère des nonces aléatoires de 192 bits, inclut l'identifiant de domaine et le destinataire dans l'AAD, vérifie le tag avant de parser, et retourne une erreur unique en cas d'échec. Quelle propriété reste à vérifier pour une mise en oeuvre complète ?

Ce qu’il faut retenir