Ecole National Polytechnique ENP 2°Année du 2°Cycle
Spécialité : DSIA Module : Cryptographie (CRYPTO)
Enseignant : ARKI Oussama Année 2023-2024
TP 02 : Une application de chat sécurisée
Objectif :
- Créer une application de chat sécurisée par l’un de Cryptosysteme classique vu au cours.
I. Programmation Réseau en Java
1. Concept de socket
Les Sockets forment une API (Application Program Interface): ils offrent aux programmeurs
une interface entre le programme d'application et les protocoles de communication.
Les sockets (prises de raccordement) forment un mécanisme de communication bidirectionnel
interprocessus dans un environnement distribué. Ils permettent évidemment la
communication interprocess à l'intérieur d'un même système.
L'interface des "sockets" n'est pas liée à une pile de protocoles spécifique (En fonction du
protocole transport utilisé les sockets vont fonctionner différemment). Dans ce cours, nous
nous intéresserons, à l'utilisation des sockets dans le monde TCP/IP.
La notion de socket en tant que prise de raccordement vient d’une analogie avec le réseau
électrique et le réseau téléphonique :
Les sockets représentent donc d'une part une API c'est à dire un ensemble de primitives de
programmation et d'autre part les extrémités de la communication (notion de prise). Les
extrémités de communication sont identifiées dans le monde TCP/IP par trois informations :
une adresse IP, le protocole utilisé (TCP ou UDP) et un numéro de port (entier sur 16 bits donc
de 0 à 65535. Etant donné que ces informations sont uniques dans l’Internet (les adresses IP
sont uniques et les numéros de ports sont uniques pour un protocole donné) ces trois
informations permettent d’identifier de façon unique une extrémité de communication (à
l’instar d’un numéro de téléphone dans un réseau téléphonique).
2. Adresses IP
Une machine (appelée aussi hôte ou host) est identifiée dans l’Internet par son adresse.
L’adresse IP d’une machine correspond à un numéro qui est unique dans le monde.
L’adresse utilisée par la version actuelle du protocole IP (adresse IP), comporte deux champs:
le champ adresse réseau (Network) dans l’Internet et le champ adresse hôte (Host) dans le
réseau. Sa taille est de 4 octets (32 bits). Elle est souvent donnée en notation décimale pointée
(ex: [Link]).Il existe actuellement cinq classes d’adresses IP.
La classe InetAddress
La classe [Link] permet de représenter les adresses IP. Chaque objet de cette
classe possède deux champs hostName et address contenant respectivement une chaîne de
caractère et un tableau d’octets. Le champ hostName stocke le plus souvent le nom de l’hote
([Link] par exemple) et le champ address l’adresse IP.
Cette classe ne possède pas de constructeur publics. Pour créer un objet de type InetAddress il
faut donc utiliser l’une des méthodes suivantes :
public static InetAddress getByName (String nom_hote) : Cette méthode utilise le DNS
pour renvoyer une instance de la classe InetAddress représentant l'adresse Internet de la
machine de nom nom_hote.
public static InetAddress [ ] getAllByName (String nom_hote) : Cette méthode renvoie
toutes les adresses Internet de la machine de nom nom_hote.
public static InetAddress getLocalHost () : Renvoie une instance de la classe InetAddress
représentant l'adresse Internet de la machine locale. Très pratique pour tester sur une même
machine les programmes client et serveur. Equivalent à getByName (null) ou getByName
("localhost").
exemple :
InetAddress adr = [Link]("[Link]");
Autres méthodes
public String getHostName () : Renvoie le nom de la machine hôte, ou bien l'adresse IP si la
machine n'a pas de nom.
getAddress () : Renvoie l'adresse IP stockée par une instance de la classe InetAddress sous la
forme d'un tableau d'octets rangés dans l'ordre standard du réseau (network byte order). Ainsi
l'octet d'indice 0 contient l'octet de poids fort de l'adresse.
3. Sockets TCP
[Link] modèle client/serveur
Le protocole TCP offre un service en mode connecté et fiable. Les données sont délivrées dans
l’ordre de leur émission.
La procédure d’établissement de connexion est dissymétrique. Un processus, appelé serveur,
attends des demandes de connexion qu’un processus, appelé client, lui envoie. Une fois l’étape
d’établissement de connexion effectuée le fonctionnement redeviens symétrique.
Les deux schémas suivants présentent les algorithmes de fonctionnement des clients et serveurs.
Il est à noter que côté serveur on utilise deux sockets : l’un, appelé socket d’écoute, reçoit les
demandes de connexion et l’autre, appelé socket de service, sert pour la communication. En
effet, un serveur peut être connecté simultanément avec plusieurs clients et dans ce cas on
utilisera autant de sockets de service que de clients.
[Link] classe Socket
La classe Socket représente en Java les sockets utilisés côtés client ou les sockets de service.
Constructeurs
public Socket (String hote, int port) throws UnknownHostException, IOException
Voici un exemple d’utilisation de ce constructeur :
Socket leSocket = new Socket("[Link]", 80);
public Socket (InetAddress addresse, int port) throws IOException
Ce constructeur fonctionne comme le premier, mais prends comme premier paramètre une
instance de la classe InetAddress. Ce constructeur provoque une IOException lorsque la
tentative de connexion échoue mais il ne renvoie pas d’UnknownHostException puisque cette
information est connue lors de la création de l’objet InetAddress.
Méthodes informatives
public InetAddress getInetAddress ()
public int getPort ()
Ces méthodes renvoient l'adresse Internet et le port distants auquel le socket est connecté.
public InetAddress getLocalAddress ()
public int getLocalPort ()
Ces méthodes renvoient l'adresse Internet et le port locaux que le socket utilise.
[Link] avec un socket
public InputStream getInputStream () throws IOException
Cette méthode renvoie un flux d’entrées brutes grâce auquel un programme peut lire des
informations à partir d’un socket. Il est d’usage de lier cet InputStream à un autre flux offrant
d’avantage de fonctionnalités (un DataInputStream par exemple) avant d’acquérir les entrées.
Voici un exemple d’utilisation de cette méthode :
DataInputStream fluxEnEntree = new DataInputStream([Link]());
public OutputStream getOutputStream () throws IOException
Cette méthode renvoie un flux de sortie brutes grâce auquel un programme peut écrire des
informations sur un socket. Il est d’usage de lier cet OutputStream à un autre flux offrant
d’avantage de fonctionnalités (un DataOutputStream par exemple) avant d’émettre des
données.
Voici un exemple d’utilisation de cette méthode :
DataOutputStream fluxEnSortie = new DataOutputStream([Link]());
Autre méthodes de communication
PrintStream fluxSortieSocket;
BufferedReader fluxEntreeSocket;
fluxEntreeSocket = new BufferedReader(new InputStreamReader([Link]()));
[Link]([Link]());
fluxSortieSocket = new PrintStream([Link]());
Scanner scan=new Scanner([Link]);
String message = [Link]();
[Link](message);
Fermeture d’un socket
public void close() throws IOException
Bien que Java ferme tous les sockets ouverts lorsqu’un programme se termine ou bien lors d’un
« garbage collect », il est fortement conseillé de fermer explicitement les sockets dont on n’a
plus besoin à l’aide de la méthode close.
[Link] classe ServerSocket
Cette classe permet de créer des sockets qui attendent des connections sur un port spécifié et
lors d’une connexion retournent un Socket qui permet de communiquer avec l’appelant.
Constructeurs
public ServerSocket (int port) throws IOException
Ce constructeur crée un socket serveur qui attendra les connexions sur le port spécifié. Lorsque
l’entier port vaut 0, le port est sélectionné par le système. Ces ports anonymes sont peu utilisés
car le client doit connaître à l’avance le numéro du port de destination.
Accepter et clore une connexion
public Socket accept () throws IOException
Cette méthode bloque l’exécution du programme serveur dans l’attente d’une demande de
connexion d’un client. Elle renvoie un objet Socket une fois la connexion établie.
Si vous ne voulez pas bloquer l’exécution du programme il suffit de placer l’appel à accept dans
un thread spécifique.
Méthodes informatives
public InetAddress getInetAddress ()
public int getLocalPort ()
Ces méthodes renvoient l'adresse Internet et le port locaux sur lequel le socket attends les
connexions.
II. Thread : Exécuter des tâches simultanément
Les threads sont des fils d'exécution de notre programme. Lorsque nous en créons plusieurs,
nous pouvons exécuter des tâches simultanément.
[Link]("Le nom du thread principal est " + [Link]().getName());
Non, vous ne rêvez pas : il s'agit bien de notre méthode main, le thread principal de notre
application !
Voyez un thread comme une machine bien huilée capable d'effectuer les tâches que vous lui
spécifiez. Une fois instancié, un thread attend son lancement. Dès que c'est fait, il invoque sa
méthode run() qui va lui permettre de connaître les tâches qu'il a à effectuer.
Nous allons maintenant apprendre à créer un nouveau thread. Je l'avais mentionné dans
l'introduction, il existe deux manières de faire :
créer une classe héritant de la classe Thread ;
créer une implémentation de l'interface Runnable et instancier un objet Thread avec
l'implémentation de cette interface.
Comme je vous le disais, nous allons opter pour la première solution. Tout ce que nous avons
à faire, c'est redéfinir la méthode run() de notre objet afin qu'il sache ce qu'il doit faire. Puisque
nous allons en utiliser plusieurs, autant pouvoir les différencier : nous allons leur donner des
noms.
Créons donc une classe gérant tout cela qui contient un constructeur comprenant un String en
paramètre pour spécifier le nom du thread. Cette classe doit également comprendre une méthode
getName() afin de retourner ce nom.
La classe Thread se trouvant dans le package [Link], aucune instruction import n'est
nécessaire. En voici le code :
public class TestThread extends Thread {
public TestThread(String name){
super(name);
}
public void run(){
for(int i = 0; i < 10; i++)
[Link]([Link]());
}
}
public class Test {
public static void main(String[] args) {
TestThread t = new TestThread("A");
TestThread t2 = new TestThread(" B");
[Link]();
[Link]();
}
}
Un thread peut présenter plusieurs états ([Link]()) :
NEW : lors de sa création.
RUNNABLE : lorsqu'on invoque la méthode start(), le thread est prêt à travailler.
TERMINATED : lorsque le thread a effectué toutes ses tâches ; on dit aussi qu'il est « mort ».
Vous ne pouvez alors plus le relancer par la méthode start().
TIMED_WAITING : lorsque le thread est en pause (quand vous utilisez la méthode sleep(),
par exemple).
WAITING : lorsque le thread est en attente indéfinie.
BLOCKED : lorsque l'ordonnanceur place un thread en sommeil pour en utiliser un autre, il
lui impose cet état.
Enoncé
1-Créer un programme qui joue le rôle d’un serveur (socket serveur + socket service).
2-Créer un programme qui joue le rôle d’un client (socket).
3-Utiliser le concept des Threads pour exécuter des taches simultanément (Input et Output)
4-Faire communiquer les deux programmes : envoyer et recevoir des messages texte simple,
tout en assurant la sécurisé du transfert (utiliser un Cryptosysteme classique)