• Non ci sono risultati.

Comunicazione criptata: libreria RsaDataLib

Nel documento UNIVERSITÀ DEGLI STUDI DI UDINE (pagine 46-59)

3 Algoritmo RSA 3.1 Descrizione

4. si calcola il numero d tale che e×d = 1 mod φ

3.3 Comunicazione criptata: libreria RsaDataLib

La libreria RsaDataLib utilizza la classe Rsa per permettere la trasmissione e la ricezione di dati cifrati grazie alle chiavi crittografiche generate. Il codice relativo è riportato interamente in appendice (si faccia riferimento alle voci RsaDataLib.hpp e RsaDataLib.cpp).

Di seguito ne richiamiamo l’header:

1      #ifndef  RSADATALIB_HPP   2      #define  RSADATALIB_HPP   3   4      #include  "ClassRsa.hpp"   5      #include  <fstream>   6  

7      void  ReceiveAndDecryptData(Rsa&  clt,  int  socket);  

8      bool  ReadAndSendData(Rsa&  srv,    int  socket);  

9      void  SendData(const  string&  data,  Rsa&  srv,  int  socket);  

10    string  ReadFromFile();  

11    string  ReadMessage();  

12    string  RebuildData(string&  decryptedStr);  

13  

14    #endif  

Le due funzioni principali della libreria sono ReadAndSendData() e

ReceiveAndDecryptData() che permettono, rispettivamente, di inviare dati criptati e

di riceverli e decifrarli. Come si evince dal codice (linee 3÷27 e 56÷90 in appendice),

sia queste due funzioni che ReadFromFile() e ReadMessage() sono sostanzialmente le stesse presenti nella libreria DhDataLib, con la sola differenza che in questo caso vengono utilizzati oggetti di classe Rsa e non Dh. Si rimanda, quindi, al punto 2.3 del capitolo 2 per la loro descrizione.

È opportuno, però, analizzare le differenze, dovute principalmente alla diversa modalità di cifratura dei dati, rispetto alla libreria DhDataLib che sono. Ciò che varia, infatti, è il modo in cui devono essere suddivisi i dati da trasmettere prima di poterli criptare (grazie alle funzioni della classe Rsa) e la loro ricostruzione una volta decifrati. La prima operazione è svolta dalla funzione SendData() mentre la seconda da RebuildData().

SendData() (linee 124÷150 in appendice) riceve in ingresso la stringa data da trasmettere, traduce (partendo dal primo) un carattere nel corrispondente valore decimale e lo accoda alla stringa encryptedStr (linee 132 e 133). Dopodiché

encryptedStr viene convertita nel numero intero encrypted di tipo mpz_class (linea

134). Se encrypted è minore di n, l’inserimento dei caratteri in encryptedStr prosegue, altrimenti quest’ultima viene cifrata grazie alla funzione RsaEncrypt() della classe Rsa e, quindi, trasmessa tramite udp_reply() (linee 140, 141 e 142). encryptedStr viene, quindi, svuotata e il procedimento ripetuto fino a quando la stringa data non è stata interamente trasmessa.

La funzione ReceiveAndDecryptData() riceve, quindi, i dati originali (ovvero quelli contenuti nella stringa data) criptati e suddivisi in blocchi nel modo appena descritto. L’operazione di decifrazione è svolta dal metodo RsaDecrypt() della classe Rsa che restituisce la stringa aux in cui è presente il valore numerico dei dati originali

(ovvero il contenuto di encryptedStr). Quest’ultima contiene, quindi, una sequenza di cifre decimali in cui ogni terna di numeri costituisce un carattere dei dati iniziali.

Di conseguenza, è necessario utilizzare la funzione RebuildData() (linee 29÷54 in

appendice) che converte il valore decimale di ciascuna terna di cifre della stringa aux in un carattere. In questo modo i dati originali vengono ricostruiti.

Nel codice di ReceiveAndDecryptData() si notano le chiamate a RsaDecrypt() e

RebuildData() (linee 11 e 12 in appendice). Il loro utilizzo rappresenta l’unica

differenza rispetto all’omonima funzione della libreria DhDataLib in cui vengono usate le funzioni della classe Dh. Di seguito viene proposto un esempio di utilizzo dei metodi della libreria RsaDataLib.

3.4 Esempio di utilizzo

Vediamo ora un esempio di utilizzo della libreria RsaDataLib e della classe Rsa in cui si realizza una comunicazione criptata tra due parti che indichiamo come client e

server. Come illustrato nei capitoli precedenti, la classe Rsa permette di generare le

chiavi di cifratura che vengono usate dalle funzioni della libreria RsaDataLib per crittografare la comunicazione tra le due parti.

