Steganographie LSB avec Python et C

Steganographie LSB avec Python et C #

Les explications générales sur la stéganographie LSB sont disponibles ici : Stéganographie LSB

Dans ce TP, nous allons non seulement aborder la méthode de stéganographie LSB, mais aussi voir de quelle façon il est possible de réaliser un programme à la fois avec Python et C, en tirant partie des avantages des deux langages :

  • Python permet de développer très rapidement, mais est lent.
  • C est très rapide à l’exécution, mais plus délicat à utiliser, et le développement est souvent plus long.

Nous allons donc réaliser un programme Python qui peut charger et afficher une image (nous utiliserons une bibliothèque pour cela). Puis, depuis le programme Python, nous appellerons une fonction C qui transforme une image en utilisant la méthode LSB. Le langage C sera donc réservé au calcul des pixels de l’image, qui est typiquement le genre de calculs pour lesquels Python est lent (si on n’utilise que du Python standard).

Il n’est pas rare que lors de la réalisation d’une application, une phase de prototypage soit nécessaire(nbsp): on teste différents algorithmes, les fonctionnalités essentielles etc….. Cette phase de développement doit être rapide et il doit être facile de tester plusieurs algorithmes. Pour cette phase Python est un langage idéal.

Puis lors de la phase de développement du produit, il faudra optimiser le code de manière à le rendre efficace. Parfois le langage utilisé lors de cette phase n’est pas le même que celui utilisé lors de la phase de prototypage. La gros avantage de Python et C et qu’une fois le prototype réalisé, nous pouvons nous contenter de réécrire en C les parties les plus sensibles uniquement, et parvenir ainsi plus rapidement au produit fini.

C’est exactement ce que nous allons faire ici :

  1. réalisation d’un prototype en Python
  2. optimisation du cœur du code par sa réécriture en C

Réalisation du prototype Python #

Nous travaillerons avec des petites images :

Pour manipuler les images, consultez la première section de la documentation Tutoriel images

Téléchargez la première image (kangourou) et réalisez un programme qui la charge et l'affiche. Testez.

Ajoutez une fonction qui prend l’image en paramètre, ainsi que le nombre de bits de poids faible de l’image finale à conserver et renvoie ce résultat. Puis, en dehors de la fonction bien sûr, affichez le résultat.

Vous devez écrire une version non vectorisée de la fonction, c’est à dire une version avec deux boucles for imbriquées pour parcourir les pixels. En utilisant les possibilités de calcul vectoriel de numpy, vous fausserez la comparaison. Toutefois, vous pouvez en plus écrire une version vectorisée pour la comparer avec la version Python standard et la version C (que vous écrirez un peu plus tard).

Avec la première image, vous devriez reconnaître un pingouin (si vous conservez le bon nombre de bits significatifs).

Limitations de la version Python #

Le module time contient la fonction time() qui renvoie le nombre de secondes écoulées depuis l'Epoch. Il est utile pour mesurer des temps de calcul (un peu à la manière de tic() et toc() avec Matlab), si on n’a pas besoin d’une grande précision (les résultats sont en effet faussés par les autres «activités» de la machine).

import time

t1 = time.time()
# Calcul très long
for i in range(100): 
    print(i,i**2, i**3, i**4, i**5)
t2 = time.time()
print('Temps écoulé : ', t2-t1)

Utilisez cette méthode pour mesurer le temps de calcul de l’image cachée, indiquez dans votre rapport à quel endroit vous avez placé les appels à time() (mettez une copie de votre code, que le lecteur puisse savoir ce que vous avez chronométré réellement). Réalisez des mesures pour toutes les images données plus haut. Vérifiez que le temps de calcul varie comme le nombre de pixels de l’image. Estimez le temps de calcul pour une image 3000x2000. Vous présenterez ces résultats sous forme de tableau synthétique.

Si vous avez une version avec boucle et une version vectorisée, faites des mesures pour les 2 et comparez les résultats.

Optimisation des parties critiques #

Nous allons optimiser la partie calcul. Pour cela, le calcul doit idéalement être isolé dans une fonction Python qui prend en paramètre l’image de départ, le nombre de bits de poids faible à conserver et renvoie l’image résultante.

Si vous n’avez pas une telle fonction dans votre code…. reprenez le.

La fonction C que nous allons écrire prendra en paramètres l’image de départ (un tableau d’octets), la largeur de l’image, la hauteur, le nombre de bits de poids faible à conserver et l’image d’arrivée (initialement vide, mais la zone mémoire sera prête).

Une fois cette fonction réalisée, nous ferons une fonction Python qui utilisera cette fonction C et devra avoir le même prototype que les fonctions python déjà faites (celle avec boucles, et éventuellement celle vectorisée).

Commencez par lire le tutoriel sur l’utilisation de ctypes (il y a un exemple sur les images à la fin, mais il faudra tout lire…) : Utilisation de Ctypes

Refaites les mesures déjà réalisées, mais en utilisant cette fois la fonction C (appel depuis Python). Réglez les options de compilation de bibliothèque C en sélectionnant l’optimisation pour la vitesse (-O2). Mesurez le gain de temps (par combien le temps de calcul est-il divisé, ce facteur est il constant lorsque la taille de l’image varie ?). Pensez à indiquer vous avez mis les time.time() pour faire vos mesures. Utilisez votre programme sur l’image donnée ci-dessous (3000x2000 pixels). Parvenez-vous à identifier l’objet caché ?