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 :

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

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 :

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 :

Notes sur la notation

Ce TP sera noté en mode "mini projet". Le barème (provisoire et sujet à modification) devrait ressembler à :

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.

TP2/chablais-orig.png TP2/chablais-lorem.png TP2/chablais-lorem-diff.png
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 :

TP2/abcdef.gif TP2/abcdee.gif
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 !

TP2/friedman-orig.png TP2/chablais-friedman.png TP2/chablais-friedman-diff.png TP2/friedman-result.png
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 :

TP2/abcdef.gif TP2/a0c0e0.gif
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 :

TP2/arbre-orig.png TP2/arbre-friedman.png

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.

  1. 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...)

  2. 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
TP2/chablais-orig.png TP2/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 :

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

  1. Créez une paire clé publique / clé privée pour pouvoir signer des données,

  2. écrivez un programme / script pour signer des données avec cette clé privée,

  3. é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
TP2/chablais-orig.png TP2/chablais-test2.png
image originale image obtenue

3.2. Intégration de la stéganographie et signature

Choisissez :

et écrivez une fonction principale qui prend en argument :

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...