Consignes
Le rendu de ce TP se fera uniquement par TPLab et consistera en une archive (zip, tar, etc.) contenant votre code et impérativement un fichier README.
Attention :
-
README doit être un fichier texte contenant les réponses aux questions du TP et vos commentaires / remarques / explications pertinentes, ainsi que le degré d'avancement dans le TP,
-
votre code (Python, C, Java) doit pouvoir être testé sous Linux. N'oubliez pas d'inclure la procédure à suivre pour tester vos programmes. Si vous faites du C / C++, fournissez un fichier Makefile pour la compilation.
Si vous utilisez des bibliothèques non-standard, n'oubliez pas de les lister et d'expliquer comment les installer.
Tout non respect d'une ou plusieurs de ces consignes entrainera automatiquement un retrait de points sur votre note !
Langage de programmation
Le langage de programmation est libre mais vous devez vérifier avec l'encadrant si vous souhaitez utiliser autre chose que C, C++, Java, Python ou Javascript.
Par rapport au TP1, ce TP ne nécessite pas de calcul intensif. Ma version a été programmée en Python...
Liens utiles
-
encadrant de TP : Pierre.Hyvernat@univ-smb.fr,
-
un exemple d'image de fond pour un diplome : au format png ou au format ppm,
-
une archive contenant toutes les images du sujet, si vous voulez regarder les détails : images.zip
-
voici les bibliothèques Python que j'ai utilisé :
Mon environnement de développement était généré avec
virtualenv VirtualEnv -p /usr/bin/python3 . ./VirtualEnv/bin/activate pip install Pillow pip install pycryptodome pip install qrcode pip install bottle
Description du sujet
L'université NIHCAMCURT souhaite créer un service de création de diplômes numériques : les étudiants qui réussissent leurs master pourront récupérer un diplôme du genre :
Ce diplôme sera constitué d'une image avec les caractéristiques suivantes :
-
il contiendra une information variable visible : au minimum, le nom de l'étudiant ainsi que sa moyenne,
-
il contiendra au moins une information cachée par stéganographie,
-
il contiendra une signature venant de l'université NIHCAMCURT (de manière cachée ou visible).
L'objectif du TP est de fournir un prototype pour les outils nécessaires à ce service afin d'en vérifier la faisabilité.
Vous devrez, en plus du prototype, produire un petit document (en plus du fichier README obligatoire) de compte-rendu. Il décrira notamment :
-
les choix fonctionnels faits (choix de l'information cachée, présentation des informations "techniques", ...) et leur justification,
-
les choix technologiques faits (langage, algorithme de signature, bibliothèques) et recommandations pour le produit final,
-
description "haut niveau" de votre solution,
-
documentation de vos programmes / fonctions / scripts, ...
-
conclusion sur la faisabilité et l'intérêt du service.
Notes sur la notation
Ce TP sera noté en mode "mini projet". Le barème (provisoire et sujet à modification) devrait ressembler à :
-
avancement "technique" sur les points obligatoires : 5 points,
-
code : 5 points,
-
qualité (sur le fond plus que la forme) du fichier de compte rendu : 5 points,
-
originalité et intérêt des idées proposées : 3 points,
-
fonctionnalités supplémentaires implémentées : 2 points.
1. Stéganographie
1.1. Description
Le premier point obligatoire du TP est la partie stéganographie dans une image. Nous allons pour cela utiliser une technique classique et simple (mais assez mauvaise) qui consiste à cacher de l'information dans les bits de poids faibles des pixels d'une image ("LSB steganography").
Par exemple, le message
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
est caché dans l'image centrale.
image originale | image avec message caché | pixels modifiés |
La technique est assez simple : la composante "rouge" des pixels de l'image est modifiée pour contenir un bit du message à caché. Plus précisément, le bit de poids faible de la composante "rouge" des pixels contient les bits du message.
La différence entre des pixels ayant uniquement leur bit de poids faible différent est indécelable à l'œil nu :
couleur #abcdef | couleur #abcdee |
Si on accepte une perte de qualité, on peut utiliser 2, 3, ou même les 4 bits de poids faible. La seconde image ci dessous n'est pas très bonne, mais reste reconnaissable. Ses pixels cachent, dans leurs 4 bits de poids faible, une version "compressée" de l'image de gauche !
image à cacher | image originale, avec l'image précédente intégrée dans les bits de poids faible | pixels modifiés | image extraite |
La différence de couleur dans ce cas est plus visible :
couleur #abcdef | couleur #a0c0e0 |
Dans l'exemple ci dessus, la différence entre l'image originale et celle avec des données cachées est visible essentiellement sur les zones unies (ciel et facade).
Voici le résultat si on utilise une image moins régulière :
1.2. Pour le TP
Écrivez un programme qui permet de cacher un message dans une image et de la récupérer.
N'oubliez pas de tester votre programme et d'inclure des exemples dans votre archive.
-
Attention, pour cette version naive de stéganographie, il est important d'utiliser un format d'image sans perte. En particulier, le format JPEG n'est pas utilisable...
Je vous conseille le format PNG. (Le format .ppm est aussi utilisable et a l'avantage de ne pas nécessiter de librairie...)
-
Pour simplifier dans un premier temps, vous pouvez avoir un paramètre
nb_bytes
à votre fonction pour récupérer le message caché. Sans cela, il vous faudra trouver un moyen de cacher la taille du message avec le message lui même.
Pour ceci, vous pouvez utiliser une bibliothèque de manipulation d'image. Vous ne pouvez par contre pas utiliser de bibliothèque de stéganographie !
Mon programme a été écrit en Python en utilisant la bibliothèque Pillow (fork de la bibliothèque PIL : Python Imaging Library).
Pour vous donnez une idée, voici un programme qui inverse la couleurs des pixels sur la moitié inférieure d'une image :
from PIL import Image
def invert_half(img):
m = img.height // 2 # milieu de l'image
pixels = img.load() # tableau des pixels
for y in range(m, img.height):
for x in range(0, img.width):
r, g, b = pixels[x, y] # on récupère les composantes RGB du pixel (x,m)
r = r ^ 0b11111111 # on les inverse bit à bit avec un XOR
g = g ^ 0b11111111 # ...
b = b ^ 0b11111111 # ...
pixels[x, y] = r, g, b # on remet les pixels inversés dans le tableau
def main(filename, output):
img = Image.open(filename) # ouverture de l'image contenue dans un fichier
invert_half(img)
img.save(output) # sauvegarde de l'image obtenue dans un autre fichier
if __name__ == "__main__":
import sys
if len(sys.argv) != 3:
print("usage: {} image output".format(sys.argv[0]))
sys.exit(1)
main(sys.argv[1], sys.argv[2])
On obtient
$ python3 test1.py chablais-orig.png chablais-test1.png
image originale | image obtenue |
2. Signature de données
2.1. Description
La signature de données électroniques se fait avec un mécanisme de clé publique / clé privée :
-
la clé privée sert à signer un message (ou son empreinte),
-
la clé publique sert à vérifier une signature.
Le diplôme sécurisé doit contenir une signature afin qu'un utilisateur malveillant ne puisse pas créer un diplôme frauduleux.
La clé privée doit donc être créée par vous (représentant l'université NIHCAMCURT) et devrait rester secrète.
Plutôt que signer un fichier, les signatures valident en général son empreinte, calculée avec une fonction de hachage sure.
2.2. Pour le TP
-
Créez une paire clé publique / clé privée pour pouvoir signer des données,
-
écrivez un programme / script pour signer des données avec cette clé privée,
-
écrivez un programme / script pour vérifier la validité d'une signature vis à vis de données existantes.
Attention, n'oubliez pas d'inclure vos clés dans l'archive du TP et de justifier les choix faits.
N'oubliez pas de tester votre programme et d'inclure des exemples dans votre archive.
Il existe des outils (GnuPG, OpenSSL) qui permettent facilement de chiffrer / signer des documents en ligne de commande. Il est donc possible d'écrire un petit script (en shell ou en Python avec le module subprocess) utilisant ces outils.
Ceci a l'avantage de la facilité, mais rend l'intégration dans une solution finale plus complexe. (Pour cette raison, les outils avec interface graphique ne sont pas autorisés.)
Je vous donne ci dessous un exemple de session shell permettant de créer les clés, signer et vérifier une signature, mais je vous conseille d'utiliser une bibliothèque dédiée dans un vrai langage de programmation. (J'ai par exemple utilisé la bibliothèque PyCryptodome en Python.)
$ # génération de la clée privée
$ openssl genrsa -aes256 -out .cle_privee.pem 4096
Generating RSA private key, 4096 bit long modulus
.......................................++++
....................++++
e is 65537 (0x010001)
Enter pass phrase for .cle_privee:
Verifying - Enter pass phrase for .cle_privee:
$ # génération de la clée publique à partir de la clé privée
openssl rsa -in .cle_privee.pem -pubout -out cle_publique.pem
Enter pass phrase for .cle_privee.pem:
writing RSA key
$ # signature du fichier shannon.png
$ openssl dgst -sha256 -sign .cle_privee.pem -out shannon.png.sig shannon.png
Enter pass phrase for .cle_privee.pem:
$ # vérification de la signature
$ openssl dgst -sha256 -verify cle_publique.pem -signature shannon.png.sig shannon.png
Verified OK
3. Création du diplôme
3.1. Image et texte
Écrivez une fonction qui génère un diplôme simple (sans composant cryptographique) en utilisant une image de fond (celle ci ou une autre de votre choix).
Cette fonction permet essentiellement d'insérer du texte (variable) sur un fond (constant).
N'oubliez pas de tester votre programme et d'inclure des exemples dans votre archive.
N'hésitez pas à utiliser une bibliothèque pour faire ceci. Pour vous donnez une idée, voici un programme qui ajoute un mot au milieu d'une image avec Pillow... (N'oubliez pas de télécharger la police sans.ttf pour tester ce programme.)
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
def add_text(img, text, x, y):
draw = ImageDraw.Draw(img) # objet "dessin" dans l'image
font = ImageFont.truetype("sans.ttf", 32) # police à utiliser
draw.text((x, y), text, "white", font) # ajout du texte
def main(filename, text, output):
img = Image.open(filename) # ouverture de l'image contenue dans un fichier
add_text(img, text, 110, 110)
img.save(output) # sauvegarde de l'image obtenue dans un autre fichier
if __name__ == "__main__":
import sys
if len(sys.argv) != 4:
print("usage: {} image msg output".format(sys.argv[0]))
sys.exit(1)
main(sys.argv[1], sys.argv[2], sys.argv[3])
On obtient
$ python3 test2.py chablais-orig.png "NIHCAMCURT" chablais-test2.png
image originale | image obtenue |
3.2. Intégration de la stéganographie et signature
Choisissez :
-
une information à signer
-
une information à cacher (signature, avatar, date, ...)
et écrivez une fonction principale qui prend en argument :
-
le nom de l'étudiant,
-
sa moyenne
et génère le diplôme.
Cette fonction utilisera bien entendu ce que vous avez fait dans les questions précédentes...
N'oubliez pas de tester votre programme et d'inclure des exemples dans votre archive.
Voici une représentation des bits différents entre le diplôme que je génère et l'image vierge.
Comme vous pouvez le constater, 3 informations (dont une assez grosse) sont cachées dans le diplôme...
Écrivez une fonction qui permet d'extraire l'information cachée dans un diplôme.
Décrivez une manière de vérifier la signature.
N'oubliez pas de tester votre programme et d'inclure des exemples dans votre archive.
Si la signature est visible, la vérification de signature doit pouvoir être faite par l'utilisateur. Dans ce cas, vous devez soit fournir un programme spécifique, soit décrire comme faire la vérification en utilisant des outils existants.
4. Pour aller plus loin
Voici quelques idées pour améliorer vos diplômes.
- Validation en ligne
-
ajout d'un site de validation des diplômes hébergé par NIHCAMCURT. Cela peut se faire en entrant un code ou une empreinte (qu'il faut alors ajouter sur le diplôme) ou avec le nom de l'étudiant.
Une manière simple simple pour faire un prototype en Python est d'utiliser bottle.
- QR-code
-
ajout d'un QR-code contenant un lien vers un site de validation (cf. idée précédente).
La bibliothèque python-qrcode permet par exemple de créer des QR-codes en Python...
- Horodatage certifié
-
Ajout d'une estampille certifiée permettant une vérification, par un tiers de confiance, de la validité du diplome ainsi que de la date de délivrance.
Pour ceci, il faut créer une "requête d'horodatage" (RFC 3161) que l'on envoie à un serveur d'horodatage (https://www.freetsa.org/ par exemple). Le serveur nous renvoie une réponse qui permet de vérifier la date d'enregistrement de la requête.
$ # création d'une requête d'horodatage (fichier .tsq) openssl ts -query -data shannon.png -sha256 -out shannon.png.tsq $ $ # envoie de la requête au serveur www.freetsa.org et récupération de la réponse curl -H "Content-Type: application/timestamp-query" \ --data-binary '@shannon.png.tsq' https://freetsa.org/tsr \ > shannon.png.tsr $
La première étape crée un fichier shannon.png.tsq à partir du fichier shannon.png. La deuxième fait une requête POST avec ce fichier au serveur d'horodatage et enregistre la réponse dans le fichier shannon.png.tsr.
Vous pouvez ensuite vérifier l'horodatage à partir de https://www.freetsa.org/index_en.php#online, onglet "Verify", ou bien à partir de la ligne de commande
$ openssl ts -verify -in shannon.png.tsr -queryfile shannon.png.tsq \ -CAfile cacert.pem -untrusted tsa.crt
(Il faut pour ceci avoir téléchargé le certificat "autorité de certification" de Freetsa ainsi que le certificat d'autorité d'horodatage de Freetsa)
- ???
-
N'hésitez pas à proposer d'autres améliorations...