Introduction
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"
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)
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(car0b101 << 1 = 0b1010).
- Exemple :
- 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(car0b101 >> 1 = 0b10).
- Exemple :
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)
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 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.
# ============================================================
# 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.
-
XORavec 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
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)
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.
Exercice 3 : Impact d’un bit modifié
Modifier un seul bit du message clair et observer les changements dans le message chiffré.
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).