Aller au contenu

Tester, auditer, répondre et migrer

Un système cryptographique ne se vérifie pas une fois pour toutes. Les bibliothèques évoluent, les configurations dérivent, des attaques apparaissent, des secrets fuient, et des algorithmes vieillissent. Ce chapitre propose une méthode de contrôle continu en quatre temps : tester ce que le code fait vraiment, auditer chaque couche, répondre à un incident sans improviser, et migrer sans casser ce qui fonctionne. Aucun de ces temps ne remplace les autres, et chacun suppose de savoir d’abord ce qui existe.

→ Lire l’explication complète de la marmotte

Un test cryptographique commence par les vecteurs de test : des triplets entrée, clé, sortie attendue publiés par les auteurs d’un algorithme ou par un organisme de normalisation. Les RFC (Request for Comments) en contiennent souvent en annexe ; la RFC 8439 donne ainsi des vecteurs complets pour ChaCha20-Poly1305. Le NIST (National Institute of Standards and Technology) publie ceux de ses algorithmes dans le cadre de ses programmes de validation. Un programme qui ne retrouve pas ces sorties octet pour octet est faux, quelle que soit l’élégance du code. Ces tests vérifient la conformité ; ils ne disent rien de la sécurité de l’usage.

La plupart des failles cryptographiques ne se trouvent pas sur le chemin heureux, mais dans ce que le code accepte alors qu’il devrait refuser. Les tests négatifs fournissent des entrées invalides et vérifient le refus : tag modifié d’un bit, message tronqué, données associées changées, nonce de mauvaise longueur, signature valide pour un autre message, courbe ou paramètre non autorisé. Le projet Wycheproof, lancé par Google, rassemble des milliers de ces cas pour les primitives courantes, construits à partir d’attaques connues.

Un test négatif vérifie aussi quelle erreur est renvoyée. L’erreur attendue doit être unique et indistinguable : si le test constate que « tag invalide » et « remplissage invalide » produisent deux exceptions différentes visibles de l’appelant, il vient de trouver un oracle potentiel. Tester seulement le chemin heureux, c’est certifier qu’un coffre s’ouvre avec la bonne clé sans jamais essayer la mauvaise.

Le fuzzing génère en masse des entrées aléatoires ou mutées et surveille les plantages, les dépassements mémoire et les assertions. Il est redoutable sur les analyseurs de formats : ASN.1 (Abstract Syntax Notation One), certificats, en-têtes de messages chiffrés. Les outils dépendent du langage : libFuzzer et AFL++ pour C et C++, cargo fuzz pour Rust, le fuzzing intégré à go test depuis Go 1.18, Atheris pour Python, Jazzer pour Java. Le programme OSS-Fuzz exécute ces campagnes en continu pour de nombreux projets libres. Le fuzzing différentiel compare deux implémentations sur les mêmes entrées : toute divergence signale un bug dans l’une des deux.

Les tests d’interopérabilité vérifient qu’un message produit par une implémentation est lu par une autre. Ils détectent les écarts d’interprétation d’une norme, ordre des octets, encodage d’une clé publique, longueur de sel implicite, qui passent inaperçus quand un programme ne se parle qu’à lui-même.

Soit un format de message chiffré : version (1 octet) || identifiant de clé (4 octets) || nonce (12 octets) || chiffré || tag (16 octets), avec AES-256-GCM (Galois/Counter Mode) et l’en-tête passé en données associées. Une suite de tests minimale comprend :

  • l’aller-retour chiffrer puis déchiffrer sur des messages de 0, 1, 15, 16, 17 octets et 1 Mo ;
  • les vecteurs officiels de l’AEAD, appelés à travers le format ;
  • un bit inversé tour à tour dans chaque champ, version, identifiant, nonce, chiffré et tag, qui doit toujours produire la même erreur ;
  • un message tronqué à chaque longueur possible, et un message vide ;
  • une version inconnue et un identifiant de clé inconnu, refusés avant tout déchiffrement ;
  • l’unicité des nonces sur un million de chiffrements avec la même clé ;
  • un message produit par une autre implémentation du format, s’il en existe une.

Cette suite tient dans un fichier ; son absence se paie en semaines d’incident.

