Les nouvelles technologies pour l’enseignement des mathématiques
Intégration des TICE dans l’enseignement des mathématiques

MathémaTICE, première revue en ligne destinée à promouvoir les TICE à travers l’enseignement des mathématiques.

Le chiffrement Symétrique DES
Article mis en ligne le 31 mars 2026
dernière modification le 28 mars 2026

par Benjamin Clerc

Introduction

Le chiffrement symétrique est un chiffrement dans lequel la clé de chiffrement sert également à déchiffrer. On parle alors de clé secrète.
CÔTÉ ÉMETTEUR
    Message clair "Bonjour le monde" + Clé secrète
    ALGORITHME DE CHIFFREMENT -> Message chiffré (illisible) :  "3f 8a 2e 1d 7c 5b 9e 4a ..."

Transmission (réseau, stockage)

CÔTÉ RÉCEPTEUR
    Message chiffré + Clé secrète (identique)
    ALGORITHME DE DÉCHIFFREMENT -> Message clair (lisible) :     "Bonjour le monde"
Schéma d’utilisation classique

Bob souhaite chiffrer son disque dur pour qu’il soit le seul à pouvoir accéder à son contenu.
 Lors de l’installation de son système d’exploitation Bob a décidé de chiffrer son disque dur, il a choisi un mot de passe MDP.
 La totalité du disque dur est chiffré avec une clé C générée par le système, cette clé est stockée sur le disque dur.
 La clé C est à son tour chiffrée grâce au mot de passe MDP (elle ne doit pas être accessible en clair sur le disque).
 À chaque déverrouillage de son ordinateur Bob entre le mot de passe qui permet de déchiffrer la clé C ; elle est alors chargée en mémoire afin d’être disponible rapidement.
 À chaque fois qu’il ouvre un fichier, celui-ci est déchiffré avec C ; à chaque fois qu’il modifie le fichier il est chiffré avec C.

Ainsi, Bob est certain que tant que son mot de passe reste secret, ses données sont sécurisées même si quelqu’un accède au disque dur de son ordinateur.

Le DES (Data Encryption Standard) est un algorithme de chiffrement symétrique par blocs, développé par IBM dans les années 1970 et adopté comme standard fédéral américain en 1977.

Il prend en entrée un texte clair de 64 bits, une clé de 64 bits (dont 8 bits de parité, donc seuls 56 bits sont effectivement utilisés pour la sécurité), et applique une structure de Feistel sur 16 tours et retourne un texte chiffré de 64 bits. À chaque tour, une sous-clé différente de 48 bits est dérivée de la clé principale, et une fonction de transformation inclut des S-Boxes (boîtes de substitution) qui assurent la confusion nécessaire à la sécurité.

Le déchiffrement s’effectue avec la même clé, mais en utilisant les sous-clés dans l’ordre inverse, ce qui permet d’inverser le processus de chiffrement.

Bien qu’aujourd’hui considéré comme obsolète pour les applications critiques (vulnérable aux attaques par force brute), il a profondément marqué l’histoire de la cryptographie et ses principes sont toujours étudiés.

1. Principes généraux du DES

1.1 Caractéristiques principales

  • Algorithme symétrique : la même clé sert à chiffrer et déchiffrer
  • Chiffrement par blocs : traite les données par blocs de 64 bits
  • Clé de 56 bits (en réalité 64 bits dont 8 bits de parité)
  • Structure de Feistel : architecture fondamentale reprise dans de nombreux algorithmes

1.2 Vue d’ensemble du processus

Message clair (64 bits)
        ↓
[Permutation initiale (IP)]
        ↓
   16 rounds de Feistel
        ↓
[Permutation finale (IP⁻¹)]
        ↓
Message chiffré (64 bits)

Schéma de fonctionnement du chiffrement DES

Le Data Encryption Standard (DES) est un algorithme de chiffrement symétrique par blocs, basé sur un schéma de Feistel, qui opère sur des blocs de 64 bits en utilisant une clé secrète de 56 bits (avec 8 bits de parité sur 64 bits total). Il utilise 16 tours itératifs pour transformer le texte clair en texte chiffré.

Étapes principales du fonctionnement :
1. Permutation initiale ($IP$) : Le bloc de 64 bits est réorganisé selon une permutation fixe, sans impact sur la sécurité.
2. 16 tours de Feistel : Chaque tour divise le bloc en deux parties de 32 bits : $L_0$ (gauche) et $R_0$ (droite).

  • $R_1 = L_0 ⊕ f(R_0, K_1)$
  • $L_1 = R_0$$f$ est une fonction de confusion utilisant la sous-clé $K_1$ (48 bits), dérivée de la clé principale.
  • La fonction $f$ comprend :
    • Expansion de 32 bits vers 48 bits (E).
    • $XOR$ avec la sous-clé $K_1$.
    • Boîtes de substitution (S-Boxes) : 8 fonctions non linéaires qui remplacent 6 bits par 4 bits, ajoutant de la confusion.
    • Permutation de 32 bits (P) pour assurer la diffusion.

3. Échange final : Après les 16 tours, les deux moitiés sont échangées ($L_{16}$ devient $R_{16}$, $R_{16}$ devient $L_{16}$), mais sans transformation.
4. Permutation finale ($IP^{-1}$) : La sortie est réorganisée selon l’inverse de la permutation initiale, produisant le texte chiffré.

Déchiffrement :
Le déchiffrement suit le même schéma, mais les sous-clés sont utilisées dans l’ordre inverse ($K_{16}$ à $K_{1}$).

2. Fondements mathématiques

2.1 Algèbre de Boole

