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.