chiffrement_affine.py

Note de lecture générée automatiquement

Le vrai fichier est scripts/maths/chiffrement_affine.py (Obsidian n’affiche pas les scripts eux-mêmes). Ne modifie pas cette note : modifie le script, puis relance python3 generer_vues_obsidian.py dans le dossier scripts.

Code

#!/usr/bin/env python3
"""
chiffrement_affine.py — Chiffrement / déchiffrement affine y = (a·x + b) mod 26
 
Utilisation :
    python3 chiffrement_affine.py chiffrer   5 13 "BONJOUR"      -> SFAGFJU
    python3 chiffrement_affine.py dechiffrer 5 13 "PDZ"           -> QYS
    python3 chiffrement_affine.py detail     5 13 "BONJOUR"       (tableau étape par étape)
    python3 chiffrement_affine.py casser "TEXTE CHIFFRE"           (essaie les 312 clés)
"""
import math
import sys
 
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 
 
def inverse_modulaire(a: int, n: int = 26) -> int:
    ancien_r, r = a % n, n
    ancien_u, u = 1, 0
    while r != 0:
        quotient = ancien_r // r
        ancien_r, r = r, ancien_r - quotient * r
        ancien_u, u = u, ancien_u - quotient * u
    if ancien_r != 1:
        raise ValueError(f"Clé invalide : pgcd({a}, {n}) = {ancien_r} ≠ 1")
    return ancien_u % n
 
 
def verifier_cle(a: int) -> None:
    if math.gcd(a, 26) != 1:
        valides = [k for k in range(1, 26) if math.gcd(k, 26) == 1]
        raise ValueError(f"a = {a} invalide (pgcd(a, 26) ≠ 1). Valeurs possibles : {valides}")
 
 
def chiffrer(texte: str, a: int, b: int) -> str:
    verifier_cle(a)
    resultat = ""
    for lettre in texte.upper():
        if lettre in ALPHABET:
            x = ALPHABET.index(lettre)
            resultat += ALPHABET[(a * x + b) % 26]
        else:
            resultat += lettre                      # espaces, ponctuation conservés
    return resultat
 
 
def dechiffrer(texte: str, a: int, b: int) -> str:
    verifier_cle(a)
    a_inverse = inverse_modulaire(a)
    resultat = ""
    for lettre in texte.upper():
        if lettre in ALPHABET:
            y = ALPHABET.index(lettre)
            resultat += ALPHABET[(a_inverse * (y - b)) % 26]
        else:
            resultat += lettre
    return resultat
 
 
def detail(texte: str, a: int, b: int) -> None:
    verifier_cle(a)
    print(f"Clé (a={a}, b={b}) — inverse de a modulo 26 : {inverse_modulaire(a)}")
    print(f"{'Lettre':<7}{'x':>4}{'a·x+b':>8}{'mod 26':>8}  Chiffrée")
    for lettre in texte.upper():
        if lettre not in ALPHABET:
            continue
        x = ALPHABET.index(lettre)
        brut = a * x + b
        print(f"{lettre:<7}{x:>4}{brut:>8}{brut % 26:>8}  {ALPHABET[brut % 26]}")
 
 
def casser(texte: str) -> None:
    # Attaque par force brute : 12 valeurs de a × 26 valeurs de b = 312 clés
    for a in range(1, 26):
        if math.gcd(a, 26) != 1:
            continue
        for b in range(26):
            print(f"a={a:>2} b={b:>2} : {dechiffrer(texte, a, b)}")
 
 
if __name__ == "__main__":
    if len(sys.argv) < 3:
        sys.exit(__doc__)
    action = sys.argv[1]
    if action == "casser":
        casser(sys.argv[2])
    else:
        cle_a, cle_b, message = int(sys.argv[2]), int(sys.argv[3]), sys.argv[4]
        if action == "chiffrer":
            print(chiffrer(message, cle_a, cle_b))
        elif action == "dechiffrer":
            print(dechiffrer(message, cle_a, cle_b))
        elif action == "detail":
            detail(message, cle_a, cle_b)
        else:
            sys.exit(__doc__)