Le DES utilise intensivement les opérations logiques bit à bit :

  • $\text{XOR (⊕)}$ : différence. Le résultat est 1 si les deux bits sont différents.
    • Exemple : $01000001 ⊕ 10101010 = 11101011$.
  • $\text{AND (&)}$ : intersection. Le résultat est 1 si les deux bits comparés sont à 1, 0 sinon.
    • Exemple : $01100001 \text{&} 10101011 = 00100001$.
  • $\text{OR (|)}$ : union. Le résultat est 1 si au moins un des deux bits est à 1.
    • Exemple : $01100001 | 10101011 = 11101011$.
  • $\text{NON (¬)}$ : Inverse tous les bits d’un opérande (complément à 1).
    • Exemple : $¬01100001 = 10011110$.
  • Décalage à gauche (<<n) : Déplace les bits de $n$ vers la gauche, ajoute des 0 à droite. Équivalent à une multiplication par $2^n$.
    • Exemple : 5 << 1 = 10 (car 0b101 << 1 = 0b1010).
  • Décalage à droite (>>) : Déplace les bits de $n$ vers la droite. Pour les entiers non signés, ajoute des 0 à gauche. Pour les entiers signés, propage le bit de signe (décalage arithmétique). Équivalent à une division entière par $2^n$.
    • Exemple : 5 >> 1 = 2 (car 0b101 >> 1 = 0b10).
def operations_bit_a_bit(a, b):
    """Illustre les opérations de base sur 8 bits"""
    print(f"a = {a:08b} ({a})")
    print(f"b = {b:08b} ({b})")
    print(f"a ^ b = {a ^ b:08b} (XOR)")
    print(f"a & b = {a & b:08b} (AND)")
    print(f"a | b = {a | b:08b} (OR)")
    print(f"~a & 0xFF = {~a & 0xFF:08b} (NOT sur 8 bits)")
    print(f"a>>3 = {a>>3:08b} (décalage à droite de 3 bits)")
    print(f"a<<3 = {a<<3 & 0xFF:08b} (décalage à gauche de 3 bits sur 8 bits)")
# Test
operations_bit_a_bit(0b11001100, 0b10101010)

>>>a = 11001100 (204)
b = 10101010 (170)
a ^ b = 01100110 (XOR)
a & b = 10001000 (AND)
a | b = 11101110 (OR)
~a & 0xFF = 00110011 (NOT sur 8 bits)
a>>3 = 00011001 (décalage à droite de 3 bits)
a<<3 = 01100000 (décalage à gauche de 3 bits sur 8 bits)

Exercice 1 : Manipulation de bits en cryptographie

En cryptographie, les algorithmes de hachage (comme SHA-256) ou de chiffrement (comme DES) utilisent intensivement des opérations sur les bits.
Deux opérations fondamentales sont :

  • La rotation de bits : les bits sortant à droite réapparaissent à gauche.
  • Le décalage de bits : les bits sortant à droite sont perdus, des zéros sont ajoutés à gauche.

Partie 1 : Le décalage à droite

Le décalage à droite (>> en Python) déplace tous les bits d’un nombre vers la droite. Les bits qui « sortent » à droite sont perdus, et des zéros sont ajoutés à gauche.

Exemple avec un nombre 8 bits (pour simplifier) :

Nombre original : 11011010 (218 en décimal)
Décalage de 2 bits vers la droite :
Résultat : 00110110 (54 en décimal)

Les deux bits de droite (10) sont perdus, et deux zéros sont ajoutés à gauche.

Question 1.1 : Écrire une fonction shr(x, n) qui prend en paramètre :
 x : un entier représenté sur 32 bits (supposé non signé)
 n : un entier positif (le nombre de décalages)

et qui retourne le résultat du décalage logique à droite de x de n bits.

Exemples d’utilisation :

>>> shr(0b1101, 1)   # 1101 (13) → 0110 (6)
6
>>> shr(0b1101, 2)   # 1101 (13) → 0011 (3)
3
>>> hex(shr(0x12345678, 8))  # Décalage de 8 bits
0x00123456

Rappel : En Python, l’opérateur >> existe déjà, mais le but est de comprendre ce qu’il fait. Vous pouvez bien sûr l’utiliser dans votre fonction.

Partie 2 : La rotation à droite

La rotation est similaire au décalage, mais les bits qui sortent à droite réapparaissent à gauche.

Exemple avec un nombre 8 bits :

Nombre original : 11011010
Rotation de 2 bits vers la droite :
Étape 1 : décalage classique → 00110110 (les bits 10 sont perdus)
Étape 2 : récupération des bits perdus → les bits 10 sont placés à gauche
Résultat : 10110110

Principe mathématique pour une rotation sur 32 bits :

rotation_droite(x, n) = (x >> n) | (x << (32-n))

| est le OU binaire. Il faut ensuite masquer le résultat pour ne garder que 32 bits (avec & 0xFFFFFFFF).def

Question 2.1 : Écrire une fonction rotr(x, n) qui prend les mêmes paramètres que précédemment et qui retourne le résultat de la rotation à droite de x de n bits sur 32 bits.

Exemples d’utilisation :

>>> rotr(0b1101, 1)  # Sur 4 bits imaginaires, mais on travaille en 32 bits
0x80000006  # Le bit de poids faible passe en poids fort
>>> rotr(0x12345678, 4)  # Rotation de 4 bits
0x81234567  # Le dernier chiffre hexa (8) passe devant

Indice : Utilisez l’opérateur >> pour le décalage, << pour le décalage gauche, | pour le OU, et & 0xFFFFFFFF pour ne conserver que 32 bits.

# Compléter les fonctions ci-dessous :
def shr(x, n):
    pass
def rotr(x, n):
    pass

Correction de l’exercice 1

def shr(x, n):
    """Décalage droite sur 32 bits"""
    return x >> n
def rotr(x, n):
    """Rotation droite sur 32 bits"""
    return hex(((x >> n) | (x << (32 - n))) & 0xFFFFFFFF)

2.2 Permutations

Les permutations sont essentielles dans DES. Mathématiquement, une permutation est une bijection d’un ensemble fini vers lui-même.

Une bijection (ou application bijective) est une fonction mathématique qui associe à chaque élément d’un ensemble de départ un et un seul élément d’un ensemble d’arrivée, et vice versa. Elle est définie comme étant à la fois injective (pas deux éléments de départ pour la même image) et surjective (chaque élément d’arrivée a au moins un antécédent).

Une permutation est une réorganisation des éléments d’un ensemble. Pour le DES, on travaille avec des permutations de bits.

