Vigenère Cipher Project
Robin Boucher - Cryptography Portfolio
1. Fonctions de Base du Chiffrement
Le chiffrement de Vigenère est une méthode classique de chiffrement par substitution polyalphabétique. Contrairement au chiffre de César, il utilise un mot-clé pour appliquer des décalages variables sur chaque lettre du message.
Démonstration:
def vigenere_chiffrer(texte_clair: str, cle: str, conserver_casse: bool = True) -> str:
texte_chiffre = []
## Répète la clé pour qu'elle ait la même longueur que le message
cle_repetee = (cle * (len(texte_clair) // len(cle) + 1)).lower()
index_cle = 0
for caractere in texte_clair:
if caractere.isalpha():
## Détermine la base ASCII selon la casse (majuscule ou minuscule)
base = ord('A') if caractere.isupper() else ord('a')
## Récupère la lettre correspondante dans la clé répétée
caractere_cle = cle_repetee[index_cle]
## Calcul du décalage en ASCII
decalage = ord(caractere_cle) - ord('a')
## Applique le décalage au caractère
caractere_chiffre = chr((ord(caractere.lower()) - ord('a') + decalage) % 26 + base)
texte_chiffre.append(caractere_chiffre if conserver_casse else caractere_chiffre.upper())
index_cle += 1
else:
## Conserve les caractères non alphabétiques tels quels
texte_chiffre.append(caractere)
return ''.join(texte_chiffre)
def vigenere_dechiffrer(texte_chiffre: str, cle: str, conserver_casse: bool = True) -> str:
texte_clair = []
## Répète la clé pour qu'elle ait la même longueur que le message chiffré
cle_repetee = (cle * (len(texte_chiffre) // len(cle) + 1)).lower()
index_cle = 0
for caractere in texte_chiffre:
if caractere.isalpha():
## Détermine la base ASCII selon la casse (majuscule ou minuscule)
base = ord('A') if caractere.isupper() else ord('a')
## Récupère la lettre correspondante dans la clé répétée
caractere_cle = cle_repetee[index_cle]
## Calcul du décalage en ASCII
decalage = ord(caractere_cle) - ord('a')
## Retire le décalage pour obtenir le caractère original
caractere_dechiffre = chr((ord(caractere.lower()) - ord('a') - decalage) % 26 + base)
texte_clair.append(caractere_dechiffre if conserver_casse else caractere_dechiffre.upper())
index_cle += 1
else:
## Conserve les caractères non alphabétiques tels quels
texte_clair.append(caractere)
return ''.join(texte_clair)
2. Limites du chiffre de Vigenère
La clé est répétée
Attaque possible via la méthode de répétitions de séquences ou indice de coïncidence, ce qui permet d’identifier la longueur de la clé.
Une fois la longueur de la clé connue, on peut découper le texte en sous-textes et utiliser l’analyse fréquentielle pour retrouver la clé, rendant le message facile à déchiffrer.
Démonstration:
import re
from collections import Counter
def calculer_indice_coincidence(texte: str) -> float:
## Supprime tous les caractères non alphabétiques et passe en minuscules
texte = re.sub(r'[^a-zA-Z]', '', texte).lower()
if len(texte) < 2:
return 0.0
freq = Counter(texte)
total = len(texte)
## Calcul de l'indice de coïncidence : somme des fréquences combinatoires divisée par le
nombre total de combinaisons possibles
ic = sum(count * (count - 1) for count in freq.values()) / (total * (total - 1))
return ic
def examen_kasiski(texte_chiffre: str, longueur_cle_max: int = 10) -> list:
## Nettoyage du texte : uniquement lettres, majuscules
texte_chiffre = re.sub(r'[^a-zA-Z]', '', texte_chiffre).upper()
sequences = {}
## On cherche toutes les séquences de 3 à 5 lettres dans le texte chiffré
for longueur in range(3, 6):
for i in range(len(texte_chiffre) - longueur):
seq = texte_chiffre[i:i + longueur]
if seq in sequences:
sequences[seq].append(i)
else:
sequences[seq] = [i]
distances = []
## Pour chaque séquence répétée, on calcule la distance entre ses positions
for seq, positions in sequences.items():
if len(positions) > 1:
for i in range(len(positions)):
for j in range(i + 1, len(positions)):
distances.append(positions[j] - positions[i])
facteurs = Counter()
## On compte combien de fois chaque longueur possible divise les distances trouvées
for distance in distances:
for longueur_possible in range(2, min(distance, longueur_cle_max) + 1):
if distance % longueur_possible == 0:
facteurs[longueur_possible] += 1
## On retourne les longueurs les plus probables de la clé
return [longueur for longueur, _ in facteurs.most_common()]
def attaque_frequence(texte_chiffre: str, longueur_cle_probable: int = None, langue: str = 'fr') -> str:
tables_freq = {
'en': 'etaoinshrdlcumwfgypbvkjxqz',
'fr': 'easnrtoulidcmpévqfbghjàxèyêzwk'
}
texte_chiffre_propre = re.sub(r'[^a-zA-Z]', '', texte_chiffre).upper()
if longueur_cle_probable is None:
## Si aucune longueur n’est fournie, on utilise l’examen de Kasiski
longueur_cle_probable = examen_kasiski(texte_chiffre)[0] if len(texte_chiffre) > 50 else 1
groupes = [''] * longueur_cle_probable
## On divise le texte chiffré en sous-groupes selon la longueur probable de la clé
for i, caractere in enumerate(texte_chiffre_propre):
groupes[i % longueur_cle_probable] += caractere
cle_devinee = []
for groupe in groupes:
freq = Counter(groupe.lower())
## La lettre la plus fréquente dans le groupe est supposée correspondre à la lettre la plus
fréquente dans la langue
plus_frequent = freq.most_common(1)[0][0]
decalage_devine = (ord(plus_frequent) - ord(tables_freq[langue][0])) % 26
cle_devinee.append(chr(decalage_devine + ord('a')))
cle_devinee = ''.join(cle_devinee)
## On déchiffre le message avec la clé devinée
return vigenere_dechiffrer(texte_chiffre, cle_devinee)
3. Améliorations possibles
Niveau 1 : Inversion du texte
Niveau 2 : Padding aléatoire
Niveau 3 : Double chiffrement avec clé transformée
Autres améliorations possible:
4. Démonstration
Ici pour voir le fichier vigenere.py complet
# -*- coding: utf-8 -*-
"""
Outil de chiffrement Vigenère - Version avec améliorations
=========================================================
Fonctionnalités :
- Chiffrement Vigenère classique
- Amélioration Niveau 1 : Inversion du texte
- Amélioration Niveau 2 : Ajout de padding aléatoire
- Amélioration Niveau 3 : Double chiffrement avec clé transformée
"""
import re
import random
def vigenere_chiffrer(texte_clair: str, cle: str) -> str:
"""
Chiffre un texte avec le chiffrement de Vigenère.
## Version classique utilisant une clé répétée.
"""
ciphertext = []
cle_repetee = (cle * (len(texte_clair) // len(cle) + 1)).lower()
idx_cle = 0
for char in texte_clair:
if char.isalpha():
base = ord('A') if char.isupper() else ord('a')
shift = ord(cle_repetee[idx_cle]) - ord('a')
chiffrer_char = chr((ord(char.lower()) - ord('a') + shift) % 26 + base)
ciphertext.append(chiffrer_char.upper())
idx_cle += 1
else:
ciphertext.append(char)
return ''.join(ciphertext)
def vigenere_dechiffrer(texte_chiffre: str, cle: str) -> str:
"""
Déchiffre un texte chiffré par Vigenère.
## Fonction symétrique à `vigenere_chiffrer`.
"""
plaintext = []
cle_repetee = (cle * (len(texte_chiffre) // len(cle) + 1)).lower()
idx_cle = 0
for char in texte_chiffre:
if char.isalpha():
base = ord('A') if char.isupper() else ord('a')
shift = ord(cle_repetee[idx_cle]) - ord('a')
decodage_char = chr((ord(char.lower()) - ord('a') - shift) % 26 + base)
plaintext.append(decodage_char.upper())
idx_cle += 1
else:
plaintext.append(char)
return ''.join(plaintext)
def vigenere_ameliorer_chiffrement(texte_clair: str, cle: str, niveau_securite: int = 1) -> str:
"""
Chiffre avec des améliorations de sécurité selon le niveau choisi.
## Options :
- Niveau 1 : Inversion du texte
- Niveau 2 : Padding aléatoire
- Niveau 3 : Double chiffrement avec clé transformée
"""
### AMÉLIORATION NIVEAU 1 : INVERSION DU TEXTE AVANT CHIFFREMENT
if niveau_securite == 1:
texte_inverse = texte_clair[::-1] # Inverser le message
return vigenere_chiffrer(texte_inverse, cle)
### AMÉLIORATION NIVEAU 2 : AJOUT DE CARACTÈRES DE PADDING ALÉATOIRES
elif niveau_securite == 2:
texte_inverse = texte_clair[::-1]
texte_chiffre = vigenere_chiffrer(texte_inverse, cle)
chars_padding = '!@#$%^&*()_+-=[]{}|;:,.<>?'
resultat = []
i = 0
while i < len(texte_chiffre):
taille_chunk = random.randint(3, 5)
chunk = texte_chiffre[i:i+taille_chunk]
resultat.append(chunk)
if i + taille_chunk < len(texte_chiffre):
resultat.append(random.choice(chars_padding))
i += taille_chunk
return ''.join(resultat)
### AMÉLIORATION NIVEAU 3 : DOUBLE CHIFFREMENT AVEC CLÉ TRANSFORMÉE
elif niveau_securite == 3:
premier_passage = vigenere_chiffrer(texte_clair, cle)
# Transformation de la clé : inversion + décalage de +1 sur chaque caractère
cle_transformee = ''.join(
chr((ord(c.lower()) - ord('a') + 1) % 26 + ord('a'))
for c in cle[::-1]
)
# Deuxième passage de chiffrement avec la clé modifiée
return vigenere_chiffrer(premier_passage, cle_transformee)
else:
raise ValueError("Niveau de sécurité invalide. Utiliser 1, 2 ou 3.")
def vigenere_ameliorer_dechiffrement(texte_chiffre: str, cle: str, niveau_securite: int = 1) -> str:
"""
Déchiffre un texte selon le niveau d'amélioration utilisé.
## Options :
- Niveau 1 : Inversion du texte
- Niveau 2 : Padding aléatoire
- Niveau 3 : Double chiffrement avec clé transformée
"""
### AMÉLIORATION NIVEAU 1 : INVERSION DU TEXTE APRÈS DÉCHIFFREMENT
if niveau_securite == 1:
texte_dechiffre = vigenere_dechiffrer(texte_chiffre, cle)
return texte_dechiffre[::-1]
### AMÉLIORATION NIVEAU 2 : SUPPRESSION DES CARACTÈRES DE PADDING
elif niveau_securite == 2:
# Supprimer les caractères de padding
texte_nettoyé = re.sub(r'[!@#$%^&*()_+\-=$\[\]{}|;:,.<>?]', '', texte_chiffre)
# Déchiffrer le texte nettoyé
texte_dechiffre = vigenere_dechiffrer(texte_nettoyé, cle)
# Inverser le résultat final
return texte_dechiffre[::-1]
### AMÉLIORATION NIVEAU 3 : DÉCHIFFREMENT DOUBLE AVEC CLÉ TRANSFORMÉE
elif niveau_securite == 3:
# Première déchiffrement avec la clé transformée
cle_transformee = ''.join(
chr((ord(c.lower()) - ord('a') + 1) % 26 + ord('a'))
for c in cle[::-1]
)
premier_passage = vigenere_dechiffrer(texte_chiffre, cle_transformee)
# Deuxième déchiffrement avec la clé originale
return vigenere_dechiffrer(premier_passage, cle)
else:
raise ValueError("Niveau de sécurité invalide. Utiliser 1, 2 ou 3.")
# Exemple d'utilisation
if __name__ == "__main__":
texte = "Le chiffrement de Vigenère est une méthode polyalphabétique."
cle = "securite"
print("=== CHIFFREMENT CLASSIQUE ===")
print(vigenere_chiffrer(texte, cle))
print("\n=== AMÉLIORATION NIVEAU 1 (INVERSION) ===")
chiffré_niv1 = vigenere_ameliorer_chiffrement(texte, cle, 1)
print("Chiffré:", chiffré_niv1)
print("Déchiffré:", vigenere_ameliorer_dechiffrement(chiffré_niv1, cle, 1))
print("\n=== AMÉLIORATION NIVEAU 2 (PADDING ALÉATOIRE) ===")
chiffré_niv2 = vigenere_ameliorer_chiffrement(texte, cle, 2)
print("Chiffré:", chiffré_niv2)
print("Déchiffré:", vigenere_ameliorer_dechiffrement(chiffré_niv2, cle, 2))
print("\n=== AMÉLIORATION NIVEAU 3 (DOUBLE CHIFFREMENT) ===")
chiffré_niv3 = vigenere_ameliorer_chiffrement(texte, cle, 3)
print("Chiffré:", chiffré_niv3)
print("Déchiffré:", vigenere_ameliorer_dechiffrement(chiffré_niv3, cle, 3))

À retenir
Ces techniques complexifient l'attaque mais ne garantissent pas la sécurité. Pour une vraie protection :
- Vigenère reste également uniquement pédagogique
- Vigenère offre un niveau supérieur de sécurité
- AES/RSA sont indispensables pour des données sensibles