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 :
tar
(éventuellement au format tar.gz
ou tar.bz2
),
tp1-nom-prenom
contenant votre code (elle ne crée rien d'autre dans le répertoire courant),
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 :
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 :
README
au format texte (mais avec un minimum de mise en page),
.odt
),
Attention : les points suivants ne rapportent rien, mais ne pas les respecter pourra retrancher jusqu'à 10 points sur la note finale :
-Wall -Werror -Wextra -pedantic -std=c99
,
info614
, ou info-614
ou info 614
,
tp2.h
mpz_fwrite
et mpz_fread
Le but de ce TP est de programmer un petit logiciel de cryptage / basé 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...)
Pour manipuler les fichiers, vous utiliserez de préférence les fonctions
FILE *fopen(const char *path, const char *mode);
int fclose(FILE *fp);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
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); }
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
void mpz_nextprime (mpz_t ROP, mpz_t OP)
,
int mpz_probab_prime_p (mpz_t N, int REPS)
,
void mpz_urandomb (mpz_t ROP, gmp_randstate_t STATE, unsigned long int N)
,
void mpz_urandomm (mpz_t ROP, gmp_randstate_t STATE, mpz_t N)
.
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.
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.
É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.
É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.
(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) ;