Un audit cryptographique examine six couches, et les défauts se logent rarement dans celle qu’on regarde. La conception : la menace est-elle définie, les primitives adaptées, les clés séparées par usage ? Le code : appels d’API (Application Programming Interface) corrects, comparaisons en temps constant, gestion des erreurs, aléa. Les dépendances : versions, maintenance, avis de sécurité, options de compilation. La configuration : algorithmes activés, tailles de clés, versions de protocole. Les opérations : génération, stockage, rotation et destruction des clés, sauvegardes, accès. La menace, enfin, qu’il faut réexaminer : un service conçu pour un réseau interne et exposé depuis sur Internet n’affronte plus le même adversaire.

Une fiche d’audit de service tient sur une page. Elle recense les secrets et clés utilisés, avec leur emplacement, leur propriétaire et leur date de rotation, les algorithmes et modes par usage, les bibliothèques et leurs versions, les points d’entrée qui déchiffrent ou vérifient des données externes, les erreurs renvoyées, les journaux produits, et les écarts constatés avec leur gravité. Elle s’appuie sur l’inventaire décrit dans Gouverner la cryptographie, sans lequel l’audit ne porte que sur ce que l’équipe a pensé à montrer.

Pour les protocoles d’authentification et d’échange de clés, l’analyse formelle va plus loin qu’un test : des outils comme ProVerif ou Tamarin modélisent le protocole et un attaquant qui contrôle le réseau, puis prouvent des propriétés comme le secret d’une clé ou l’authentification mutuelle, ou exhibent une trace d’attaque. TLS 1.3 a fait l’objet de telles analyses pendant sa conception, ce qui a corrigé des défauts avant publication. Le fuzzing trouve des bugs dans du code ; la preuve formelle établit une propriété d’un modèle. Ni l’un ni l’autre ne couvre l’écart entre le modèle et le code.

Un audit n’est pas une certification. L’audit est une revue, plus ou moins approfondie, par un tiers ou une équipe interne, qui produit des constats. La certification, FIPS 140-3 ou Critères communs par exemple, atteste qu’un produit précis, dans une configuration précise, satisfait un référentiel ; elle ne dit rien de l’usage qui en est fait ensuite (voir les certifications FIPS et Critères communs).

Un incident cryptographique suit une séquence. Déterminer la portée : quel secret, depuis quand, accessible à qui, utilisé par quels services. Identifier les secrets exposés en propageant : un secret qui protégeait d’autres secrets les expose aussi. Révoquer : déclarer invalides les clés et certificats touchés, pour que personne ne puisse plus les utiliser. Effectuer la rotation : émettre de nouvelles clés et les déployer. Rechiffrer les données protégées par l’ancienne clé, si l’attaquant a pu copier ces données ou peut encore y accéder. Communiquer et documenter : utilisateurs, partenaires, autorités le cas échéant.

Rotation et révocation ne se confondent pas. La rotation remplace une clé par une autre, de façon planifiée ou non ; la révocation retire la confiance accordée à une clé. Une rotation sans révocation laisse l’ancienne clé valable ; une révocation sans rotation coupe le service. Et tourner une clé de chiffrement sans rechiffrer ce qui doit l’être laisse les données existantes sous la clé compromise : on a changé la serrure de la porte neuve, pas celle du coffre.

Atelier : un secret d’intégration continue compromis

Section intitulée « Atelier : un secret d’intégration continue compromis »

Un jeton d’accès au registre de paquets, stocké comme secret de la plateforme d’intégration continue, apparaît dans le journal public d’une exécution, à la suite d’un set -x laissé dans un script. Portée : le journal est public depuis neuf jours, le jeton permet de publier des paquets. Actions : révoquer immédiatement le jeton auprès du registre, en générer un nouveau limité au seul paquet concerné et à durée courte, purger les journaux d’exécution, puis examiner l’historique du registre pour vérifier qu’aucune version n’a été publiée hors du pipeline. Si une publication suspecte existe, l’incident change de nature : il faut retirer la version, prévenir les utilisateurs et vérifier les signatures des artefacts. Enfin, chercher tous les autres services qui utilisaient ce jeton : l’inventaire dira s’il y en a, son absence garantit qu’on en oubliera un. Les mesures préventives sont décrites dans Sécuriser un pipeline d’intégration et de déploiement continus et Prévenir les secrets dans le code source.

