Exercices — Cryptographie et chiffrement
| Exercice | Méthode | Clé | Sécurité |
|---|---|---|---|
| 1 | Run-Length Encoding (RLE) | — | Compression (pas chiffrement) |
| 2 | Substitution par fréquence | occurrences dans le mot | Faible |
| 3 | Décalage fixe (ROT+1) | d = 1 | Très faible |
| 4 | Chiffre de César | décalage d | Faible (26 clés) |
| 5 | Substitution alphabétique | alphabet désordonné | Moyen (26! clés) |
| 6 | Chiffre de Vigenère | mot-clé variable | Bon (poly-alphabétique) |
Exercice 1 — Compression RLE (Run-Length Encoding)
Encoder une chaîne par comptage des répétitions successives
Principe : parcourir la chaîne de gauche à droite, compter les répétitions successives, puis écrire le couple
nbr + caractère dans le résultat.Écrire une fonction crypter(CH) qui encode la chaîne CH (≤ 50 caractères alphabétiques) selon le principe RLE.
str(cpt) + CH[i] au résultat et remettre le compteur à 1.def crypter(CH):
"""Encode CH en RLE (Run-Length Encoding)."""
res = ''
if len(CH) <= 50:
cpt = 1
for i in range(len(CH) - 1):
if CH[i] == CH[i + 1]:
cpt += 1
else:
res += str(cpt) + CH[i]
cpt = 1
res += str(cpt) + CH[-1] # traiter le dernier groupe
return res
print(crypter("aaaFyBssssssssssssazz")) # 3a1F1y1B12s1a2zimport re
def encoder_rle(CH):
"""
Encode CH en RLE. Version avec itertools.groupby.
groupby groupe les caractères identiques consécutifs.
"""
from itertools import groupby
return ''.join(str(sum(1 for _ in groupe)) + c
for c, groupe in groupby(CH))
def decoder_rle(code):
"""
Décode une chaîne RLE.
re.findall(r'(\d+)([A-Za-z])') extrait les couples (nombre, lettre).
"""
return ''.join(c * int(n) for n, c in re.findall(r'(\d+)([A-Za-z])', code))
# Tests
original = "aaaFyBssssssssssssazz"
code = encoder_rle(original)
retour = decoder_rle(code)
print(f"Original : {original}")
print(f"Encodé : {code}")
print(f"Décodé : {retour}")
print(f"Sans perte : {original == retour}")Original : aaaFyBssssssssssssazz Encodé : 3a1F1y1B12s1a2z Décodé : aaaFyBssssssssssssazz Sans perte : True
groupby("aaaBBC") produit : ('a', iter), ('B', iter), ('C', iter). C'est l'outil Python idéal pour ce type de problème. La version originale avec boucle est tout aussi correcte et plus pédagogique.CH = 'aaaFyBssssssssssssazz'
Res = '3a1F1y1B12s1a2z'
Exercice 2 — Chiffrement par fréquence d'occurrence
Chiffrer un mot selon le nombre d'occurrences de chaque lettre
- Compter son nombre d'occurrences n dans M.
- Calculer le décalage K :
- n impair →
K = 2 × n - n pair →
K = n ÷ 2
- n impair →
- Remplacer la lettre par celle qui se trouve K positions après dans l'alphabet (modulo 26 pour le retour au début).
Exemple — HAPPY
| Lettre | n (occurrences) | n pair/impair | K | Position orig. | Nouvelle pos. | Résultat |
|---|---|---|---|---|---|---|
| H | 1 | impair | 2×1 = 2 | 7 | 9 | J |
| A | 1 | impair | 2×1 = 2 | 0 | 2 | C |
| P | 2 | pair | 2÷2 = 1 | 15 | 16 | Q |
| P | 2 | pair | 2÷2 = 1 | 15 | 16 | Q |
| Y | 1 | impair | 2×1 = 2 | 24 | 26 % 26 = 0 | A |
Résultat : JCQQA
def crypter_mc(s):
alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
res = ''
for lettre in s:
n = s.count(lettre)
K = n * 2 if n % 2 != 0 else n // 2
ordre = alpha.find(lettre)
res += alpha[(ordre + K) % 26] # % 26 gère le retour circulaire
return res
print(crypter_mc("HAPPY")) # JCQQAdef crypter_mc(s):
"""
Chiffre le mot s par substitution basée sur la fréquence de chaque lettre.
Version avec chr/ord pour éviter la dépendance à la chaîne alpha.
"""
res = ''
for lettre in s:
n = s.count(lettre)
K = 2 * n if n % 2 == 1 else n // 2
pos = ord(lettre) - ord('A') # position 0–25
res += chr(ord('A') + (pos + K) % 26) # nouvelle lettre
return res
# Vérification lettre par lettre
def expliquer(s):
print(f"Mot : {s}")
print(f"{'Lettre':<8} {'n':>3} {'K':>4} {'→':>3}")
for c in s:
n = s.count(c)
K = 2*n if n % 2 == 1 else n // 2
pos = ord(c) - ord('A')
nc = chr(ord('A') + (pos + K) % 26)
print(f" {c:<6} {n:>3} {K:>4} {nc}")
print(f"Chiffré : {crypter_mc(s)}\n")
expliquer("HAPPY")
expliquer("BANANA")Mot : HAPPY H 1 2 J A 1 2 C P 2 1 Q P 2 1 Q Y 1 2 A Chiffré : JCQQA
ord('A') = 65 et chr(65) = 'A'. La formule chr(ord('A') + (pos + K) % 26) calcule la lettre cible sans avoir besoin de stocker la chaîne 'ABCDEFGHIJ…'.M = 'HAPPY'
MC = 'JCQQA'
Exercice 3 — Décalage de 1 (ROT+1)
Chiffrer un message en décalant chaque lettre d'une position
Remplacer chaque lettre majuscule par la suivante dans l'alphabet, avec retour circulaire : Z → A.
def crypter(ch):
alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
res = ''
for c in ch:
indice = alpha.find(c)
pos = (indice + 1) % 26 # % 26 remplace le if pos == 26
res += alpha[pos]
return res
print(crypter("ABCCZABEY")) # BCDDABCFZdef crypter(ch):
"""ROT+1 : cas particulier du chiffre de César avec d=1."""
return ''.join(chr(ord('A') + (ord(c) - ord('A') + 1) % 26) for c in ch)
# Généralisation : décoder = ROT+25 (ou ROT-1)
def decrypter(ch):
return ''.join(chr(ord('A') + (ord(c) - ord('A') - 1) % 26) for c in ch)
msg = "ABCCZABEY"
code = crypter(msg)
print(f"Chiffré : {code}")
print(f"Décodé : {decrypter(code)}")Chiffré : BCDDABCFZ Décodé : ABCCZABEY
CH = 'ABCCZABEY'
Res = 'BCDDABCFZ'
Exercice 4 — Chiffre de César
Chiffrer un message avec un décalage variable d
Formule :
lettre_chiffrée = chr(ord('A') + (pos + d) % 26)def cesar(s, d):
alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
res = ''
for lettre in s:
ordre = alpha.find(lettre)
indice = (ordre + d) % 26 # % 26 est plus élégant que le if
res += alpha[indice]
return res
print(cesar("ABCDEFGH", 3)) # DEFGHIJKdef cesar(s, d):
"""Chiffre de César — chiffrement si d > 0, déchiffrement si d < 0."""
return ''.join(chr(ord('A') + (ord(c) - ord('A') + d) % 26)
for c in s.upper() if c.isalpha())
def dechiffrer_cesar(s, d):
"""Déchiffrement = chiffrement avec le décalage opposé."""
return cesar(s, -d)
def bruteforce_cesar(code):
"""Essayer les 26 décalages possibles (attaque par force brute)."""
print("Attaque par force brute :")
for d in range(26):
print(f" d={d:2} → {cesar(code, -d)}")
# Tests
msg = "ABCDEFGH"
code = cesar(msg, 3)
print(f"Chiffré : {code}")
print(f"Déchiffré: {dechiffrer_cesar(code, 3)}")
print()
bruteforce_cesar("KHOOR") # 'KHOOR' est 'HELLO' chiffré avec d=3Chiffré : DEFGHIJK Déchiffré: ABCDEFGH Attaque par force brute : d= 0 → KHOOR d= 3 → HELLO ← message d'origine retrouvé ...
s = "ABCDEFGH", d = 3
DEFGHIJK
Exercice 5 — Substitution par alphabet-clé
Chiffrer par substitution avec un alphabet désordonné
Exemple avec la clé
'HYLUJPVREAKBNDOFSQZCWMGITX' :A → H, B → Y, C → L, D → U, E → J, F → P …
Il y a 26! ≈ 4 × 10²⁶ clés possibles, rendant la force brute impossible.
def crypter_alea(msg, cle='HYLUJPVREAKBNDOFSQZCWMGITX'):
alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
res = ''
for c in msg:
indice = alpha.find(c)
res += cle[indice]
return res
print(crypter_alea("ABCDEFZ")) # HYLUJPXimport random
import string
def crypter_alea(msg, cle='HYLUJPVREAKBNDOFSQZCWMGITX'):
"""Chiffrement par substitution alphabétique."""
table = str.maketrans('ABCDEFGHIJKLMNOPQRSTUVWXYZ', cle)
return msg.upper().translate(table)
def decrypter_alea(code, cle='HYLUJPVREAKBNDOFSQZCWMGITX'):
"""Déchiffrement : inverse la table de substitution."""
table = str.maketrans(cle, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
return code.translate(table)
def generer_cle():
"""Génère une clé aléatoire valide (permutation de A–Z)."""
lettres = list(string.ascii_uppercase)
random.shuffle(lettres)
return ''.join(lettres)
# Tests
cle = 'HYLUJPVREAKBNDOFSQZCWMGITX'
msg = "ABCDEFZ"
code = crypter_alea(msg, cle)
back = decrypter_alea(code, cle)
print(f"Original : {msg}")
print(f"Chiffré : {code}")
print(f"Déchiffré: {back}")Original : ABCDEFZ Chiffré : HYLUJPX Déchiffré: ABCDEFZ
str.maketrans(source, cible) crée une table de correspondance caractère→caractère. str.translate(table) applique cette table en une seule instruction, bien plus efficace qu'une boucle find.CH = 'ABCDEFZ', clé = 'HYLUJPVREAKBNDOFSQZCWMGITX'
Res = 'HYLUJPX'
Exercice 6 — Chiffre de Vigenère
Chiffrement poly-alphabétique avec un mot-clé
Formule :
chiffré[i] = (message[i] + clé[i % len(clé)]) % 26Avantage principal : la même lettre du message peut produire des lettres chiffrées différentes (poly-alphabétique) → résistant à l'analyse de fréquences.
Début du chiffrement — clé "VIGENERE", message "ILFAUT..."
| Position | Lettre msg | Lettre clé | Décalage clé | Résultat |
|---|---|---|---|---|
| 0 | I (8) | V (21) | +21 | (8+21)%26 = 3 → D |
| 1 | L (11) | I (8) | +8 | (11+8)%26 = 19 → T |
| 2 | F (5) | G (6) | +6 | (5+6)%26 = 11 → L |
alpha.find(caractere) sur chaque caractère du message, y compris les espaces. Pour un espace, find() retourne -1 (non trouvé), ce qui provoque un comportement incorrect (alpha[-1] = 'Z'). La version améliorée préserve les espaces sans les chiffrer.def Vigenere(msg, cle):
alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
res = ''
i = 0
for c in msg:
debut = alpha.find(cle[i % len(cle)])
pos = alpha.find(c) # ⚠ -1 si espace ou caractère non alpha
indice = (pos + debut) % 26
res += alpha[indice]
i += 1
return res
print(Vigenere("ILFAUTCODER", "VIGENERE"))def vigenere_chiffrer(msg, cle):
"""
Chiffrement de Vigenère.
- Ignore les espaces (les conserve dans le résultat).
- La clé ne progresse que sur les lettres de l'alphabet.
"""
alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
res = []
k = 0 # index dans la clé (avance seulement sur les lettres)
for c in msg.upper():
if c in alpha:
d = alpha.index(cle.upper()[k % len(cle)])
pos = alpha.index(c)
res.append(alpha[(pos + d) % 26])
k += 1
else:
res.append(c) # conserver espaces et ponctuation
return ''.join(res)
def vigenere_dechiffrer(code, cle):
"""Déchiffrement : décalage négatif."""
alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
res = []
k = 0
for c in code.upper():
if c in alpha:
d = alpha.index(cle.upper()[k % len(cle)])
pos = alpha.index(c)
res.append(alpha[(pos - d) % 26])
k += 1
else:
res.append(c)
return ''.join(res)
# Tests
cle = "VIGENERE"
msg = "IL FAUT CODER CETTE PHRASE"
code = vigenere_chiffrer(msg, cle)
back = vigenere_dechiffrer(code, cle)
print(f"Original : {msg}")
print(f"Chiffré : {code}")
print(f"Déchiffré: {back}")Original : IL FAUT CODER CETTE PHRASE Chiffré : DT LNLB VBWIV XIBLX DLNLBI Déchiffré: IL FAUT CODER CETTE PHRASE
len(clé) décalages rotatifs : une même lettre peut produire des chiffrés différents selon sa position → résistant à l'analyse de fréquences simple. Il a été considéré indéchiffrable pendant 300 ans.message = "ILFAUTCODER" clé = "VIGENERE"
DTLNLBVBWIV
Récapitulatif — Techniques de chiffrement
| Méthode | Principe Python | Clés possibles | Bug corrigé |
|---|---|---|---|
| RLE | Boucle + compteur / itertools.groupby | — | — |
| Fréquence | s.count(c) + modulo 26 | Variable | — |
| ROT+1 | (pos + 1) % 26 | 1 | — |
| César | (pos + d) % 26 | 25 | — |
| Substitution | str.maketrans / cle[alpha.find(c)] | 26! ≈ 4×10²⁶ | — |
| Vigenère | César avec décalage cyclique selon la clé | 26^len(clé) | Espace → alpha.find retourne -1 |
- Le modulo 26 (
% 26) remplace avantageusement les testsif indice > 25pour tout chiffrement circulaire. chr/ordévite de stocker la chaîne'ABCDE…Z'et rend le code plus lisible.- Toujours vérifier la gestion des caractères non alphabétiques (espaces, ponctuation) avant d'indexer dans un alphabet.
str.maketrans+str.translateest la méthode Pythonique pour les substitutions lettre-à-lettre.- Le déchiffrement de Vigenère est simplement le chiffrement avec le décalage opposé :
(pos - d) % 26.
Discussion (0)
Soyez le premier à laisser un commentaire !
Laisser un commentaire
Votre commentaire sera visible après modération.