Prenons un exemple simple avec 4 bits $[b₁, b₂, b₃, b₄] = [1, 0, 1, 1]$.
Une permutation est définie par une table qui indique où chaque bit doit être déplacé. Par exemple, la table $[4, 1, 3, 2]$ signifie :

  • Le 1er bit $(b₁)$ va en position $2$
  • Le 2e bit $(b₂)$ va en position $4$
  • Le 3e bit $(b₃)$ reste en position $3$
  • Le 4e bit $(b₄)$ va en position $1$

Résultat : $[b_4, b_1, b_3, b_2] = [1, 1, 1, 0]$.

C’est une bijection car :

  • Chaque bit d’entrée a une unique position de sortie
  • Chaque position de sortie reçoit exactement un bit d’entrée

Dans le DES, ces permutations sont fixes (toujours les mêmes tables) et servent à diffuser l’information : un seul bit modifié en entrée va influencer plusieurs bits en sortie, créant ainsi l’effet avalanche essentiel à la sécurité.

def appliquer_permutation(bloc, table_permutation, taille_entree):
    """
    Applique une permutation à un bloc binaire
    bloc : entier représentant les bits
    table_permutation : liste des positions de sortie (1-indexées)
    """
    resultat = 0
    for i, pos_source in enumerate(table_permutation):
        # On récupère le bit à la position pos_source
        bit_source = (bloc >> (taille_entree - pos_source)) & 1
        # On place ce bit à la position i
        resultat |= bit_source << (len(table_permutation) - 1 - i)
        #print(i, pos_source,bin(resultat))   Pour afficher toutes les étapes de la permutation
    return resultat
# Exemple : permutation simple
bloc_test = 0b0111  # 4 bits
perm = [2, 4, 1, 3]  # Table de permutation
resultat = appliquer_permutation(bloc_test, perm, 4)
print(f"Bloc original : {bloc_test:04b}")
print(f"Après permutation : {resultat:04b}")

>>> 0 2 0b1000
1 4 0b1100
2 1 0b1100En résumé
3 3 0b1101
Bloc original : 0111
Après permutation : 1101

2.3 Boîtes de substitution (S-Boxes)

Les S-Boxes (Voir sur Wikipedia) sont le cœur non-linéaire du DES. Elles prennent 6 bits en entrée et produisent 4 bits en sortie selon une table de correspondance.

l’origine des S-Boxes

L’histoire de l’origine des S-Boxes du DES est l’un des épisodes les plus fascinants et controversés de la cryptographie moderne. Elle mêle secrets d’État, avancées technologiques secrètes et une résolution qui n’est intervenue que près de 20 ans après la publication du standard.

Voici l’histoire en trois actes.

1. Une origine mystérieuse et des soupçons de « backdoor » (années 1970)

Au départ, IBM développa un algorithme nommé Lucifer avec une clé de 128 bits. Lorsque le National Bureau of Standards (NBS, devenu NIST) appela à un standard de chiffrement, IBM le soumit, mais avec une clé réduite à 56 bits à la demande de la NSA (National Security Agency). La modification la plus mystérieuse, cependant, fut l’altération des S-Boxes, ces huit tables de substitution qui constituent le cœur non-linéaire de l’algorithme.
La NSA modifia les S-Boxes proposées initialement par IBM . Aucune explication publique ne fut donnée, ce qui alimenta immédiatement les soupçons : beaucoup pensèrent que la NSA avait implanté une « porte dérobée » (backdoor) , une propriété mathématique secrète leur permettant d’espionner facilement les communications chiffrées .

2. La découverte publique d’une attaque et la levée du secret (années 1990)

Le mystère a commencé à se dissiper en 1990, lorsque les chercheurs Eli Biham et Adi Shamir publièrent une méthode d’attaque révolutionnaire appelée cryptanalyse différentielle . Ils découvrirent alors une chose surprenante : le DES, avec ses S-Boxes modifiées, était extraordinairement résistant à cette nouvelle attaque . De simples modifications des S-Boxes suffisaient à rendre le DES vulnérable.
Il est apparu que la NSA et l’équipe d’IBM (notamment Don Coppersmith) connaissaient déjà la cryptanalyse différentielle dès la conception du DES dans les années 1970, bien avant sa publication publique . Leurs modifications des S-Boxes avaient pour but précis de renforcer l’algorithme contre cette attaque, et non de l’affaiblir.
Ce n’est qu’en 1994 que Don Coppersmith publia officiellement les critères de conception des S-Boxes, confirmant cette version . Le gouvernement américain autorisa cette publication une fois que la technique de cryptanalyse différentielle fut tombée dans le domaine public.

3. Des critères de conception très stricts

Loin d’être arbitraires, les S-Boxes du DES ont été choisies pour satisfaire à des critères mathématiques rigoureux afin de maximiser la sécurité :

  • Non-linéarité : Aucune S-Box ne doit être une fonction linéaire ou affine de son entrée . C’est ce qui crée la confusion (selon Shannon), essentielle pour déjouer les analyses statistiques.
  • Résistance à la cryptanalyse différentielle : Pour toute différence d’entrée non nulle, la différence de sortie ne doit pas être trop prévisible. Les concepteurs ont notamment veillé à ce qu’un changement d’un seul bit en entrée entraîne la modification d’au moins deux bits en sortie .
  • Résistance à la cryptanalyse linéaire : Bien que cette attaque n’ait été publiée qu’en 1993, les S-Boxes ont montré par la suite une bonne résistance, même si elle n’était pas un critère de conception initial .
Les 8 S-Boxes de DES

Elles sont nommées $S_1, S_2, S_3, S_4, S_5, S_6, S_7, S_8$. Chaque S-Box convertit 6 bits en 4 bits. Toutes utilisent le même principe :
 Bits externes (positions 1 et 6) → ligne (0 à 3)
 Bits centraux (positions 2 à 5) → colonne (0 à 15)

Les 8 S-Boxes sont différentes pour maximiser la sécurité.

Exemple

