Consignes

Le TP est à rendre, par email, avant le mercredi 28 avril à 20h00...

Pour ce TP comme pour les suivants, la pièce la plus importante sera une archive tp1-nom-prenom.tar contenant votre programme. J'insiste sur les points suivants :

  1. l'archive est au format tar (éventuellement au format tar.gz ou tar.bz2),
  2. l'archive contient un répertoire tp1-nom-prenom contenant votre code (elle ne crée rien d'autre dans le répertoire courant),
  3. l'archive ne contient pas de fichier binaire ou exécutable (seulement des fichiers sources),
  4. l'archive contient un fichier Makefile pour compiler votre programme, avec au minimum les cibles all et clean.

Le non-respect des ces consignes risque de me mettre de mauvaise humeur... (Remarque : un fichier Makefile basique mais modulaire est disponible ici.)

Attention, le seul fait que votre programme fonctionne ne suffit pas pour avoir une bonne note. Les points suivants seront pris en compte :

  1. l'architecture de votre programme (code découpé en fonctions etc.),
  2. la lisibilité de votre programme (choix pertinent pour les noms de variables etc.),
  3. la présence de commentaires aux endroits appropriés,
  4. la présence de documentation pour vos fonctions.

Certaines questions appellent à une réponse que vous pouvez mettre en commentaire dans votre fichier principal. Si vous le souhaitez vraiment, vous pouvez aussi m'envoyer un rapport de TP. Le format de ce rapport sera au choix, mais par ordre de préférence :

Attention : les points suivants ne rapportent rien, mais ne pas les respecter pourra retrancher jusqu'à 10 points sur la note finale :

Liens utiles


1. Préliminaires

Le but de ce TP est de programmer un petit logiciel de cryptage / basé sur RSA.

1.1. Rappel sur RSA

Le principe de RSA est basé sur le théorème d'Euler (a^φ(n) = 1 (mod n) si a est premier avec n). RSA fonctionne de la manière suivante :

La clé publique est e (et base) et la clé privée est d (et base). Les nombres p et q ne servent plus, sauf si l'ont souhaite optimiser les calculs. (Et il faut les garder secrets...)

1.2. Entrées / sorties en C

Pour manipuler les fichiers, vous utiliserez de préférence les fonctions

et similaires.

Voici de plus deux fonctions pour écrire et lire le contenu d'une clé dans un fichier :

/* lecture d'une clé */
void lire_cle(char *fichier, mpz_t base, mpz_t cle_e) {
  FILE *cle ;

  if (NULL == (cle = fopen(fichier, "r"))) {
      fprintf(stderr, "Erreur lors de l'ouverture du fichier \"%s\".\nAbandon.\n", fichier);
      exit(-1);
  }

  char str[MAX_SIZE] ;
  fgets(str, MAX_SIZE, cle);
  mpz_set_str(base, str, 62);
  fgets(str, MAX_SIZE, cle);
  mpz_set_str(cle_e, str, 62);
  fclose(cle);
}

/* écriture d'une clé */
void ecrire_cle(char *fichier, mpz_t base, mpz_t cle_e) {
  FILE *cle ;

  if (NULL == (cle = fopen(fichier, "w+"))) {
      fprintf(stderr, "Erreur lors de l'ouverture du fichier \"%s\".\nAbandon.\n", fichier);
      exit(-1);
  }

  fputs(mpz_get_str(NULL, 62, base), cle);
  fputs("\n",cle);
  fputs(mpz_get_str(NULL, 62, cle_e), cle);
  fputs("\n",cle);
  fclose(cle);
}

2. Implémentation

2.1. Génération de la paire clé privée / clé publique

Pour générer une paire clé privée / clé publique, il faut choisir deux nombres premiers p et q de tailles similaires. En partant d'un nombre e premier avec (p-1)(q-1), calculez l'inverse de e modulo (p-1)(q-1).

Pour générer ces clés, il faut donc chercher des nombres premiers assez grand (plusieurs centaines de chiffres). Vous pouvez pour ceci utiliser les fonctions

Pour générer le nombre e, vous pouvez simplement tirer un nombre au hasard et vérifier s'il est premier avec (p-1)(q-1). S'il ne l'est pas, recommencez. (L'espérance du nombre de tirage est assez faible et cette méthode fonctionne donc bien...)

Écrivez une fonction

void genere_cles(unsigned taille_base, mpz_t base, mpz_t cle_d, mpz_t cle_e) ;

pour générer une paire clé publique / clé privée sur taille_base bits.

2.2. Codage à partir de la clé publique

Vous devrez découper le flot entrant en morceaux de taille appropriée et traiter chaque morceau séparément... Écrivez une fonction

int mpz_fwrite(mpz_t bloc, int nb_limbs, FILE *fichier) ;

et une fonction

int mpz_fread(mpz_t bloc, int nb_limbs, FILE *fichier) ;

pour pouvoir lire / écrire un bloc (vu comme grand-entier) dans un fichier.

Vous devrez pour ceci utiliser la structure interne des grands entiers vue dans le TP précédent.

Écrivez une fonction

int coder_RSA(mpz_t base, mpz_t cle_e, FILE *fichier_clair, FILE *fichier_crypte);

qui permet de coder le contenu du fichier fichier_clair et de le mettre dans le fichier fichier_crypte en utilisant le clé publique base et cle_e.

Attention : réfléchissez bien à la taille des blocs que vous utiliserez pour découper votre fichier.

2.3. Décodage à partir de la clé privée

Écrivez une fonction

int decoder_RSA(mpz_t base, mpz_t cle_d, FILE *fichier_crypte, FILE *fichier_clair);

qui permet de décoder le contenu du fichier fichier_crypte et de le mettre sur le fichier fichier_clair.

Attention : réfléchissez bien à la taille des blocs que vous utiliserez pour découper votre fichier.

2.4. Petite interface

Écrivez une petite interface dans votre fonction main pour faciliter l'utilisation de votre programme. Vous devez pouvoir :

Vous devrez pour ceci utilisez les argument argv et argc passés à la fonction main pour savoir ce qu'il faut faire. L'utilisation de la fonction getopt déclarée dans unistd.h pourra vous être utile, mais ce n'est pas obligatoire.

2.5. Test

2.6. Finalisation

(Bonus)

Il y a un risque élevé pour que votre programme ne gère pas le dernier bloc correctement. Expliquez comment corriger ceci...

(Bonus)

Pour rendre la crytpanalyse difficile, il faut chercher un clé qui vérifie certaine propriétés. En utilisant ce paragraphe de wikipedia, programmez une seconde version de la fonction

void genere_cles(unsigned taille_base, mpz_t base, mpz_t cle_d, mpz_t cle_e) ;