1. Fonctions de Base du Chiffrement Symétrique
Le chiffrement symétrique utilise une seule clé pour chiffrer et déchiffrer les données.
Un exemple moderne populaire est l'AES (Advanced Encryption Standard).
Démonstration simplifiée de S-AES
S-AES (Simplified AES) en est une version allégée pour l'apprentissage :
S-AES ne devrait jamais être utilisé en production.
Blocs/clés de 16 bits (vs 128+ bits pour AES)
2 tours seulement (vs 10+)
Opérations simplifiées (S-Box 4 bits, calcul de clés réduit)
S-AES est utilisé afin de comprendre la structure des tours, la confusion/diffusion et les opérations en corps fini sans la complexité d'AES.
# S-AES simplifié - version pédagogique sur 16 bits
# S-Box et inverse (nibbles 4 bits)
SBOX = [
0x9, 0x4, 0xA, 0xB,
0xD, 0x1, 0x8, 0x5,
0x6, 0x2, 0x0, 0x3,
0xC, 0xE, 0xF, 0x7
]
INV_SBOX = [
0xA, 0x5, 0x9, 0xB,
0x1, 0x7, 0x8, 0xF,
0x6, 0x0, 0x2, 0x3,
0xC, 0x4, 0xD, 0xE
]
def nibble_substitution(nibble, sbox):
return sbox[nibble]
def s_aes_subnibbles(state, inverse=False):
sbox = INV_SBOX if inverse else SBOX
return [nibble_substitution(b, sbox) for b in state]
def s_aes_shiftrows(state):
# 16-bit state: [w0, w1, w2, w3] → w0,w1,w2,w3
# Shiftrows: w0,w1,w2,w3 → w0,w1,w3,w2
return [state[0], state[1], state[3], state[2]]
def s_aes_mixcolumns(state, inverse=False):
# Matrice : [[1, 4], [4, 1]] dans GF(16)
# Polynôme irréductible : x^4 + x + 1 → 0x13
def gf_mult(a, b):
p = 0
while b:
if b & 1:
p ^= a
a <<= 1
if a & 0x10:
a ^= 0x13 # x^4 + x + 1
b >>= 1
return p & 0xF
if not inverse:
a = gf_mult(4, state[1]) ^ state[0]
b = gf_mult(4, state[0]) ^ state[1]
c = gf_mult(4, state[3]) ^ state[2]
d = gf_mult(4, state[2]) ^ state[3]
else:
# Matrice inverse : [[9, 2], [2, 9]]
a = (gf_mult(9, state[0]) ^ gf_mult(2, state[1]))
b = (gf_mult(2, state[0]) ^ gf_mult(9, state[1]))
c = (gf_mult(9, state[2]) ^ gf_mult(2, state[3]))
d = (gf_mult(2, state[2]) ^ gf_mult(9, state[3]))
return [a, b, c, d]
def int_to_state(x):
# 16-bit → [nibble0, nibble1, nibble2, nibble3]
return [
(x >> 12) & 0xF,
(x >> 8) & 0xF,
(x >> 4) & 0xF,
x & 0xF
]
def state_to_int(state):
return (state[0] << 12) | (state[1] << 8) | (state[2] << 4) | state[3]
def key_expansion(key16):
# key16: int 16 bits
w = [0] * 6
w[0] = (key16 >> 8) & 0xFF
w[1] = key16 & 0xFF
RCON = {2: 0x80, 4: 0x30}
for i in range(2, 6):
temp = w[i-1]
if i % 2 == 0:
# SubWord + RotWord + Rcon
temp = ((temp & 0x0F) << 4) | ((temp & 0xF0) >> 4) # RotWord
temp = (nibble_substitution(temp >> 4, SBOX) << 4) | (nibble_substitution(temp & 0x0F, SBOX))
temp ^= RCON[i]
w[i] = w[i-2] ^ temp
key0 = (w[0] << 8) | w[1]
key1 = (w[2] << 8) | w[3]
key2 = (w[4] << 8) | w[5]
return key0, key1, key2
def s_aes_encrypt_block(plaintext, key16):
# plaintext, key16: int (16 bits)
keys = key_expansion(key16)
state = int_to_state(plaintext)
# Round 0: AddRoundKey
state = [state[i] ^ ((keys[0] >> (4*(3-i))) & 0xF) for i in range(4)]
# Round 1
state = s_aes_subnibbles(state)
state = s_aes_shiftrows(state)
state = s_aes_mixcolumns(state)
state = [state[i] ^ ((keys[1] >> (4*(3-i))) & 0xF) for i in range(4)]
# Round 2
state = s_aes_subnibbles(state)
state = s_aes_shiftrows(state)
state = [state[i] ^ ((keys[2] >> (4*(3-i))) & 0xF) for i in range(4)]
return state_to_int(state)
def s_aes_decrypt_block(ciphertext, key16):
keys = key_expansion(key16)
state = int_to_state(ciphertext)
# Round 2 inverse
state = [state[i] ^ ((keys[2] >> (4*(3-i))) & 0xF) for i in range(4)]
state = s_aes_shiftrows(state)
state = s_aes_subnibbles(state, inverse=True)
# Round 1 inverse
state = [state[i] ^ ((keys[1] >> (4*(3-i))) & 0xF) for i in range(4)]
state = s_aes_mixcolumns(state, inverse=True)
state = s_aes_shiftrows(state)
state = s_aes_subnibbles(state, inverse=True)
# Round 0
state = [state[i] ^ ((keys[0] >> (4*(3-i))) & 0xF) for i in range(4)]
return state_to_int(state)
# ========================
# EXEMPLE D'UTILISATION
# ========================
if __name__ == "__main__":
# Clé S-AES : 16 bits (ex: 0b1010001110110100 = 0xA3B4)
KEY_16BIT = 0xA3B4 # Tu peux changer cette clé
# Message : b'hi' → 'h' = 0x68, 'i' = 0x69 → 0x6869
message = b'hi'
plaintext_int = (message[0] << 8) | message[1] # 0x6869
print(f"Message clair : {message}")
print(f"En hexa : 0x{plaintext_int:04X}")
# Chiffrement
ciphertext = s_aes_encrypt_block(plaintext_int, KEY_16BIT)
print(f"Chiffré : 0x{ciphertext:04X} (hex)")
# Déchiffrement
decrypted_int = s_aes_decrypt_block(ciphertext, KEY_16BIT)
decrypted_bytes = bytes([(decrypted_int >> 8) & 0xFF, decrypted_int & 0xFF])
print(f"Déchiffré : {decrypted_bytes}")
# Vérification
if decrypted_bytes == message:
print("✅ Succès : le déchiffrement a bien retrouvé le message d'origine !")
else:
print("❌ Échec du déchiffrement.")
Message clair : b'hi'
En hexa : 0x6869
Chiffré : 0x650C (hex)
Déchiffré : b'hi'
2. Attaques sur S-AES
Attaques sur S-AES
Deux attaques concrètes sur un chiffrement simplifié :
- Attaque par dictionnaire : exploitation de clés faibles ou prévisibles pour retrouver la clé secrète à partir d'un morceau connu du message.
-
Analyse de motifs : détection de blocs répétés dans le chiffré, révélant des structures du message d'origine.
Ces attaques montrent que même un bon algorithme devient vulnérable avec une mauvaise clé ou un mauvais mode d'usage — une leçon clé en sécurité réelle.
# -*- coding: utf-8 -*-
"""
S-AES Simplifié - avec attaques
==============================================
Version autonome avec toutes les fonctions nécessaires intégrées
"""
# ======================
# IMPORTS ET CONFIGURATION
# ======================
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import os
# ======================
# FONCTIONS S-AES CORE
# ======================
# S-Box et inverse
SBOX = [
0x9, 0x4, 0xA, 0xB,
0xD, 0x1, 0x8, 0x5,
0x6, 0x2, 0x0, 0x3,
0xC, 0xE, 0xF, 0x7
]
INV_SBOX = [
0xA, 0x5, 0x9, 0xB,
0x1, 0x7, 0x8, 0xF,
0x6, 0x0, 0x2, 0x3,
0xC, 0x4, 0xD, 0xE
]
def nibble_substitution(nibble, sbox):
return sbox[nibble]
def s_aes_subnibbles(state, inverse=False):
sbox = INV_SBOX if inverse else SBOX
return [nibble_substitution(b, sbox) for b in state]
def s_aes_shiftrows(state):
return [state[0], state[1], state[3], state[2]]
def gf_mult(a, b):
"""Multiplication dans GF(2^4)"""
p = 0
while b:
if b & 1:
p ^= a
a <<= 1
if a & 0x10:
a ^= 0x13
b >>= 1
return p & 0xF
def s_aes_mixcolumns(state, inverse=False):
if not inverse:
return [
gf_mult(4, state[1]) ^ state[0],
gf_mult(4, state[0]) ^ state[1],
gf_mult(4, state[3]) ^ state[2],
gf_mult(4, state[2]) ^ state[3]
]
else:
return [
gf_mult(9, state[0]) ^ gf_mult(2, state[1]),
gf_mult(2, state[0]) ^ gf_mult(9, state[1]),
gf_mult(9, state[2]) ^ gf_mult(2, state[3]),
gf_mult(2, state[2]) ^ gf_mult(9, state[3])
]
def int_to_state(x):
return [(x >> 12) & 0xF, (x >> 8) & 0xF, (x >> 4) & 0xF, x & 0xF]
def state_to_int(state):
return (state[0] << 12) | (state[1] << 8) | (state[2] << 4) | state[3]
def key_expansion(key16):
w = [0] * 6
w[0] = (key16 >> 8) & 0xFF
w[1] = key16 & 0xFF
RCON = {2: 0x80, 4: 0x30}
for i in range(2, 6):
temp = w[i - 1]
if i % 2 == 0:
temp = ((temp & 0x0F) << 4) | ((temp & 0xF0) >> 4)
temp = (SBOX[temp >> 4] << 4) | SBOX[temp & 0x0F]
temp ^= RCON[i]
w[i] = w[i - 2] ^ temp
return [(w[0] << 8) | w[1], (w[2] << 8) | w[3], (w[4] << 8) | w[5]]
def s_aes_encrypt_block(plaintext, key16):
keys = key_expansion(key16)
state = int_to_state(plaintext)
# Round 0
state = [state[i] ^ ((keys[0] >> (4 * (3 - i))) & 0xF) for i in range(4)]
# Round 1
state = s_aes_subnibbles(state)
state = s_aes_shiftrows(state)
state = s_aes_mixcolumns(state)
state = [state[i] ^ ((keys[1] >> (4 * (3 - i))) & 0xF) for i in range(4)]
# Round 2
state = s_aes_subnibbles(state)
state = s_aes_shiftrows(state)
state = [state[i] ^ ((keys[2] >> (4 * (3 - i))) & 0xF) for i in range(4)]
return state_to_int(state)
def s_aes_decrypt_block(ciphertext, key16):
keys = key_expansion(key16)
state = int_to_state(ciphertext)
# Round 2 inverse
state = [state[i] ^ ((keys[2] >> (4 * (3 - i))) & 0xF) for i in range(4)]
state = s_aes_shiftrows(state)
state = s_aes_subnibbles(state, inverse=True)
# Round 1 inverse
state = [state[i] ^ ((keys[1] >> (4 * (3 - i))) & 0xF) for i in range(4)]
state = s_aes_mixcolumns(state, inverse=True)
state = s_aes_shiftrows(state)
state = s_aes_subnibbles(state, inverse=True)
# Round 0
state = [state[i] ^ ((keys[0] >> (4 * (3 - i))) & 0xF) for i in range(4)]
return state_to_int(state)
# ======================
# FONCTIONS POUR MESSAGES LONGS
# ======================
def preparer_message(message):
"""Ajoute un padding si nécessaire pour un nombre pair d'octets"""
return message if len(message) % 2 == 0 else message + b'\x00'
def chiffrer_message(message, cle):
"""Chiffre un message de taille quelconque"""
message = preparer_message(message)
texte_chiffre = b''
for i in range(0, len(message), 2):
bloc = message[i:i + 2]
bloc_entier = (bloc[0] << 8) | bloc[1]
bloc_chiffre = s_aes_encrypt_block(bloc_entier, cle)
texte_chiffre += bytes([(bloc_chiffre >> 8) & 0xFF, bloc_chiffre & 0xFF])
return texte_chiffre
def dechiffrer_message(texte_chiffre, cle):
"""Déchiffre un message de taille quelconque"""
message = b''
for i in range(0, len(texte_chiffre), 2):
bloc = texte_chiffre[i:i + 2]
bloc_entier = (bloc[0] << 8) | bloc[1]
bloc_dechiffre = s_aes_decrypt_block(bloc_entier, cle)
message += bytes([(bloc_dechiffre >> 8) & 0xFF, bloc_dechiffre & 0xFF])
# Retire le padding final si présent
return message[:-1] if message[-1:] == b'\x00' else message
# ======================
# ATTAQUES CRYPTOGRAPHIQUES
# ======================
def attaque_par_dictionnaire(texte_chiffre, texte_clair_connu):
"""Tente de retrouver la clé par force brute"""
print("\n🔎 Attaque par dictionnaire en cours...")
cles_possibles = [0x0000, 0xFFFF, 0x1234, 0xA3B4, 0x4242]
for cle in cles_possibles:
resultat = s_aes_encrypt_block(texte_clair_connu, cle)
if resultat == texte_chiffre:
print(f"✅ Clé trouvée: 0x{cle:04X}")
return cle
print("❌ Aucune clé valide trouvée")
return None
def analyser_motifs(texte_chiffre):
"""Détecte les répétitions dans le texte chiffré"""
print("\n🔍 Analyse des motifs...")
blocs = [texte_chiffre[i:i + 2] for i in range(0, len(texte_chiffre), 2)]
for i in range(len(blocs)):
for j in range(i + 1, len(blocs)):
if blocs[i] == blocs[j]:
print(f"Motif répété détecté: bloc {i} et {j} identiques")
# ======================
# DÉMONSTRATION PRINCIPALE
# ======================
if __name__ == "__main__":
# Configuration
CLE_SECRETE = 0xA3B4
MESSAGE = b"Bonjour le monde !"
print("\n" + "=" * 50)
print(" DÉMONSTRATION S-AES - MESSAGES LONGS ")
print("=" * 50)
# 1. Chiffrement
print(f"\n🔒 Message original: {MESSAGE.decode()}")
message_chiffre = chiffrer_message(MESSAGE, CLE_SECRETE)
print(f"Texte chiffré ({len(message_chiffre)} octets):")
print(" ".join(f"{b:02X}" for b in message_chiffre))
# 2. Déchiffrement normal
print("\n=== DÉCHIFFREMENT LÉGITIME ===")
message_dechiffre = dechiffrer_message(message_chiffre, CLE_SECRETE)
print(f"Résultat: {message_dechiffre.decode()}")
print(f"Correspondance: {message_dechiffre == MESSAGE}")
# 3. Attaques
print("\n=== ANALYSE CRYPTANALYTIQUE ===")
# Attaque sur le premier bloc (supposant qu'on connaît "Bo")
premier_bloc = (message_chiffre[0] << 8) | message_chiffre[1]
cle_trouvee = attaque_par_dictionnaire(premier_bloc, 0x426F) # 0x426F = "Bo"
# Analyse des motifs
analyser_motifs(message_chiffre)
# 4. Déchiffrement avec clé trouvée
if cle_trouvee:
print("\n=== DÉCHIFFREMENT AVEC CLÉ TROUVÉE ===")
texte_pirate = dechiffrer_message(message_chiffre, cle_trouvee)
print(f"Message déchiffré: {texte_pirate.decode()}")
if texte_pirate == MESSAGE:
print("\n💥 ATTAQUE RÉUSSIE! La clé était correcte.")
else:
print("\n⚠️ Attention: le déchiffrement est incorrect!")
RÉSULTAT:
==================================================
DÉMONSTRATION S-AES - MESSAGES LONGS
==================================================
🔒 Message original: Bonjour le monde !
Texte chiffré (18 octets):
A3 D9 EE E1 9F 68 BF 0D D7 77 A4 FA 9F 5B C4 77 A4 F7
=== DÉCHIFFREMENT LÉGITIME ===
Résultat: Bonjour le monde !
Correspondance: True
=== ANALYSE CRYPTANALYTIQUE ===
🔎 Attaque par dictionnaire en cours...
✅ Clé trouvée: 0xA3B4
🔍 Analyse des motifs...
=== DÉCHIFFREMENT AVEC CLÉ TROUVÉE ===
Message déchiffré: Bonjour le monde !
💥 ATTAQUE RÉUSSIE! La clé était correcte.
Process finished with exit code 0