Dans le capstone TLS, une boîte « AES-128-GCM » s’occupait du chiffrement des données — le contenu réel de la page HTTPS. C’est l’ouvrier le plus sollicité de toute la cryptographie moderne : vos messages, vos fichiers, chaque octet d’une connexion sécurisée passent par lui. D’habitude, c’est une instruction du processeur (AES-NI) ou une fonction de bibliothèque qu’on n’ouvre jamais.

Cet article ouvre les deep-dives de l’arc crypto : on prend une des briques de TLS et on regarde dedans. Ici, AES — émis, comme le reste, en code machine verbose. On s’appuie sur le SHA-256 (chapitre 1) pour les opérations bit à bit.


AES, c’est mélanger une grille 4×4, dix fois

AES travaille sur des blocs de 16 octets, rangés en grille 4×4. Chiffrer un bloc, c’est appliquer quatre transformations, répétées dix fois (pour AES-128) :

  L'état : 16 octets en grille 4×4

    ┌────┬────┬────┬────┐
    │ b0 │ b4 │ b8 │b12 │
    │ b1 │ b5 │ b9 │b13 │
    │ b2 │ b6 │b10 │b14 │
    │ b3 │ b7 │b11 │b15 │
    └────┴────┴────┴────┘

  Un tour = 4 transformations, répétées 10 fois :

    SubBytes    → chaque octet remplacé via la S-box
    ShiftRows   → les lignes décalées d'un cran croissant
    MixColumns  → les colonnes mélangées (arithmétique GF(2⁸))
    AddRoundKey → XOR avec la clé du tour

Chacune est une transformation réversible et bien définie. Regardons les deux qui surprennent quand on les voit pour la première fois.


La S-box : une table, assumée comme telle

SubBytes remplace chaque octet par un autre, via la S-box — une substitution non-linéaire qui donne à AES sa résistance. Mathématiquement, c’est l’inverse dans GF(2⁸) suivi d’une transformation affine. En pratique, tout le monde l’implémente comme une table de 256 entrées. verbose ne fait pas exception :

rule aes_sbox
  @intention: "AES forward S-box, FIPS-197 standard 256-byte lookup"
  logic:
    out = if inp.b == 0 then 99
      else if inp.b == 1 then 124
      else if inp.b == 2 then 119
      ...

Soyons honnêtes : ce n’est pas « calculé », c’est une table — 256 branches, une par octet d’entrée, exactement les valeurs de la FIPS-197. C’est le choix standard, et c’est aussi le plus vérifiable : la table est là, lisible, confrontable ligne à ligne à la spec. Pas de magie cachée derrière un appel de bibliothèque.


MixColumns : de l’arithmétique dans un corps fini

MixColumns est la transformation qui diffuse — qui fait qu’un octet modifié à l’entrée se répercute sur toute une colonne. Elle multiplie chaque colonne par une matrice fixe, mais dans un corps fini un peu particulier, GF(2⁸), où « multiplier par 2 » a une définition à soi. Cette brique s’appelle xtime :

rule xtime
  logic:
    out = if band(inp.b, 128) > 0 then band(bxor(shl(inp.b, 1), 27), 255)
          else band(shl(inp.b, 1), 255)

Lisez-la : on décale l’octet d’un bit vers la gauche (shl — « multiplier par 2 »), et si le bit de poids fort débordait (band(inp.b, 128) > 0), on applique un XOR avec 27 — soit 0x1b, le polynôme de réduction d’AES. C’est toute l’arithmétique de GF(2⁸) en une ligne. MixColumns n’est ensuite qu’une combinaison de xtime et de XOR. Comme pour SHA-256, ces opérations deviennent quelques instructions x86-64 — pas d’appel, pas de runtime.


Dix tours, déroulés — et pourquoi c’est un choix, pas une faiblesse

SHA-256 exprimait ses 64 tours en récursion (chapitre 1). AES, ici, déroule ses 10 tours : ils sont écrits l’un après l’autre, en clair. Le binaire n’est donc pas aussi compact qu’il pourrait l’être — mais on peut lire les 10 tours un par un, sans rien deviner.

C’est le point qui mérite d’être nommé : verbose sait récurser ET dérouler, et choisit la forme qui sert la lisibilité de chaque algorithme. Pour une boucle de 255 marches (le ladder X25519), la récursion + une preuve de terminaison est la bonne forme. Pour 10 tours fixes et lisibles, le déroulé l’est. La compacité n’est pas l’objectif ; la lisibilité auditée l’est.


Du bloc au canal : ECB → CTR → GCM

Chiffrer un bloc de 16 octets, c’est la base. Une vraie connexion chiffre un flux de taille quelconque, et veut en plus garantir que personne n’a altéré les données. D’où trois couches :

  AES (un bloc de 16 octets)
       │  on l'enchaîne pour traiter un flux...

  AES-CTR (chiffre n'importe quelle longueur)
       │  on ajoute l'authentification...

  AES-128-GCM  (chiffre ET prouve que rien n'a été altéré)

       └─► c'est ce que TLS utilise pour protéger la page

Le « A » de GCM (l’authentification) repose sur GHASH, une multiplication dans un autre corps fini, GF(2¹²⁸). Même principe que xtime, en plus grand : décalages, XOR, réduction par un polynôme. Là encore, tout est émis en natif.


Comment on sait que c’est juste

Le test ultime : le mode complet AES-128-GCM, confronté octet pour octet au vecteur NIST GCM Test Case 2. Avec une clé et un bloc de zéros en entrée, le chiffré publié par le NIST commence par :

0388dace60b6a392f328c2b971b2fe78

Le binaire verbose produit exactement ça — plus le tag d’authentification attendu. Pas « ça ressemble » : la valeur publiée, à l’octet près.


Pourquoi ça compte

AES est partout, et personne ne le lit jamais — c’est une instruction CPU ou une boîte noire de bibliothèque. Ici, chaque transformation est posée sur la table : la S-box comme table assumée, xtime comme une ligne d’arithmétique GF(2⁸), les 10 tours déroulés et lisibles, GHASH pour l’authentification. Et le tout est confronté aux vecteurs du NIST.

C’est la même thèse qu’au chapitre 1 et que dans le capstone : on ne fait pas confiance parce qu’un outil l’a écrit — on lit, et on confronte au réel. Un chiffrement assez petit et assez clair pour être audité ligne à ligne, dont la sortie matche la référence officielle. C’est ce que TLS utilisait pour servir la page au navigateur. Le prochain chapitre regardera l’autre moitié de l’identité : les signatures Ed25519.