Quand l'implémentation trahit le secret
Les attaques temporelles (timing)
Le plus simple des canaux auxiliaires à comprendre est le temps. Si la durée d'un calcul dépend de la valeur du secret, alors chronométrer ce calcul, c'est déjà espionner le secret.
L'idée : le temps parle
Un ordinateur ne met pas toujours le même temps à faire une opération. Une boucle qui s'arrête plus tôt, une branche if qui saute une étape, un accès mémoire déjà présent en cache : tout cela raccourcit ou allonge l'exécution. Si ces variations sont corrélées au secret, l'attaquant les exploite.
L'exemple classique : la comparaison naïve
Imaginons un système qui vérifie un mot de passe (ou une signature) en comparant octet par octet, et qui s'arrête au premier octet faux :
comparer(saisie, secret):
pour i de 0 à longueur - 1:
si saisie[i] != secret[i]:
retourner FAUX <- sortie anticipée !
retourner VRAI
Le défaut : plus la saisie a de premiers octets corrects, plus la fonction tourne longtemps avant de renvoyer FAUX. En mesurant ce temps, l'attaquant devine les octets un par un :
secret = "K7z..."
essai "A???" -> échec au 1er octet (rapide)
essai "K???" -> échec au 2e octet (un peu plus long) => 'K' est bon !
essai "K7??" -> échec au 3e octet (encore plus long) => '7' est bon !
On ne teste plus 256^n combinaisons, mais 256 × n : le secret tombe octet par octet. Une attaque exponentielle devient linéaire.
La même faille dans RSA
L'exponentiation modulaire m = c^d mod n du déchiffrement RSA parcourt les bits de l'exposant secret d. Une implémentation naïve fait une multiplication supplémentaire uniquement quand le bit vaut 1 :
bit = 1 -> élévation au carré + multiplication (lent)
bit = 0 -> élévation au carré seulement (rapide)
Le temps total révèle alors le nombre — voire la position — des bits à 1 de la clé privée. Des attaques réelles ont récupéré des clés TLS ainsi.
La parade : le temps constant
La contre-mesure porte un nom précis : le code à temps constant (constant-time). Sa règle est stricte :
Le temps d'exécution ne doit dépendre ni des branches, ni des accès mémoire qui dépendent du secret.
Concrètement, une comparaison à temps constant examine tous les octets, sans jamais s'arrêter en cours de route :
comparer_ct(saisie, secret):
diff = 0
pour i de 0 à longueur - 1:
diff = diff | (saisie[i] XOR secret[i]) # accumule, ne sort jamais
retourner (diff == 0)
Le résultat est identique, mais la durée est la même quel que soit le secret : le chronomètre n'apprend plus rien. Les bibliothèques sérieuses fournissent des fonctions dédiées (par exemple des comparaisons dites constant-time) précisément pour cela.
En résumé
Si le temps de calcul dépend du secret — comparaison à sortie anticipée, exponentiation branchante — le chronomètre révèle le secret bit à bit, transformant une recherche exponentielle en recherche linéaire. La parade est le code à temps constant : pas de branche ni d'accès mémoire dépendant du secret, une durée toujours identique.