Voici une S-Box ($S_5$) tirée de l’algorithme DES. La sortie de 4 bits est obtenue à partir de l’entrée de 6 bits. On divise ces 6 bits en deux parties : les deux bits aux extrémités et les quatre bits restants (au centre). Les deux bits aux extrémités indiquent la ligne et les bits centraux donnent la colonne correspondante. Par exemple, avec une entrée « 011011 », on divise en « 0 1101 1 ». Ce qui donne pour la ligne « 01 » et pour la colonne « 1101 ». La sortie de la table est alors « 1001 ».

  • Sur la première ligne les 4 bits au centre de l’entrée
  • Sur la première colonne les 2 bits externes
S5 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
00 0010 1100 0100 0001 0111 1010 1011 0110 1000 0101 0011 1111 1101 0000 1110 1001
01 1110 1011 0010 1100 0100 0111 1101 0001 0101 0000 1111 1010 0011 1001 1000 0110
10 0100 0010 0001 1011 1010 1101 0111 1000 1111 1001 1100 0101 0110 0011 0000 1110
11 1011 1000 1100 0111 0001 1110 0010 1101 0110 1111 0000 1001 1010 0100 0101 0011
Contraintes d’implémentation matérielle

Les S-Boxes ont également été optimisées pour être implémentées avec un nombre minimum de circuits logiques (52 ou 53 minterms au lieu des $2^6 = 64$ théoriques : Dans les années 1970, moins de minterms = moins de transistors = circuit plus petit, plus rapide et moins cher.), une contrainte forte pour la technologie des années 1970. Cela prouve que les S-Boxes ne sont pas aléatoires. Des tables complètement aléatoires auraient nécessité beaucoup plus de minterms. Les concepteurs ont choisi des fonctions booléennes ayant une structure mathématique particulière, ce qui a permis cette optimisation.Shannon

En résumé

Les S-Boxes du DES ne contiennent pas de porte dérobée. Leur conception, en avance sur la recherche publique de plusieurs décennies, visait à protéger l’algorithme contre des attaques que seuls les concepteurs connaissaient. C’est un exemple parfait de la longueur d’avance des agences gouvernementales en cryptographie à cette époque.

# ============================================================
# LES 8 S-BOXES DU DES (standards)
# ============================================================
S_BOXES = [
    # S1 correspond à S_BOXES[0]
    [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7],    #ligne 0
     [0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8],    #ligne 1
     [4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0],    #ligne 2
     [15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13]],   #ligne 3
    
    # S2 correspond à S_BOXES[1]
    [[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10],
     [3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5],
     [0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15],
     [13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9]],
    
    # S3 correspond à S_BOXES[2]
    [[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8],
     [13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1],
     [13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7],
     [1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12]],
    
    # S4 correspond à S_BOXES[3]
    [[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15],
     [13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9],
     [10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4],
     [3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14]],
    
    # S5 correspond à S_BOXES[4]
    [[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9],
     [14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6],
     [4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14],
     [11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3]],
    
    # S6 correspond à S_BOXES[5]
    [[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11],
     [10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8],
     [9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6],
     [4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13]],
    
    # S7 correspond à S_BOXES[6]
    [[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1],
     [13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6],
     [1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2],
     [6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12]],
    
    # S8 correspond à S_BOXES[7]
    [[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7],
     [1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2],
     [7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8],
     [2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]]
]
def appliquer_sbox(valeur_6bits, sbox_num):
    """
    Applique la S-Box numéro sbox_num (0-7) à une valeur 6 bits
    Retourne 4 bits
    """
    sbox = S_BOXES[sbox_num]  # sbox_num est un entier entre 0 et 7 
    ligne = ((valeur_6bits & 0b100000) >> 4) | (valeur_6bits & 0b1) # Ligne = bit1 et bit6
    colonne = (valeur_6bits >> 1) & 0b1111 # Colonne = bits 2-5
    return sbox[ligne][colonne]

# Test de la S-Box
print(appliquer_sbox(0b101011,0))  # b1=1, b6=1, bits_centraux = 5 → ligne 3, col 5 → 9
print(appliquer_sbox(0b000000,1))  # b1=0, b6=0, bits_centraux = 0 → ligne 0, col 0 → 15
print(appliquer_sbox(0b111111,2))  # b1=1, b6=1, bits_centraux = 15 → ligne 3, col 15 → 12
print(appliquer_sbox(0b001111,3))  # b1=0, b6=1, bits_centraux = 7 → ligne 1, col 7 → 3

3. Structure détaillée du DES

3.1 Génération des sous-clés (16 rounds)

La génération des sous-clés dans le DES transforme une clé principale de 64 bits (dont 8 bits de parité) en 16 sous-clés distinctes de 48 bits, une pour chaque round. Voici son fonctionnement en 4 étapes :

  • Permutation PC1 (56 bits) : On applique une permutation qui élimine les bits de parité et mélange les 56 bits restants, puis on les divise en deux moitiés de 28 bits : $C_0$ (gauche) et $D_0$ (droite).
  • Décalages circulaires : Pour chaque round $i$ (de 1 à 16), on décale circulairement vers la gauche les registres C et D. Le nombre de décalages est prédéfini : 1 pour les rounds 1, 2, 9, 16 et 2 pour les autres.
  • Permutation PC2 (48 bits) : On concatène les deux moitiés décalées $C_1$ et $D_1$ pour former un bloc de 56 bits. Une seconde permutation, PC2, sélectionne et mélange 48 de ces 56 bits pour produire la sous-clé $K_1$ du round.
  • Répétition : On répète les étapes 2 et 3 pour les 16 rounds. La clé de chaque round est donc une version décalée et compressée de la clé principale. Ce processus garantit que chaque bit de la clé principale influence plusieurs sous-clés différentes.
    # ============================================================
    # GÉNÉRATION DES SOUS-CLÉS
    # ============================================================
    def generer_sous_cles(cle_64bits):
        """
        Génère les 16 sous-clés de 48 bits à partir d'une clé de 64 bits
        """
        # Permutation PC1 (64→56 bits)
        PC1 = [57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18,
               10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36,
               63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22,
               14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4]
        # Permutation PC2 (56→48 bits)
        PC2 = [14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10,
               23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2,
               41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48,
               44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32]
        # Étape 1: Permutation PC1
        cle_56bits = 0
        for i, pos in enumerate(PC1):
            bit = (cle_64bits >> (64 - pos)) & 1
            cle_56bits |= bit << (55 - i)
        # Séparation en deux moitiés de 28 bits
        C = (cle_56bits >> 28) & 0xFFFFFFF
        D = cle_56bits & 0xFFFFFFF
        # Décalages pour chaque round
        decalages = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
        sous_cles = []
        for decal in decalages:
            # Décalage circulaire gauche
            C = ((C << decal) | (C >> (28 - decal))) & 0xFFFFFFF
            D = ((D << decal) | (D >> (28 - decal))) & 0xFFFFFFF
            # Combinaison
            CD = (C << 28) | D
            # Permutation PC2
            sous_cle = 0
            for j, pos in enumerate(PC2):
                bit = (CD >> (56 - pos)) & 1
                sous_cle |= bit << (47 - j)
            sous_cles.append(sous_cle)
        return sous_cles
    
    # Test avec une clé exemple
    cle_exemple = 0x133457799BBCDFF1  # Clé exemple du standard DES
    sous_cles = generer_sous_cles(cle_exemple)
    print(f"Clé principale : {cle_exemple:016X}")
    print(f"Première sous-clé : {sous_cles[0]:012X}")
    
    >>>Clé principale : 133457799BBCDFF1
    Première sous-clé : 1B02EFFC7072
    