Une migration cryptographique, nouvel algorithme, nouvelle clé, nouveau format, ne se fait pas en un soir. Le premier prérequis est le versionnement : chaque donnée chiffrée ou signée porte un identifiant de version et de clé, sans lequel il est impossible de savoir comment la relire. Viennent ensuite les phases. La double lecture : le système sait lire l’ancien et le nouveau format. La double écriture, quand elle est utile : il écrit dans les deux, pour que des consommateurs anciens continuent de fonctionner. La bascule de l’écriture vers le seul nouveau format. Le rechiffrement progressif du stock. Puis la suppression finale de l’ancien chemin.

Chaque phase garde un retour arrière (rollback) possible : tant que l’ancien format est lisible, revenir en arrière ne perd aucune donnée. L’observabilité pilote la progression : proportion de lectures par version, erreurs de déchiffrement par version, volume restant à migrer. Une migration n’est pas une réécriture : elle change un composant en gardant le système en fonctionnement, ce qui est précisément ce qui la rend difficile.

ÉtapeLectureÉcritureRetour arrière
1. Déployer la clé v2v1, v2v1Retirer v2
2. Écrire en v2v1, v2v2Réécrire en v1
3. Rechiffrer le stockv1, v2v2Possible, v1 encore lisible
4. Compteur v1 à zéro pendant 30 joursv1, v2v2Possible
5. Retirer v1 et la détruirev2v2Non

L’étape 5 est la plus souvent oubliée. Garder indéfiniment une option faible « pour compatibilité » revient à la garder pour l’attaquant, qui saura demander la version 1 si on la lui propose. La démarche générale est développée dans Choisir, combiner et migrer et Exploiter les clés et leur rotation.

Un audit produit des constats, une certification une attestation de conformité à un référentiel. La rotation remplace une clé, la révocation lui retire la confiance. Une migration conserve le système en le modifiant, une réécriture le remplace. Et le fuzzing trouve des contre-exemples sans jamais prouver leur absence, quand la preuve formelle démontre une propriété d’un modèle qui n’est pas tout à fait le programme.

Sans tests négatifs, une bibliothèque qui accepte un message falsifié passe toute la suite de tests, puisque celle-ci ne vérifie que le chemin heureux ; le défaut reste en production jusqu’à ce qu’un attaquant le trouve à la place de l’équipe. Sans inventaire des secrets et de leurs usages, la compromission d’un secret d’intégration continue ne se traite qu’à moitié : on remplace ce qu’on connaît, et les services oubliés continuent d’accepter l’ancien secret. Sans plan de réponse, la révocation est improvisée pendant l’incident, c’est-à-dire au moment précis où personne n’a le temps de réfléchir.

Contrôler un système cryptographique se fait en continu, selon quatre activités.

Tester d’abord. Les vecteurs de test officiels donnent des entrées et les résultats exacts attendus : si le programme ne les retrouve pas, il est faux. Les tests négatifs envoient des données falsifiées ou abîmées, qui doivent être refusées avec l’erreur prévue. Le fuzzing envoie en masse des données aléatoires ou déformées pour faire apparaître des plantages. Les tests d’interopérabilité vérifient que deux programmes différents se comprennent.

Auditer ensuite, à plusieurs niveaux : la conception, le code, les bibliothèques utilisées, la configuration, l’exploitation au quotidien et les menaces visées. L’analyse formelle démontre mathématiquement certaines propriétés d’un protocole, ce qu’un test ne fait jamais.

Répondre à un incident suit un ordre : mesurer la portée, identifier les secrets exposés, révoquer, c’est-à-dire déclarer invalides les clés touchées, les remplacer par rotation, rechiffrer les données protégées par l’ancienne clé, puis documenter et communiquer. Remplacer une clé sans rechiffrer laisse les anciennes données sous la clé compromise.

Migrer enfin sans casser : chaque donnée porte un numéro de version, le système lit l’ancien et le nouveau format, écrit parfois dans les deux, garde un retour arrière possible, surveille la transition, puis supprime l’ancien format. Sans inventaire préalable, on oublie des usages ; en gardant indéfiniment une option faible, on la laisse à la disposition des attaquants.

À lire aussi

Pages voisines

: les deux pages se citent l’une l’autre.