Riportiamo di seguito il codice in linguaggio C++ relativo alle applicazioni del client e del server che utilizzano gli strumenti appena citati (insieme alla libreria

udpsocketlib). Server 1      #include  "ClassRsa.hpp"   2      #include  "udpsocketlib.hpp"   3      #include  "RsaDataLib.hpp"   4  

5      int  main(int  argc,  char  *argv[])  

6      {   7              Rsa  srv;   8              int  sk,  udpPort;   9              bool  done;   10            string  serverIpAdrress;   11            if  (argc  ==  1)   12            {  

13                    cout<<"Server  ip  address:";  

14                    cin>>serverIpAdrress;  

15                    cout<<"Udp  port:";  

16                    cin>>udpPort;   17            }   18            else  if  (argc  ==  3)   19            {   20                    serverIpAdrress  =  argv[1];   21                    udpPort  =  atoi(argv[2]);   22            }   23            else   24            {  

25                    cout<<"Required  arguments:  server  ip  address,  udp  port"<<endl;  

26                    exit(1);  

27            }  

28            if  ((sk  =  create_udp_server((char*)serverIpAdrress.c_str(),   udpPort))  <  0)  

29            {  

30                    cout<<"Cannot  open  server  socket"<<endl;  

31                    exit(1);   32            }   33            if  (!srv.ReceivePublicKey(sk))   34                    exit(1);   35            do   36            {   37                    done  =  ReadAndSendData(srv,  sk);   38            }while  (!done);   39            cout<<"\n\nExit..\n\n";  

40            close_udp_socket(sk);   41            return  0;   42    }   Client 1      #include  "ClassRsa.hpp"   2      #include  "udpsocketlib.hpp"   3      #include  "RsaDataLib.hpp"   4  

5      int  main(int  argc,  char  *argv[])  

6      {   7              Rsa  clnt;   8              int  sk,  udpPort;   9              string  serverIpAdrress;   10   11            if  (argc  ==  1)   12            {  

13                    cout<<"Server  ip  address:";  

14                    cin>>serverIpAdrress;  

15                    cout<<"Udp  port:";  

16                    cin>>udpPort;   17            }   18            else  if  (argc  ==  3)   19            {   20                    serverIpAdrress  =  argv[1];   21                    udpPort  =  atoi(argv[2]);   22            }   23            else   24            {  

25                    cout<<  "Required  arguments:  server  ip  address,  udp   port"<<endl;  

26                    exit(1);  

27            }  

28            if  ((sk  =  create_udp_client())  <  0)  

29            {  

30                    cout<<"Cannot  open  client  socket"<<endl;  

31                    exit(1);  

32            }  

33            clnt.GenerateEncryptionKeys();  

34            if  (!clnt.SendPublicKey(sk,  serverIpAdrress,  udpPort))  

35                    exit(1);  

36            ReceiveAndDecryptData(clnt,  sk);  

37            close_udp_socket(sk);  

38            return  0;  

39    }  

Osservando il codice di entrambi i programmi, si vede che la creazione del socket per

mezzo delle funzioni messe a disposizione dalla libreria udpsocketlib (linee 11÷32

server e client) avviene in modo analogo a quello visto nell’esempio di utilizzo della classe Dh. Si rimanda quindi ai capitoli 1.3 e 2.4 per una descrizione dettagliata. È opportuno analizzare, invece, l’uso delle funzioni della classe Rsa. Come si evince dal codice, dopo che entrambe le parti hanno creato il socket, il server si mette in attesa di ricevere la chiave pubblica del client grazie alla funzione

riscontrato qualche errore nella ricezione della chiave, il programma del server termina.

Contemporaneamente, il client prima genera la chiave privata e quella pubblica chiamando la funzione GenerateEncryptionKeys() (linea 33, client) e poi condivide quest’ultima grazie a SendPublicKey() (linee 34 e 35, client). Anche in questo caso, se si riscontra qualche errore nella condivisione della chiave, il programma del client termina.

Se lo scambio della chiave pubblica tra le due parti avviene correttamente, il server può utilizzare quest’ultima per trasmettere dati cifrati al client attraverso la libreria RsaDataLib. Nell’esempio realizzato vediamo, infatti, che il client si mette in attesa di ricevere informazioni dal server attraverso la funzione ReceiveAndDecryptData() (linea 36, client). Quest’ultimo, invece, entra in un ciclo do..while per trasmettere

dati al client grazie a ReadAndSendData() (linee 35÷38, server).

Quando la trasmissione di dati da parte del server finisce, da entrambi i lati viene chiuso il socket e i programmi terminano.

3.5 Confronto con la versione in C