3.2 La fonction de Feistel (round)

La fonction de Feistel est le cœur de chaque round du DES. Elle prend en entrée la moitié droite (32 bits) du bloc et une sous-clé (48 bits) pour produire une sortie de 32 bits qui viendra masquer la moitié gauche.

Son fonctionnement se décompose en 4 étapes :

  • Expansion : la moitié droite (32 bits) est étendue à 48 bits par duplication de certains bits, permettant à un bit d’influencer plusieurs S-Boxes.
  • XOR avec la sous-clé : le résultat de l’expansion est combiné avec la sous-clé du round via un OU exclusif.
  • Substitution (S-Boxes) : le bloc de 48 bits est divisé en 8 blocs de 6 bits. Chaque bloc traverse une S-Box (table de substitution), une boîte non linéaire qui transforme 6 bits en 4 bits. C’est l’étape qui apporte la confusion en masquant la relation statistique entre la clé et le chiffré.
  • Permutation : les 32 bits issus des S-Boxes sont mélangés selon une table fixe pour assurer la diffusion.

Le résultat de cette fonction est ensuite XORé avec la moitié gauche du bloc pour produire la nouvelle moitié droite du round suivant.

# ============================================================
# FONCTION DE FEISTEL
# ============================================================
def fonction_feistel(moitie_droite, sous_cle):
    """
    Fonction de Feistel complète du DES
    moitie_droite: 32 bits
    sous_cle: 48 bits
    retourne: 32 bits
    """
    # Table d'expansion E (32 → 48 bits)
    E = [32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9,
         8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17,
         16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25,
         24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1]
    # 1. Expansion
    R_expand = 0
    for i, pos in enumerate(E):
        bit = (moitie_droite >> (32 - pos)) & 1
        R_expand |= bit << (47 - i)
    # 2. XOR avec sous-clé
    R_xor = R_expand ^ sous_cle
    # 3. Substitution via S-Boxes ( (8 × 6 = 48 bits → 8 × 4 = 32 bits))
    resultat_sbox = 0
    for i in range(8):
        # Extraction d'un bloc de 6 bits
        bloc_6bits = (R_xor >> (42 - 6*i)) & 0x3F
        sortie_4bits = appliquer_sbox(bloc_6bits, i)  # i est l'entier 0-7
        resultat_sbox = (resultat_sbox << 4) | sortie_4bits
    # 4. Permutation P
    P = [16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10,
         2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25]
    resultat = 0
    for i, pos in enumerate(P):
        bit = (resultat_sbox >> (32 - pos)) & 1
        resultat |= bit << (31 - i)
    return resultat

3.3 Le chiffrement complet

# ============================================================
# CHIFFREMENT DES COMPLET
# ============================================================
def des_chiffrement(message_64bits, cle_64bits):
    """
    Chiffre un bloc de 64 bits avec DES
    """
    # Permutation initiale IP
    IP = [58, 50, 42, 34, 26, 18, 10, 2,
          60, 52, 44, 36, 28, 20, 12, 4,
          62, 54, 46, 38, 30, 22, 14, 6,
          64, 56, 48, 40, 32, 24, 16, 8,
          57, 49, 41, 33, 25, 17, 9, 1,
          59, 51, 43, 35, 27, 19, 11, 3,
          61, 53, 45, 37, 29, 21, 13, 5,
          63, 55, 47, 39, 31, 23, 15, 7]
    # Permutation finale IP⁻¹
    IP_INV = [40, 8, 48, 16, 56, 24, 64, 32,
              39, 7, 47, 15, 55, 23, 63, 31,
              38, 6, 46, 14, 54, 22, 62, 30,
              37, 5, 45, 13, 53, 21, 61, 29,
              36, 4, 44, 12, 52, 20, 60, 28,
              35, 3, 43, 11, 51, 19, 59, 27,
              34, 2, 42, 10, 50, 18, 58, 26,
              33, 1, 41, 9, 49, 17, 57, 25]
    # 1. Permutation initiale
    message_permute = appliquer_permutation(message_64bits, IP, 64)
    # 2. Séparation en deux moitiés
    L = (message_permute >> 32) & 0xFFFFFFFF
    R = message_permute & 0xFFFFFFFF
    # 3. Génération des sous-clés
    sous_cles = generer_sous_cles(cle_64bits)
    # 4. 16 rounds de Feistel
    for i in range(16):
        L_old, R_old = L, R
        L = R_old
        R = L_old ^ fonction_feistel(R_old, sous_cles[i])
    # 5. Permutation finale
    bloc_pre_final = (R << 32) | L
    message_chiffre = appliquer_permutation(bloc_pre_final, IP_INV, 64)
    return message_chiffre
