Cryptographie élémentaire en Python
Créez un compte gratuit pour suivre votre avancement et reprendre où vous avez laissé.
Créer un compteBienvenue en cryptographie !
La cryptographie, c'est l'art de cacher un message pour que seul son destinataire puisse le lire. Tu vas découvrir ici les chiffrements historiques (César, Vigenère) et apprendre à les casser — sans installer le moindre logiciel, tout s'exécute dans ton navigateur.
À la fin de cette fiche, tu sauras transformer une lettre en code numérique avec ord et chr, chiffrer et déchiffrer un message avec César, attaquer un chiffrement par force brute, manipuler une clé de Vigenère et démasquer un texte secret en analysant la fréquence des lettres.
Remarque
Comment lire cette fiche. Tu vas rencontrer deux types d'exercices à la suite des explications :
- Complète le programme — 1 ou 2 mots à compléter, juste après chaque nouveau concept.
- À toi de jouer — un petit programme à finir, en fin de section.
Chaque cellule s'exécute en cliquant sur Exécuter. Les manipulations ici reposent sur le modulo (%) et sur les chaînes de caractères : si ces notions sont floues, la fiche Tables et listes est un bon préalable.
Lettres et codes : ord et chr
Pour chiffrer un texte, on a besoin de transformer une lettre en nombre. Python fournit deux fonctions inverses : ord(c) renvoie le code numérique d'un caractère, et chr(n) renvoie le caractère associé à un code.
Les majuscules de A à Z occupent les codes 65 à 90, et les minuscules de a à z les codes 97 à 122. On peut donc convertir une lettre en un indice entre 0 et 25 :
Complète le programme
Affiche le code numérique de la lettre 'M' :
Voir la solution
print(ord('M'))Complète le programme
Retrouve la lettre dont le code est 81 :
Voir la solution
print(chr(81))À toi de jouer
Affiche tout l'alphabet majuscule sur une seule ligne, en utilisant une boucle for et la fonction chr. Aide : la fonction print accepte le paramètre end='' pour ne pas aller à la ligne après chaque affichage.
Voir la solution
for i in range(26):
print(chr(ord('A') + i), end='')
print()Remarque
Spécificité Python — pas de type char. Contrairement à C ou Java, Python ne distingue pas les caractères des chaînes : 'A' est une chaîne de longueur 1, pas un type différent. C'est pratique mais cela explique pourquoi on doit explicitement passer par ord dès qu'on veut faire de l'arithmétique sur une lettre.
Le chiffre de César
Jules César décalait chaque lettre de son alphabet de 3 rangs : A devient D, B devient E, … X devient A, Y devient B, Z devient C. C'est la plus ancienne méthode de chiffrement connue. La difficulté est le « retour à zéro » en fin d'alphabet : on utilise pour cela le modulo 26.
Pour chiffrer un mot complet, on applique cette transformation à chaque lettre. La construction ''.join(...) reconstruit une chaîne à partir d'une suite de caractères :
Complète le programme
Chiffre le mot 'PYTHON' avec un décalage de 5 :
Voir la solution
def cesar_lettre(c, d):
return chr(ord('A') + (ord(c) - ord('A') + d) % 26)
def cesar(m, d):
return ''.join(cesar_lettre(c, d) for c in m)
print(cesar('PYTHON', 5))Complète le programme
Pour déchiffrer un message César avec un décalage d, il suffit de chiffrer avec le décalage opposé. Complète :
Voir la solution
def cesar_lettre(c, d):
return chr(ord('A') + (ord(c) - ord('A') + d) % 26)
def cesar(m, d):
return ''.join(cesar_lettre(c, d) for c in m)
secret = 'EUDYR'
print(cesar(secret, -3))À toi de jouer
Écris une fonction decoder_cesar(message, decalage) qui déchiffre directement, et utilise-la pour révéler le message 'KHOOR PRQGH' (chiffré avec un décalage de 3). Aide : conserve les espaces tels quels.
Voir la solution
def decoder_cesar(message, decalage):
resultat = ''
for c in message:
if c == ' ':
resultat = resultat + ' '
else:
indice = (ord(c) - ord('A') - decalage) % 26
resultat = resultat + chr(ord('A') + indice)
return resultat
print(decoder_cesar('KHOOR PRQGH', 3))Remarque
Spécificité Python — le modulo des nombres négatifs. En C ou Java, -3 % 26 vaut -3 (le signe suit le dividende). En Python, -3 % 26 vaut 23 : le résultat a toujours le signe du diviseur. C'est exactement le comportement dont on a besoin en cryptographie modulaire — pas besoin d'ajouter 26 « à la main » avant de prendre le modulo.
Casser César par force brute
Le code César a un défaut majeur : il n'existe que 25 décalages utiles (le décalage 0 ne change rien). Si on intercepte un message chiffré, on peut tous les essayer et lire celui qui a du sens. C'est ce qu'on appelle une attaque par force brute.
L'humain repère immédiatement la ligne lisible. Mais sur des milliers de messages, il faudrait automatiser : on cherche celui qui contient le plus de lettres « plausibles ». Un critère simple : compter les voyelles (en français environ 40 % des lettres).
Complète le programme
Calcule la proportion de voyelles du mot 'CRYPTOGRAPHIE' :
Voir la solution
def prop(t):
v = 'AEIOU'
return sum(1 for c in t if c in v) / len(t)
print(prop('CRYPTOGRAPHIE'))Complète le programme
Combien y a-t-il de décalages distincts à tester pour casser un César ? (Souviens-toi que le décalage 0 ne change rien.) Complète la valeur de nb :
Voir la solution
nb = 25
print(nb)À toi de jouer
Écris une fonction casser_cesar(secret) qui retourne le décalage le plus probable : celui dont la version déchiffrée a la plus forte proportion de voyelles. Teste-la sur le secret ci-dessous (le bon décalage est 4).
Voir la solution
def cesar(m, d):
r = ''
for c in m:
if c == ' ':
r = r + ' '
else:
r = r + chr(ord('A') + (ord(c) - ord('A') + d) % 26)
return r
def prop_voyelles(t):
v = 'AEIOU'
n = sum(1 for c in t if c.upper() in v)
return n / max(1, len(t))
def casser_cesar(secret):
meilleur_d = 0
meilleur_score = 0
for d in range(26):
clair = cesar(secret, -d)
score = prop_voyelles(clair)
if score > meilleur_score:
meilleur_score = score
meilleur_d = d
return meilleur_d
secret = 'PIXLVI HI GIWEV'
d = casser_cesar(secret)
print('decalage trouve :', d)
print('message :', cesar(secret, -d))Remarque
Spécificité Python — l'opérateur in sur les chaînes. L'expression c in 'AEIOU' teste si le caractère c apparaît dans la chaîne. C'est un opérateur du langage, pas une méthode : il marche aussi sur les listes, les tuples, les dictionnaires (où il teste les clés). Cette polyvalence est typique de Python — la même idée s'écrit de la même façon, peu importe la structure.
Le chiffre de Vigenère
César est trivial à casser : un seul décalage pour toutes les lettres. Vigenère (XVIᵉ siècle) corrige ce défaut en utilisant une clé qu'on répète tout au long du message. Chaque lettre de la clé donne un décalage différent.
Si la clé est 'CLEF' (codes 2, 11, 4, 5) et le message 'BONJOURATOUS', on chiffre la 1ʳᵉ lettre avec un décalage de 2, la 2ᵉ avec 11, la 3ᵉ avec 4, la 4ᵉ avec 5, la 5ᵉ revient à 2, et ainsi de suite.
Pour déchiffrer, on applique la clé à l'envers : on soustrait le décalage au lieu de l'ajouter.
Complète le programme
La fonction enumerate parcourt à la fois l'indice et la valeur. Complète l'appel pour afficher chaque caractère de 'PYTHON' avec sa position :
Voir la solution
for i, c in enumerate('PYTHON'):
print(i, c)Complète le programme
Chiffre le message 'MATHEMATIQUES' avec la clé 'KEY' :
Voir la solution
def vigenere(m, cle):
r = ''
for i, c in enumerate(m):
d = ord(cle[i % len(cle)]) - ord('A')
r = r + chr(ord('A') + (ord(c) - ord('A') + d) % 26)
return r
print(vigenere('MATHEMATIQUES', 'KEY'))À toi de jouer
Écris la fonction dechiffrer_vigenere et déchiffre le message 'XAMS PI HMRYM' avec la clé 'WHO'. Aide : ignore les espaces (laisse-les tels quels) et n'avance dans la clé que sur les vraies lettres.
Voir la solution
def dechiffrer_vigenere(secret, cle):
resultat = ''
j = 0
for c in secret:
if c == ' ':
resultat = resultat + ' '
else:
d = ord(cle[j % len(cle)]) - ord('A')
resultat = resultat + chr(ord('A') + (ord(c) - ord('A') - d) % 26)
j = j + 1
return resultat
print(dechiffrer_vigenere('XAMS PI HMRYM', 'WHO'))Remarque
Spécificité Python — enumerate et l'itération « propre ». Là où on écrirait en C for (int i = 0; i < n; i++) puis message[i], Python encourage à itérer directement sur les éléments. Quand on a aussi besoin de l'indice, enumerate le donne sans avoir à toucher à un compteur manuel — moins de bugs hors-bornes, et le code se lit comme une phrase.
Attaque par fréquence des lettres
Vigenère a longtemps été appelé le chiffre indéchiffrable. En réalité, dès qu'on connaît (ou qu'on devine) la longueur de la clé, on peut le casser en utilisant un fait statistique : dans un texte français, la lettre E apparaît environ 17 % du temps, A environ 8 %, Z presque jamais.
L'idée : compter combien de fois chaque lettre apparaît dans le message chiffré, et trouver la lettre la plus fréquente. Si elle correspond à E, on en déduit le décalage.
La méthode dict.get(cle, defaut) est l'idiome standard pour incrémenter un compteur sans risquer de KeyError sur une clé absente. Pour trier le résultat par effectif décroissant :
Complète le programme
Compte les lettres du mot 'MISSISSIPPI' :
Voir la solution
def compter(t):
c = {}
for x in t:
c[x] = c.get(x, 0) + 1
return c
print(compter('MISSISSIPPI'))Complète le programme
Trouve la lettre la plus fréquente dans le texte chiffré ci-dessous :
Voir la solution
texte = 'WTKKTWITNAQTVI'
c = {}
for x in texte:
c[x] = c.get(x, 0) + 1
plus_freq = max(c, key=c.get)
print(plus_freq)À toi de jouer
Écris une fonction casser_cesar_par_frequence(secret) qui suppose que la lettre la plus fréquente du message chiffré correspond à la lettre E du clair. Renvoie le décalage déduit, puis applique-le au message chiffré 'PIXLVIHIGIWEVIWXTSYVHIQEMR' (une phrase française sans espaces).
Voir la solution
def casser_cesar_par_frequence(secret):
c = {}
for x in secret:
c[x] = c.get(x, 0) + 1
plus_freq = max(c, key=c.get)
decalage = (ord(plus_freq) - ord('E')) % 26
return decalage
def cesar(m, d):
return ''.join(chr(ord('A') + (ord(c) - ord('A') + d) % 26) for c in m)
secret = 'PIXLVIHIGIWEVIWXTSYVHIQEMR'
d = casser_cesar_par_frequence(secret)
print('decalage :', d)
print('clair :', cesar(secret, -d))Remarque
Spécificité Python — les dictionnaires comme structure universelle. Là où d'autres langages obligent à choisir entre tableaux indexés et tables de hachage, Python utilise partout son dict : compteurs, configurations, JSON, attributs d'objets, espaces de noms. La ligne compteur[c] = compteur.get(c, 0) + 1 est probablement l'idiome le plus écrit en Python — apprends-le par cœur.
Pour aller plus loin
Le module collections : Counter. L'idiome dict.get(c, 0) + 1 est si fréquent que la bibliothèque standard fournit une classe dédiée : Counter. Elle compte une chaîne ou une liste en une ligne, et expose une méthode most_common(n) qui retourne directement les n éléments les plus fréquents.
À ton tour : utilise Counter pour trouver les trois lettres les plus fréquentes du texte ci-dessous. Aide : most_common(3) renvoie une liste de couples (lettre, effectif).
Voir la solution
from collections import Counter
texte = 'CRYPTOGRAPHIEELEMENTAIRE'
top = Counter(texte).most_common(3)
print(top)Comparaison avec C / Java / JavaScript. Là où Python expose ord(c) qui renvoie un int de taille arbitraire, C te force à choisir entre char (8 bits, codé sur le domaine ASCII) et wchar_t selon le système. JavaScript utilise "A".charCodeAt(0) et travaille en UTF-16. La conséquence est que Python est le langage idéal pour prototyper de la cryptographie : tu n'as à te soucier ni des débordements d'entiers ni de l'encodage tant que tu restes sur l'alphabet latin.
Bytes vs str : la frontière critique en cryptographie réelle. Dans la vraie vie, on chiffre des octets, pas des lettres. Une chaîne Python (type str) est une suite de caractères Unicode ; une suite d'octets (type bytes) s'écrit avec un préfixe b : b'hello'. Pour passer de l'un à l'autre, on choisit explicitement un encodage.
Le « é » occupe 2 octets en UTF-8 — c'est la base de toute crypto moderne, qui voit un message comme une suite d'octets indifférenciée.
Pourquoi César et Vigenère sont morts. Une attaque par fréquence cassait Vigenère en quelques heures avec papier et crayon dès le XIXᵉ siècle (méthode de Kasiski + Friedman). Les chiffrements modernes (AES, ChaCha20) éliminent toute corrélation statistique entre clair et chiffré : peu importe combien de messages on intercepte, la distribution des octets chiffrés est statistiquement uniforme. La cryptographie à clé publique (RSA, courbes elliptiques) ajoute une autre dimension : on peut publier une clé de chiffrement sans que personne ne puisse en déduire la clé de déchiffrement — quelque chose d'impensable avec César.
Python et la cryptographie sérieuse. La bibliothèque standard inclut hashlib (SHA-256, SHA-3, BLAKE2) et secrets (générateur cryptographiquement sûr). Pour AES, RSA et le chiffrement authentifié, on utilise le paquet cryptography (PyPI). Règle d'or absolue : ne jamais réimplémenter un algorithme cryptographique réel soi-même — les fuites par canaux auxiliaires (timing, mémoire) sont quasi impossibles à éliminer sans expertise pointue.
Ce que tu as appris
ord(c)etchr(n)convertissent une lettre en son code ASCII et inversement.- Le chiffre de César décale toutes les lettres d'une même quantité ; le modulo 26 gère le retour à A après Z.
- Une attaque par force brute essaie les 25 décalages possibles ; on choisit celui dont le clair est le plus « plausible ».
- Le chiffre de Vigenère applique un décalage différent à chaque lettre, dicté par une clé répétée — il résiste à la force brute.
- Une attaque par fréquence exploite les statistiques du français (E ≈ 17 %) pour deviner les décalages : c'est ce qui a tué Vigenère au XIXᵉ siècle.
- L'idiome
compteur[c] = compteur.get(c, 0) + 1est le squelette universel d'un compteur. - En Python,
-3 % 26vaut 23, ce qui simplifie tout le code modulaire.
Et après…
Tu vas aborder l'algorithmique avancée — complexité, récursivité, dictionnaires, compréhensions, idiomes Pythoniques. Le grand saut vers le code professionnel.