Vous êtes sur la page 1sur 9

Chire de Vernam (Masque jetable / XOR)

R3.09 : Cryptographie et sécurité

TD2

Exercice 1
Pour chirer un message, le chirement de Vernam (ou méthode du masque jetable), consiste à
combiner le message avec une chaîne de caractères de longueur comparable.
Une implémentation possible utilise l'opérateur XOR (ou exclusif) dont voici la table de vérité :
a b a XOR b
0 0 0
0 1 1
1 0 1
1 1 0

Dans la suite, les nombres écrits en binaire seront précédés du préxe 0b.
1. Pour chirer un message, on convertit chacun de ses caractères en binaire (à l'aide du format
Unicode), et on réalise l'opération XOR bit à bit avec la clé.

Après conversion en binaire, et avant que l'opération XOR bit à bit avec la clé n'ait été
eectuée, Alice obtient le message suivant :
m = 0b 0110 0011 0100 0110
(a) Le message m correspond à deux caractères codés chacun sur 8 bits : déterminer quels
sont ces caractères. On fournit pour cela la table ci-dessous qui associe à l'écriture hexadé-
cimale d'un octet le caractère correspondant (gure 2). Exemple de lecture : le caractère
correspondant à l'octet codé 4A en hexadécimal est la lettre J.

gure 2
Solution:

m = 0b 0110 0011 0100 0110 = Ox 63 46 = c F

(b) Pour chirer le message d'Alice, on réalise l'opération XOR bit à bit avec la clé suivante :
k = 0b 1110 1110 1111 0000
Donner l'écriture binaire du message obtenu.

Solution:

m 0 1 1 0 0 0 1 1 0 1 0 0 0 1 1 0
k 1 1 1 0 1 1 1 0 1 1 1 1 0 0 0 0
m XOR k 1 0 0 0 1 1 0 1 1 0 1 1 0 1 1 0

2.(a) Dresser la table de vérité de l'expression booléenne suivante :


(a XOR b) XOR b

Solution:

a b a XOR b (a XOR b) XOR b


0 0 0 0
1 0 1 1
0 1 1 0
1 1 0 1

(b) Bob connaît la chaîne de caractères utilisée par Alice pour chirer le message. Quelle
opération doit-il réaliser pour déchirer son message ?

Solution:
D'après la question précédente : (m XOR k) XOR k = m
Bob doit donc réaliser un XOR entre le message reçu (m XOR k) et la clé k pour obtenir
le message initial m.

Exercice 2
Écrire en Python une fonction une fonction chiffre_xor(msg, cle) qui prend en arguments deux
chaînes d'octets (de type bytes) et qui renvoie le chirement XOR du message avec la clé, sous
forme d'une chaîne d'octets.

Indications :

Page 2
ˆ L'opérateur XOR est noté  en Python.
ˆ Il est possible de créer une chaîne d'octets en utilisant le constructeur bytes et en lui donnant
en argument une liste d'entiers, chacun compris entre 0 et 255. Informations sur le type bytes
ˆ Pour tester, appliquez la méthode encode() sur les string pour les coder en UTF-8.

Solution:

def chiffre_xor(msg, cle):


res = [msg[i] ^ cle[i%len(cle)] for i in range(len(msg))]
print(res)
return bytes(res)

# test
m = "L'INFORMATIQUE C'EST SUPER".encode()
c = "NSI".encode()
s = chiffre_xor(m,c)
print(s)
print(chiffre_xor(s,c))

Exercice 3
Lorsque la longueur de la clé est inférieure à celle du message à chirer, on recopie plusieurs fois
la clé de façon à obtenir une chaîne de même longueur que le message.
Nous allons voir qu'une clé trop courte peut compromettre la sécurité de la communication.
Dans cet exercice, on va montrer qu'en connaissant quelques informations on peut facilement re-
trouver la clé si cette dernière est trop courte.

Soit la chaîne d'octets chirée :