"""
======================================================================
TEST DE L'IMPLÉMENTATION PÉDAGOGIQUE DU DES
======================================================================
"""
print("=" * 70)
print("TEST DE L'IMPLÉMENTATION PÉDAGOGIQUE DU DES")
print("=" * 70)
# Exemple classique du standard DES
message_clair = 0x0123456789ABCDEF
cle = 0x133457799BBCDFF1
print(f"\nMessage clair : {message_clair:016X}")
print(f"Clé           : {cle:016X}")
# Chiffrement
message_chiffre = des_chiffrement(message_clair, cle)
print(f"\nMessage chiffré : {message_chiffre:016X}")

4. Limitations et héritage du DES

Attaques connues contre DES

Le tableau ci-dessous résume les meilleures attaques connues contre DES :

attaque paires connues/choisies mémoire temps
pré-calcul 1 $2^{56}$ 1
recherche exhaustive 1 1 $2^{55}$
cryptanalyse linéaire $2^{43}$ textes $2^{43}$
cryptanalyse différentielle $2^{47}$ textes $2^{47}$
  • La colonne « Paires connues/choisies » indique le nombre de couples (message clair, message chiffré) nécessaires à l’attaque :
    • « Connues » : l’attaquant dispose de ces paires sans pouvoir les choisir
    • « Choisies » : l’attaquant peut choisir les messages clairs à chiffrer
  • La colonne « Mémoire » indique l’espace de stockage nécessaire (en nombre de blocs de 64 bits)
    • Exprimé en puissance de 2 : $2^{56} \text{blocs}\approx 72\times10^{15}\times 8 \text{octets} = 5.76×10^{17} \text{o} = 576 000 000 \text{To}$ (impossible en pratique)
  • La colonne « Temps » indique le nombre d’opérations de chiffrement nécessaires
    • Une opération = un chiffrement DES complet
    • Exprimé aussi en puissance de 2

Analyse détaillée des attaques

1. Attaque par pré-calcul $(1, 2^{56}, 1)$
# Principe : calculer à l'avance tous les chiffrements possibles
def attaque_precalcul(message_clair, message_chiffre):
    # Phase de précalcul (une seule fois)
    table = {}
    for cle in range(2**56):  # Toutes les clés possibles
        table[des_chiffrement(message_clair, cle)] = cle
    # Phase d'attaque (instantanée)
    return table[message_chiffre]
  • Paires : 1 seul couple suffit
  • Mémoire : Énorme ($2^{56}$ entrées) → impossible en pratique
  • Temps : 1 seul accès mémoire après pré-calcul

Conclusion : Théorique mais irréaliste (nécessiterait des yottabytes ($10^{24}$ bytes))

2. Recherche exhaustive (force brute) $(1, 1, 2^{55})$
def force_brute(message_clair, message_chiffre):
    for cle in range(2**56):
        if des_chiffrement(message_clair, cle) == message_chiffre:
            return cle
    return None
  • Paires : 1 seul couple suffit
  • Mémoire : Négligeable
  • Temps : $2^{55}$ essais en moyenne (moitié de l’espace des clés)

En 2024 : $2^{55} \approx 36 000 000$ milliards d’opérations → réalisable avec du matériel dédié comme Deep Crack (2008) qui cassait DES en 6 jours.

3. Cryptanalyse linéaire $(2^{43}, 2^{43}, 2^{43})$

Découverte par Matsui en 1993, elle utilise des approximations linéaires du DES.

# Principe simplifié
def cryptanalyse_lineaire(message_clair, message_chiffre):
    # Étape 1 : Trouver une relation linéaire approchée
    # P[i1] ⊕ ... ⊕ P[ik] ⊕ C[j1] ⊕ ... ⊕ C[jl] = K[m]
    # avec probabilité ≠ 1/2
    # Étape 2 : Collecter 2^43 paires
    for paire in 2**43 paires:
        # Vérifier la relation
        # Accumuler des statistiques sur les bits de clé
    # Étape 3 : La clé qui apparaît le plus souvent est la bonne
    return cle_probable
  • Principe : Trouver une approximation linéaire entre bits du clair, du chiffré et de la clé
  • Données : $2^{43}$ textes clairs connus ($\approx 8 796$ milliards)
  • Complexité : $2^{43}$ opérations (bien mieux que $2^{55}$)
     Efficacité : Première attaque théoriquement plus rapide que la force brute
4. Cryptanalyse différentielle $(2^{47}, 2^{47}, 2^{47})$

Découverte par Biham et Shamir (années 1990), elle analyse la propagation de différences entre paires de textes.

# Principe simplifié
def cryptanalyse_differentielle():
    # Étape 1 : Choisir une différence ΔP pour les messages clairs
    ΔP = 0x00200000  # Exemple : différence sur un bit
    # Étape 2 : Collecter 2^47 paires (P, P⊕ΔP)
    paires = []
    for _ in range(2**47):
        P1 = random_message()
        P2 = P1 ⊕ ΔP
        C1 = des_chiffrement(P1)
        C2 = des_chiffrement(P2)
        paires.append((P1, P2, C1, C2))
    # Étape 3 : Analyser les différences ΔC = C1⊕C2
    # Certaines différences apparaissent avec des probabilités caractéristiques
    # Ces probabilités révèlent des informations sur la clé
    return cle-* 
  • Principe : Observer comment une différence dans le clair se propage dans le chiffré
  • Données : $2^{47}$ paires choisies (l’attaquant choisit les différences)
  • Complexité : $2^{47}$ opérations

Remarque : Le DES a été spécifiquement conçu pour résister à cette attaque.

Pourquoi DES n’est plus sûr

Le tableau montre que :

  • La force brute est devenue réalisable (machine à 250 000 $ en 1998)
  • Les attaques cryptanalytiques réduisent théoriquement la complexité
  • Mais surtout, la clé de 56 bits est trop courte :

Résultat : DES a été remplacé par AES (clés de 128-256 bits)

La machine à 250 000 $ de 1998 (la célèbre Deep Crack) mettait environ 56 heures pour trouver une clé DES.

