Série 10 – Flots et cryptographie

Introduction

Le masque jetable (one-time pad en anglais) est une technique de chiffrement très facile à mettre en œuvre mais malgré tout inviolable, sous certaines conditions malheureusement difficiles à satisfaire en pratique. Le but de cette série est d'écrire un programme permettant de faire le chiffrage et le déchiffrage de fichiers au moyen d'un masque jetable. Ce faisant, vous utiliserez deux patrons de conception vus au cours.

Pour chiffrer une suite d'octets au moyen de la technique du masque jetable, il faut posséder une autre séquence d'octets — le masque — qui soit aléatoire et ait au moins la longueur des données à chiffrer. Le ie octet chiffré s'obtient simplement en faisant le OU exclusif entre le ie octet du message à chiffrer et le ie octet du masque. Pour déchiffrer une suite d'octets ainsi chiffrée, il suffit de faire la même opération avec le même masque et le tour est joué.

La table ci-dessous illustre la technique avec une séquence de trois octets. Toutes les valeurs sont données en binaire, et on vérifie facilement que chaque octet chiffré est le OU exclusif (bit-à-bit) des octets correspondants de la séquence avant chiffrement et du masque. Et aussi que chaque octet déchiffré est le OU exclusif des octets correspondants de la séquence chiffrée et du masque.

  Octet 1 Octet 2 Octet 3
Avant chiffrement 001010102 010010102 010010012
Masque 100101002 111111002 001001102
Après chiffrement 101111102 101101102 011011112

Exercice 1 – flot « OU exclusif »

Le but du premier exercice est d'écrire un flot d'entrée qui, étant donnés deux flots d'entrée, produit un nouveau flot dont chaque octet est le OU exclusif des octets correspondants des deux flots. Ce nouveau flot doit se terminer dès que l'un des deux flots d'entrée se termine.

Pour ce faire, écrivez une classe XOrInputStream qui hérite de java.io.InputStream et qui prenne deux flots d'entrée (de type InputStream) en argument et se comporte comme décrit ci-dessus. Votre classe doit redéfinir les méthodes read et close de sa super-classe. La méthode read lit un octet de chaque flot et retourne le OU exclusif de ces deux valeurs, tandis que close ferme les deux flots sous-jacents. Comme vous l'avez deviné, le flot XOrInputStream est une instance du patron de conception Composite !

Note de programmation : en Java, le OU exclusif entre deux valeurs (entières) se fait au moyen de l'opérateur infixe ^, comme l'illustre l'exemple ci-dessous :

byte r = 0b00101010 ^ 0b10010100;

qui donne la valeur 101111102 à la variable r. Rappelez-vous qu'un entier commençant par 0b est spécifié en format binaire.

Pour vérifier votre flot, vous pourriez écrire un test unitaire avec JUnit, comme d'habitude. Mais nous vous proposons plutôt d'écrire un petit programme qui, étant donné un premier fichier contenant des données chiffrées avec un masque jetable et un second fichier contenant ce masque, produise la version déchiffrée dans un troisième fichier. Une fois cela fait, exécutez-le sur la paire de fichiers que nous mettons à votre disposition (dans cette archive zip). Vous devriez obtenir en sortie un fameux télégramme intercepté et décrypté il y a plus d'une centaine d'années.

Exercice 2 – Itérateur pour flots

Comme vous l'avez certainement constaté lors de l'écriture de votre programme de chiffrement/déchiffrement, les flots ne sont pas très agréables à l'usage, étant donnée la manière dont la fin de flot est signalée.

Pour rendre le code de votre programme principal plus lisible, écrivez une classe InputStreamToIterable permettant de transformer un flot d'entrée de type InputStream en un objet implémentant l'interface Iterable<Byte>. Une fois cela fait, vous devriez être capable de parcourir les octets d'un flot d'entrée inputStream quelconque au moyen de la boucle for-each, ainsi :

for (byte b: new InputStreamToIterable(inputStream)) {
    // ... code utilisant b
}

Attention à bien gérer les exceptions qui pourraient être levées par la méthode read et à fermer le flot une fois sa fin atteinte.

Une fois votre classe écrite, modifiez votre programme de chiffrement/déchiffrement pour qu'il parcourt les octets du flot XOrInputStream au moyen de la boucle for-each.

Vous l'aurez remarqué, la classe InputStreamToIterable est un adaptateur (au sens du patron Adapter) permettant de transformer un flot d'entrée en un objet itérable.