b'\x0e6/+y;.< x-(7\x9b\xf0z48z:646<z*\x9a\xf3(64+<{'
On sait que les 4 derniers caractères du message en clair sont "nse!". On sait aussi que la clé fait
exactement 3 caractères et que ce sont des lettres majuscules sans accent.
Écrire un programme Python, utilisant la fonction chiffre_xor de l'exercice précédent, qui essaye
toutes les combinaisons de clé jusqu'à trouver la bonne. Mesurer le temps d'exécution.

Indication : La fonction time.time() du module time permet de connaître le nombre de seconde


écoulées depuis le premier janvier 1970.

Solution:

Page 3
import time
secret = b'\x0e6/+y;.< x-(7,,\x9b\xf0z48z:646<z*\x9a\xf3(64+<{'

def force():
cA = ord('A')
cZ1 = ord('Z')+1
for c1 in range(cA, cZ1):
for c2 in range(cA, cZ1):
for c3 in range(cA, cZ1):
cle = bytes([c1,c2,c3])
test = chiffre_xor(secret,cle)
if test.endswith(b"nse!"):
return (cle, test)

# test
t1 = time.time()
res = force()
t2 = time.time()
if res is None:
print("Je n'ai pas trouvé la clé")
else:
print("Clé:", res[0])
print("Message décrypté:", res[1])
print("Temps de calcul:", t2-t1, "secondes")

Sur une machine relativement récente, énumérer de cette façon toutes les clés entre AAA, AAB,
. . ., XYZ (qui est la clé) prend moins d'une seconde.
Le message en clair est la chaîne d'octets :
b'Vous avez trouv\xc3\xa9 la bonne r\xc3\xa9ponse!'
La séquence d'octets \xc3\xa9 correspond au caractère é en UTF-8.

Exercice 4 : Chirement d'une image bitmap


Le format PBM est l'un des plus simples pour dénir une image en noir et blanc. Il s'agit d'un
chier texte dont le contenu est :

ˆ sur la première ligne, P1 qui dénit le format ;


ˆ sur la ligne suivante, facultative, un commentaire qui commence par le symbole # ;
ˆ sur la ligne suivante, la largeur et la hauteur de l'image en pixel ;
ˆ sur les lignes suivantes, des 0 et des 1 qui représentent chacun un pixel en blanc ou en noir.

Exemple :

Page 4
P1
# Un exemple bitmap de la lettre "J"
7 10
0 0 0 0 0 0 0
0 0 0 0 0 1 0
0 0 0 0 0 1 0
0 0 0 0 0 1 0
0 0 0 0 0 1 0
0 0 0 0 0 1 0
0 0 0 0 0 1 0
0 1 0 0 0 1 0
0 0 1 1 1 0 0
0 0 0 0 0 0 0

Un chier est enregistré avec l'extension .pbm. Pour visualiser l'image, il sut de l'ouvrir avec un
logiciel comme XnView ou GIMP.

Une image est représentée par une matrice, i.e. une liste de listes en Python. Pour l'exemple
ci-dessus, on obtient : [[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0], ..., 0, 0, 1, 1,
1, 0, 0], 0, 0, 0, 0, 0, 0, 0]]

On dénit la matrice d'une image exemple avec le code suivant :

def matrice(n, p):