Aujourd’hui, la donne a complètement changé, principalement grâce à l’optimisation matérielle pour le minage de Bitcoin. Le minage de Bitcoin utilise l’algorithme SHA-256, dont les opérations de base (rotations, décalages, XOR) sont très similaires à celles du chiffrement DES. Les ASICs (circuits intégrés spécifiques) conçus pour le minage sont donc extraordinairement efficaces pour ce type de calcul.

Le calcul pour aujourd’hui

L’espace des clés DES est de $2^{56}$ ($\approx 7.2\times10^{16}$ environ 72 quadrillions de clés). En moyenne, pour trouver la bonne clé, il faut en essayer la moitié, soit $2^{55} \approx 3.6\times10^{16}$ clés à tester.

Une ASIC moderne spécialisée dans le minage de Bitcoin (comme l’Antminer S19) peut atteindre environ $100×10^{12}$ hachages SHA-256 par seconde. Mais le calcul pour DES est différent. Si on conçoit une ASIC spécifiquement optimisée pour DES, on peut estimer sa capacité.

Prenons une ASIC moderne dédiée au DES :

  • Fréquence : 1 GHz (1 milliard d’opérations par seconde)
  • Nombre de cœurs : 1000 cœurs par puce
  • Nombre de puces : 100 puces par machine

Soit une capacité totale de : $10^9 \text{op/s} × 1000 × 100 = 10^{14}$ clés/seconde.
Le temps moyen pour casser DES est donc : $\dfrac{2^{55} \text{clés}}{10^{14} \text{clés/s}} ≈ 360 \text{secondes} ≈ 6 \text{minutes}$.

Comparaison :
 EFF Deep Crack (1998) : 88 milliards clés/s → environ 56 heures pour tester tout l’espace (soit 28 heures en moyenne)
 ASIC moderne : 100 000 milliards clés/s → environ 6 minutes en moyenne

Soit un gain de vitesse d’environ 280 fois par rapport au Deep Crack.

Le facteur coût : la vraie révolution

La véritable avancée réside dans le coût. En 1998, il fallait un budget de 250 000 $.

Aujourd’hui, avec le même budget (250 000 $), on pourrait acheter environ 90 ASICs (250 000 / 2 800). En les faisant travailler ensemble, le temps de cassage tomberait à seulement 4 secondes.

Message à retenir : Un algorithme de chiffrement doit résister à la fois à la puissance de calcul croissante (loi de Moore) et aux avancées de la cryptanalyse théorique. Le cas DES montre qu’une clé de 56 bits, considérée comme sûre dans les années 1970, est devenue vulnérable en 20 ans, et totalement obsolète aujourd’hui face aux capacités de calcul parallélisées à bas coût.

4.1 Points faibles

 Clé trop courte (56 bits) → vulnérable aux attaques par force brute
 Clés faibles : certaines clés produisent des résultats prévisibles
 Complémentarité : $DES(¬M, ¬K) = ¬DES(M, K)$ (Voir exercice 2 ci-dessous)

Les clés faibles du DES

Dans le DES, certaines clés produisent des comportements cryptographiques anormaux car elles génèrent des sous-clés identiques ou présentant des symétries. On distingue quatre catégories.

1. Clés faibles (4 clés)

Une clé est dite faible lorsque les 16 sous-clés générées sont toutes identiques. Cela signifie que chiffrer deux fois avec la même clé ramène au message original : DESₖ(DESₖ(M)) = M.

Pourquoi ? Cela arrive quand les deux moitiés de la clé (C et D) sont composées uniquement de 0 ou uniquement de 1. Les décalages ne changent rien.

Les 4 clés faibles (en hexadécimal, avec bits de parité) sont :

Moitié C Moitié D Clé (64 bits)
1 Tous 0 Tous 0 0x0101010101010101
2 Tous 0 Tous 1 0x01010101010101FE
3 Tous 1 Tous 0 0xFEFEFEFEFEFEFE01
4 Tous 1 Tous 1 0xFEFEFEFEFEFEFEFE

Conséquence : Pour ces clés, le chiffrement est involutif (chiffrer deux fois déchiffre).

2. Clés semi-faibles (12 clés)

Des clés sont dites semi-faibles lorsqu’elles existent par paires : la clé K₁ génère les mêmes sous-clés qu’une autre clé K₂, mais dans l’ordre inverse. Ainsi : DESₖ₁(DESₖ₂(M)) = M.

Cela arrive quand les moitiés C et D produisent des séquences de sous-clés qui sont simplement inversées entre les deux clés.

Exemple de paire :

  • K₁ = 0x01FE01FE01FE01FE
  • K₂ = 0xFE01FE01FE01FE01

Conséquence : Ces clés créent une relation de dualité entre chiffrement et déchiffrement.

3. Clés possiblement faibles (48 clés)

Une catégorie intermédiaire où les sous-clés ne sont pas toutes identiques, mais se répètent avec une période courte (4 ou 2 sous-clés distinctes seulement au lieu de 16). Cela réduit la complexité effective du chiffrement.

4. Clés totalement faibles

Cas extrême : les clés pour lesquelles **chaque bit est identique** (tout 0 ou tout 1, mais avec les bits de parité corrects) :

  • 0x0000000000000000 (théoriquement invalide à cause de la parité, mais conceptuellement)
  • 0xFFFFFFFFFFFFFFFF

Avec ces clés, le DES devient complètement linéaire dans certaines configurations.

Pourquoi ces clés existent ?

La structure de génération des sous-clés par décalage est responsable :

  • Si C et D sont constants (tout 0 ou tout 1), les décalages ne changent rien → clés faibles
  • Si C et D ont des motifs symétriques, on obtient des paires de clés qui s’inversent → clés semi-faibles
Impact pratique

Bien que ces clés existent, leur nombre est infime par rapport à l’espace total des clés (seulement 64 clés concernées sur $2^{56} ≈ 7,2 × 10^{16}$). Le risque de les choisir aléatoirement est donc négligeable (environ $10^{-15}$).

Cependant, une implémentation sérieuse du DES doit détecter et rejeter ces clés, car leur utilisation compromet totalement la sécurité.

4.2 Successeurs

  • Triple DES (3DES) : applique DES trois fois avec deux ou trois clés différentes
  • AES (Advanced Encryption Standard) : successeur actuel, plus robuste
