HMAC et les pièges

Comparaison en temps constant et ordre chiffrement/MAC

Un MAC bien construit peut encore être mis en défaut par la façon dont on l'utilise. Deux pièges classiques : la comparaison des tags et l'ordre des opérations.

L'attaque temporelle sur la comparaison

Comparer deux tags semble trivial. Mais une comparaison octet par octet s'arrête au premier octet différent :

def comparer(a, b):          # NAÏF et DANGEREUX
    for i in range(len(a)):
        if a[i] != b[i]:
            return False     # sortie anticipée !
    return True

Ce retour anticipé fait varier le temps d'exécution selon le nombre d'octets corrects au début. Un attaquant qui mesure finement ce temps peut deviner le tag octet par octet :

tag essayé : A?......   -> rejet très rapide
tag essayé : 3?......   -> rejet un peu plus lent  = 1er octet bon !
  ... on fixe l'octet 1, puis on cherche le 2, etc.

C'est une attaque temporelle (timing attack). En quelques milliers de requêtes chronométrées, le tag entier peut être reconstruit.

La parade : comparaison en temps constant

Il faut comparer tous les octets, quel que soit le résultat, sans sortie anticipée :

def temps_constant(a, b):
    if len(a) != len(b): return False
    diff = 0
    for x, y in zip(a, b):
        diff |= x ^ y        # accumule sans jamais s'arrêter
    return diff == 0

Le temps ne dépend plus du contenu. Les bibliothèques fournissent cette primitive (hmac.compare_digest en Python, crypto.timingSafeEqual en Node).

L'ordre : chiffrer puis authentifier

Quand on veut à la fois confidentialité (chiffrement) et authenticité (MAC), l'ordre compte. Comparons deux approches :

MAC-then-Encrypt          Encrypt-then-MAC   (RECOMMANDÉ)
------------------        -------------------
tag = MAC(M)              c   = Enc(M)
c   = Enc(M || tag)       tag = MAC(c)
envoie c                  envoie c || tag

Avec Encrypt-then-MAC, le récepteur vérifie d'abord le tag sur le chiffré : si le tag est invalide, il rejette sans jamais déchiffrer. Cela protège le déchiffreur contre les messages forgés et les attaques par oracle de padding. C'est la construction prouvée la plus sûre.

Les schémas AEAD

En pratique moderne, on n'assemble plus soi-même chiffrement et MAC : on utilise un mode AEAD (Authenticated Encryption with Associated Data) qui combine les deux de façon sûre et éprouvée. Le plus courant est AES-GCM ; ChaCha20-Poly1305 en est un équivalent très répandu sur mobile. Ils fournissent confidentialité et authenticité en une seule primitive, difficile à mal utiliser.

En résumé

Comparer des tags naïvement (octet par octet avec arrêt anticipé) laisse fuir de l'information par le temps d'exécution : c'est une attaque temporelle, dont la parade est la comparaison en temps constant. Pour combiner chiffrement et MAC, Encrypt-then-MAC est la construction sûre : on vérifie le tag avant tout déchiffrement. En pratique, les modes AEAD comme AES-GCM intègrent chiffrement et authentification en une primitive robuste.