m = [[0 for j in range(p)] for i in range(n)]
for i in range(n): # n est la hauteur de l'image
for j in range(p): # p est la largeur de l'image
if (j // 20) % 2 == 0:
m[i][j] = 1
return m

m1 = matrice(240, 180)

1. Écrire une fonction fichier qui prend en paramètres une matrice comme celle dénie ci-
dessus, et une chaîne de caractères représentant le nom d'un chier, sans l'extension.
La fonction fichier crée le chier image, avec l'extension pbm, et y écrit les lignes dénissant
l'image avec les valeurs données par la matrice.
Vérier que l'instruction fichier(m1, "image1") crée un chier image1.pbm et visualiser
l'image avec par exemple le logiciel XnView ou GIMP.
Vous devez obtenir l'image suivante :

Page 5
Solution: La fonction fichier.
def fichier(mat, nom):
hauteur = len(mat)
largeur = len(mat[0])
nom = nom + '.pbm'
f = open(nom, 'w')
f.write('P1\n' + str(largeur) + ' ' + str(hauteur) + '\n')
for i in range(hauteur):
for j in range(largeur):
bit = str(mat[i][j])
f.write(bit + ' ')
f.close()

fichier(m1, "image1")

2. Écrire une fonction hasard qui prend en paramètres les dimensions d'une image n (la hauteur)
et p (la largeur), et renvoie une matrice représentant une image aléatoire. Chaque pixel vaut
0 ou 1 de manière équiprobable.
Créer une image aléatoire avec les dimensions que l'image image1.pbm. Nommer cette image
alea.pbm et visualiser la.

Solution: La fonction hasard. On importe la fonction randint du module random.


L'expression randint(0, 1) prend la valeur aléatoire 0 ou 1.
def hasard(n, p):
from random import randint
m = [[0 for j in range(p)] for i in range(n)]
for i in range(n): # n est la hauteur de l'image
for j in range(p): # p est la largeur de l'image
m[i][j] = randint(0, 1)
return m

a = hasard(240, 180)
fichier(a, "alea")

Page 6
On obtient une image aléatoire comme celle-ci :

3. Écrire une fonction chiffre pour chirer l'image image1.pbm. La fonction prend en para-
mètres une image à chirer et une image clé sous la forme de matrices de mêmes dimensions.
Elle crée une nouvelle image de même dimension. Pour déterminer chaque bit de l'image, on
utilise l'opérateur ou exclusif entre les bits de l'image à chirée et ceux de l'image clé.
L'image clé s'appelle un masque.
Tester la fonction chiffre avec l'image image1.pbm et l'image clé alea.pbm. Visualiser le
résultat.

Comment déchirer l'image chirée ?

Solution: La fonction de chirement.


On dénit une matrice m avec les bonnes dimensions remplies de 0. Ensuite on utilise
l'opérateur ou exclusif entre chaque nombre de la matrice img et le nombre correspondant
de la matrice cle pour obtenir la valeur de m[i][j].
def chiffre(img, cle):
n = len(img)
p = len(img[0])
m = [[0 for j in range(p)] for i in range(n)]
for i in range(n): # n est la hauteur de l'image
for j in range(p): # p est la largeur de l'image
m[i][j] = img[i][j] ^ cle[i][j]
return m

c = chiffre(m1,a)
fichier(c, "image_chiffree")

L'image chirée obtenue ressemble à une image aléatoire.

Page 7
Il s'agit d'un chirement symétrique. Pour déchirer le résultat, on utilise la clé de chif-
frement avec le même programme et on écrit simplement :
d = chiffre(c, a)
fichier(d,"image_dechiffree")

On obtient une image identique à l'image initiale. On peut vérier que le contenu des
deux chiers image1.pbm et image_dechiffree.pbm est exactement le même.

4. Faire une copie de la fonction chiffre de la question précédente, la renommer, et remplacer


l'opérateur ou exclusif par l'opérateur ou. Que peut on dire de l'image obtenue si on exécute
ce programme avec l'image chirée et l'image clé ?

Solution:
On reprend la fonction chiffre qui sert aussi à déchirer et on remplace l'opérateur ou
exclusif par l'opérateur ou.

def devoile(img, cle):


n = len(img)
p = len(img[0])
m = [[0 for j in range(p)] for i in range(n)]
for i in range(n): # n est la hauteur de l'image
for j in range(p): # p est la largeur de l'image
m[i][j] = img[i][j] | cle[i][j]
return m

dv = devoile(c, a)
fichier(dv, "image_devoilee")

On obtient une image dite  dévoilée . Elle laisse apparaître l'image déchirée.

Page 8
Pour travailler avec des images plus motivantes, on peut utiliser une image de son choix au
format jpeg ou png et la convertir au format pbm. Après le chirement on reconvertit l'image
chirée en jpeg ou png.
Pour les images en niveau de gris ou en couleur, les formats avec les mêmes principes que le
pbm sont les formats pgm et ppm. Ces trois formats (P1, P2, P3) utilise le codage ASCII.
Avec un codage brut sous forme d'octets on obtient les formats similaires (P4, P5 et P6),
moins gourmands en taille de stockage.

Page 9

Vous aimerez peut-être aussi