def triple_des_simplifie(message, k1, k2, k3):
    """
    Principe du Triple DES : Eₖ₃(Dₖ₂(Eₖ₁(message)))
    """
    # Chiffrement avec k1
    temp1 = des_chiffrement(message, k1)
    # Déchiffrement avec k2
    temp2 = des_chiffrement(temp1, k2)  # Note: déchiffrement similaire
    # Chiffrement avec k3
    temp3 = des_chiffrement(temp2, k3)
    return temp3
message_clair = 0x0123456789ABCDEF
cle1 = 0x7A3B5C1D8E4F2A6B
cle2 = 0x9C8D7E6F5A4B3C2D
cle3 = 0x1F2E3D4C5B6A7F8E
triple_des_simplifie(message_clair, cle1, cle2, cle3)

5. Exercices pratiques

Exercice 2 : Propriété de complémentarité

Vérifier que DES(¬M, ¬K) = ¬DES(M, K) sur un exemple simple.

Correction de l’exercice 2

def test_complementarite():
    """Test de la propriété de complémentarité"""
    """Vérifie la propriété DES(¬M, ¬K) = ¬DES(M, K)"""
    print("\n" + "=" * 70)
    print("TEST DE LA PROPRIÉTÉ DE COMPLÉMENTARITÉ")
    print("=" * 70)
    
    M = 0x0123456789ABCDEF
    K = 0x133457799BBCDFF1
    
    # DES(M, K)
    C1 = des_chiffrement(M, K)
    print(f"\nDES(M, K)      = {C1:016X}")
    
    # ¬DES(M, K)
    C1_comp = (~C1) & 0xFFFFFFFFFFFFFFFF
    print(f"¬DES(M, K)     = {C1_comp:016X}")
    
    # DES(¬M, ¬K)
    M_comp = (~M) & 0xFFFFFFFFFFFFFFFF
    K_comp = (~K) & 0xFFFFFFFFFFFFFFFF
    C2 = des_chiffrement(M_comp, K_comp)
    print(f"DES(¬M, ¬K)    = {C2:016X}")
    if C2 == C1_comp:
        print("\n✓ Propriété de complémentarité vérifiée")
        return True
test_complementarite()

======================================================================
TEST DE LA PROPRIÉTÉ DE COMPLÉMENTARITÉ
======================================================================
DES(M, K)      = 85E813540F0AB405
¬DES(M, K)     = 7A17ECABF0F54BFA
DES(¬M, ¬K)    = 7A17ECABF0F54BFA
✓ Propriété de complémentarité vérifiée
True

Exercice 3 : Impact d’un bit modifié

Modifier un seul bit du message clair et observer les changements dans le message chiffré.

Correction de l’exercice 3

message_clair = 0x0123456789ABCDEF
K = 0x133457799BBCDFF1
# DES(M, K)
C1 = des_chiffrement(message_clair, K)
print(f"\nDES(M, K)      = {C1:016X}")
message_clair_modifie = 0x012345A789ABCDEF
C2 = des_chiffrement(message_clair_modifie, K)
print(f"\nDES(M, K)      = {C2:016X}")
#DES(M, K)      = 85E813540F0AB405
#DES(M, K)      = 6C1694019AD6841A

Exercice 4 : Implémentation du déchiffrement

Adapter la fonction des_chiffrement pour réaliser le déchiffrement (utiliser les sous-clés en ordre inverse).

Correction de l’exercice 4

# ============================================================
# DÉCHIFFREMENT DES
# ============================================================
def des_dechiffrement(message_chiffre_64bits, cle_64bits):
    """
    Déchiffre un bloc de 64 bits avec DES
    """
    IP = [58, 50, 42, 34, 26, 18, 10, 2,
          60, 52, 44, 36, 28, 20, 12, 4,
          62, 54, 46, 38, 30, 22, 14, 6,
          64, 56, 48, 40, 32, 24, 16, 8,
          57, 49, 41, 33, 25, 17, 9, 1,
          59, 51, 43, 35, 27, 19, 11, 3,
          61, 53, 45, 37, 29, 21, 13, 5,
          63, 55, 47, 39, 31, 23, 15, 7]
    
    IP_INV = [40, 8, 48, 16, 56, 24, 64, 32,
              39, 7, 47, 15, 55, 23, 63, 31,
              38, 6, 46, 14, 54, 22, 62, 30,
              37, 5, 45, 13, 53, 21, 61, 29,
              36, 4, 44, 12, 52, 20, 60, 28,
              35, 3, 43, 11, 51, 19, 59, 27,
              34, 2, 42, 10, 50, 18, 58, 26,
              33, 1, 41, 9, 49, 17, 57, 25]
    
    message_permute = appliquer_permutation(message_chiffre_64bits, IP, 64)
    
    L = (message_permute >> 32) & 0xFFFFFFFF
    R = message_permute & 0xFFFFFFFF
    
    sous_cles = generer_sous_cles(cle_64bits)
    
    # Rounds en ordre inverse
    for i in range(15, -1, -1):
        L_old, R_old = L, R
        L = R_old
        R = L_old ^ fonction_feistel(R_old, sous_cles[i])
    
    bloc_pre_final = (R << 32) | L
    message_clair = appliquer_permutation(bloc_pre_final, IP_INV, 64)
    
    return message_clair

print("=" * 60)
print("TEST DE L'IMPLÉMENTATION PÉDAGOGIQUE DU DES")
print("=" * 60)

# Exemple du standard DES
message = 0x0123456789ABCDEF
cle = 0x133457799BBCDFF1

print(f"\nMessage original : {message:016X}")
print(f"Clé              : {cle:016X}")

chiffre = des_chiffrement(message, cle)
print(f"\nMessage chiffré  : {chiffre:016X}")

dechiffre = des_dechiffrement(chiffre, cle)
print(f"Message déchiffré: {dechiffre:016X}")

if message == dechiffre:
    print("\n✓ SUCCÈS: Le déchiffrement fonctionne!")
else:
    print("\n✗ ÉCHEC: Problème dans l'implémentation")