Anche in questo caso, come per il Diffie-Hellman, nella versione in linguaggio C è stata mantenuta la stessa struttura di quella in C++. Di conseguenza la creazione delle chiavi crittografiche così come la cifratura, la decifratura e la trasmissione dei dati avvengono nello stesso modo e attraverso le stesse funzioni descritte in precedenza nel capitolo. Tuttavia, come nel caso precedente, esistono alcune differenze fra le due versioni, dovute alle diverse caratteristiche dei due linguaggi di programmazione, che si ritiene utile analizzare.

Poiché nel linguaggio C non esiste il concetto di classe è stato necessario creare la libreria RsaLib per la generazione delle chiavi di cifratura dell’algoritmo Rsa. Il codice è riportato interamente in appendice (si faccia riferimento alle voci RsaLib.h e RsaLib.c). Di seguito ne richiamiamo l’header.

13    typedef  enum  {  false,  true  }  bool;  

14   15    struct  keysRsa   16    {   17            mpz_t  p,  q,  phi;   18            mpz_t  n,  e,  d;  //keys   19    };   20   21    struct  publicKeyRsa   22    {   23            mpz_t  e,  n;   24    };   25  

26    bool  ReceivePublicKey(struct  publicKeyRsa*  pbk,  int  socket);  

27    char*  RsaEncrypt(char*  toEncryptStr,  struct  publicKeyRsa*  pbk);  

28    char*  RsaDecrypt(char*  toDecrypt,  struct  keysRsa*  key);  

29    void  GenerateEncryptionKeys(struct  keysRsa*  keys);  

30    void  GeneratePrimeNumber(char*  name,  mpz_t  num,  int  numBits);  

31    void  GeneratePQandComputeNPhi(struct  keysRsa*  var);  

32    void  GetRandomE(struct  keysRsa*  var);  

33    void  FindE(struct  keysRsa*  var);  

34    void  ComputeGCD(mpz_t  r,  mpz_t  e,  mpz_t  phi);  

35    void  ComputeD(struct  keysRsa*  var);  

36    bool  SendPublicKey(int  socket,  char*  ipAddress,  int  udpPort,  struct  

keysRsa*  pubK);  

37    int  rdtsc();  

L’analisi del codice conferma quanto appena detto e cioè che le funzioni della libreria RsaLib sono le stesse presenti nella classe Rsa (ma opportunamente tradotte in linguaggio C). La generazione della chiave di cifratura pubblica e di quella

privata, quindi, avviene sempre attraverso il metodo GenerateEncryptionKeys()  e la

successione delle funzioni chiamate è la stessa della versione in C++.

L’importanza delle classi dal punto di vista della sicurezza e robustezza del software è già stata ampiamente discussa ai punti 2.2 e 2.5 del capitolo 2. Di conseguenza ricordiamo solamente che l’assenza delle classi in C comporta l’impossibilità di sfruttare il principio dell’information hiding. Come nella libreria DhLib, anche in RsaLib le variabili dell’algoritmo non sono più campi privati di una classe ma

vengono inserite all’interno di una struct keysRsa (linee 15÷19) e, quindi, chi utilizza la libreria può accedervi direttamente. Come detto, questo determina un livello di sicurezza minore rispetto alle classi del C++.

Nella libreria è stata definita, inoltre, la struct publicKeyRsa (linee 21÷24) per

contenere la chiave pubblica costituita dalla coppia (n, e). Abbiamo visto che questi due numeri sono le uniche informazioni condivise pubblicamente e, quindi, chi le riceve non ha bisogno di utilizzare tutti i campi della struct keysRsa (contrariamente a chi genera entrambe le chiavi). Di conseguenza la funzione ReceivePublicKey() riceve come parametro una struct publicKeyRsa (linea 26) in cui memorizzerà la chiave pubblica ricevuta. Poiché quest’ultima viene utilizzata anche nell’operazione

di cifratura, anche la funzione RsaEncrypt() ha come parametro d’ingresso una struct

publicKeyRsa (linea 27).

Al punto 2.5 del capitolo 2 si è parlato, inoltre, dell’effetto che l’overloading degli operatori del C++ determina sulla leggibilità del codice. Abbiamo visto che in C++ le variabili dell’algoritmo sono oggetti della classe mpz_class, che quest’ultima realizza l’overloading degli operatori (relazionali, matematici e di input/output) e che ciò semplifica notevolmente l’utilizzo di tale tipo di dato e la leggibilità del codice. In C, invece, le variabili sono di tipo mpz_t ed è necessario utilizzare le funzioni specifiche della libreria GMP per lavorare su di esse. Questo può peggiorare di molto la leggibilità del codice se confrontato con il C++, come risulta evidente per i metodi

ComputeGCD() e ComputeD(). In entrambe le funzioni le operazioni svolte sui dati

possono risultare di difficile comprensione, a differenza di quanto accade per le stesse funzioni scritte in C++. Si ritiene, quindi, opportuno evidenziare le funzioni della libreria GMP utilizzate in RsaLib (oltre a quelle già citate in precedenza):

• mpz_sub() che esegue la sottrazione tra due interi mpz_t, ovvero svolge il compito dell’operatore “-” per questo tipo di dati

• mpz_ui_sub() per sottrarre un intero mpz_t ad uno unsigned long

• mpz_mul() che esegue la sottrazione tra due interi mpz_t (operatore “ * ”) • mpz_add() che svolge il compito dell’operatore “+” per gli interi mpz_t • mpz_fdiv_r() che esegue l’operazione “%” tra due interi mpz_t

• mpz_fdiv_q() che esegue l’operazione “ / ” tra due interi mpz_t

Anche la libreria RsaDataLib è stata opportunamente tradotta in linguaggio C (il codice è riportato in appendice alle voci RsaDataLib.h RsaDataLib.c). Come fatto in precedenza, ne richiamiamo di seguito la dichiarazione delle funzioni:

6      void  ReceiveAndDecryptData(struct  keysRsa*  privateKey,  int  socket);  

7      char*  ReadFromFile();  

8      char*  ReadMessage();  

9      void  SendData(char*  data,  struct  publicKeyRsa*  pbk,  int  socket);  

10    bool  ReadAndSendData(struct  publicKeyRsa*  rk,  int  socket);  

11    char*  ResizeDinamicString(char*  toResize,  int  newDim);  

12    void  RebuildData(char*  decryptedStr);  

Anche in questo caso sono state mantenute tutte le funzioni presenti nella versione in C++ e quindi la comunicazione cifrata fra due parti avviene esattamente nello stesso modo descritto in precedenza.

Anche in questo caso valgono le considerazioni fatte al punto 2.5 del capitolo 2 per quanto riguarda la gestione delle stringhe di dimensione dinamica. Per maggiori dettagli si rimanda a tale capitolo. Qui ci limitiamo solamente a evidenziare il ricorso all’allocazione dinamica della memoria. Da notare, a questo proposito, la presenza della funzione ResizeDinamicString() nella libreria (linea11) utilizzata per il ridimensionamento delle stringhe (già descritta nel capitolo 2) e delle chiamate a

malloc() e free() nel codice (rispettivamente linee 54, 201 e 32, 36, 90, 98, 189 e 208

in appendice).

Il codice relativo ai programmi scritti come esempio di utilizzo delle librerie RsaLib e RsaDataLib è riportato in appendice alle voci Rsa_s.c e Rsa_c.c.

Conclusioni

In questa tesi sono stati realizzati tutti gli strumenti e i programmi necessari ad effettuare comunicazioni cifrate attraverso la rete grazie agli algoritmi considerati. Il raggiungimento di tale risultato ha richiesto lo studio di nuovi argomenti o, comunque, l’approfondimento di argomenti trattati solo parzialmente durante i corsi. Questo ha permesso, quindi, l’apprendimento di importanti nozioni riguardanti la crittografia, la sicurezza informatica e l’utilizzo di strumenti per la programmazione in linguaggio C e C++ che estendono le conoscenze acquisite durante i corsi.

Bibliografia

[1] «Corso di Reti di Calcolatori», 18/12/2014,

http://web.diegm.uniud.it/pierluca/public_html/teaching/reti_di_calcolatori/reti_d i_calcolatori.html

[2] «Cygwin», 18/12/2014, https://cygwin.com/install.html [3] «GMP», 18/12/2014, https://gmplib.org/#DOWNLOAD

[4] W. Diffie and M.E. Hellman, “New Directions in Cryptography”, IEEE Transactions on Information Theory, Vol. IT-22, No. 6, November 1976, pp. 644-654.

[5] «Diffie-Hellman: choosing wrong generator “g” parameter and its implications of practical attacks», 18/12/2014,

http://crypto.stackexchange.com/questions/10025/diffie-hellman-choosing-wrong-generator-g-parameter-and-its-implications-of-p

[6] «How does one calculate a primitive root for Diffie-Hellman?», 18/12/2014, http://crypto.stackexchange.com/questions/820/how-does-one-calculate-a-primitive-root-for-diffie-hellman

[7] R.L. Rivest, A. Shamir and L. Adelman “A Method for Obtaining Digital Signatures and Public-Key Cryptosystems”, Communications of the ACM, Vol. 21, No 2, February 1978, pp. 120-126.

[8] A.S. Tanenbaum, D.J Wetherall, “Reti Di Calcolatori”, Editore Pearson, settembre 2011, pp 758-759.

Appendici

Nel documento UNIVERSITÀ DEGLI STUDI DI UDINE (pagine 46-59)

Documenti correlati