UNIVERSITÀ DEGLI STUDI DI UDINE -...
Transcript of UNIVERSITÀ DEGLI STUDI DI UDINE -...
UNIVERSITÀ DEGLI STUDI DI UDINE
Dipartimento di Ingegneria Elettrica, Gestionale e Meccanica
Corso di Laurea in Ingegneria Gestionale
Tesi di Laurea
IMPLEMENTAZIONI DIDATTICHE DI ALGORITMI DI CIFRATURA PER SICUREZZA INFORMATICA
Relatore: Laureando: Prof. Pier Luca Montessoro Pierpaolo Basso
ANNO ACCADEMICO 2013/2014
Alla mia famiglia e a Veronica
I
Sommario La confidenzialità dei dati è uno dei requisiti essenziali nell'ambito della sicurezza informatica. Oggi, infatti, milioni di persone usano la rete per fare acquisti, per l’home banking e per molte altre applicazioni che richiedono la condivisione di informazioni personali. In tutti questi casi le comunicazioni avvengono attraverso canali pubblici, cioè non sicuri. La crittografia svolge, quindi, un ruolo fondamentale nel prevenire l’estrazione non autorizzata di informazioni da tali comunicazioni. In questa tesi viene realizzata l’implementazione di due algoritmi di cifratura che consentono una comunicazione sicura (cioè criptata) attraverso un canale pubblico: l’algoritmo Diffie-Hellman e l’algoritmo RSA. Innanzitutto, entrambi gli algoritmi saranno analizzati dal punto di vista teorico allo scopo di comprenderne le finalità e i contenuti. Dopodichè verrà presentata la loro implementazione in linguaggio C e C++. Per ciascuno di essi sarà, inoltre, proposto un esempio di utilizzo nella cifratura delle comunicazioni che avvengono attraverso la rete. L’attenzione sarà focalizzata principalmente sulla versione in C++ che verrà puntualmente confrontata con quella in linguaggio C. Il prodotto finale della tesi è costituito dall’insieme di classi e di librerire realizzate per implementare i due algoritmi e dai programmi per utilizzarle.
II
III
Indice Sommario I Indice III Indice delle figure V Introduzione VII 1 Linguaggio e librerie utilizzate 1 1.1 Scelte di progetto 1 1.2 Installazione del compilatore e delle librerie in Windows 3 1.2.1 Installazione del compilatore C/C++ in ambiente Cygwin 3 1.2.2 Installazione libreria GMP 4 1.3 Libreria udpsocketlib 6 2 Algoritmo Diffie-Hellman 11 2.1 Descrizione 11 2.1.1 Sicurezza 12 2.2 Scambio della chiave di cifratura: classe Dh 14 2.2.1 Funzioni crittografiche della classe Dh 17 2.3 Comunicazione criptata: libreria DhDataLib 19 2.4 Esempio di utilizzo 21 2.5 Confronto con la versione in C 24 3 Algoritmo RSA 27 3.1 Descrizione 27 3.1.1 Correttezza dell’algoritmo 28 3.2 Generazione delle chiavi di cifratura: classe Rsa 30 3.3 Comunicazione criptata: libreria RsaDataLib 34 3.4 Esempio di utilizzo 36 3.5 Confronto con la versione in C 39 Conclusioni 43 Bibliografia 45 Appendici 47 File in C++ 47 ClassDh.hpp 47 ClassDh.cpp 48 DhDataLib.hpp 53 DhDataLib.cpp 53 ClassRsa.hpp 56 ClassRsa.cpp 57 RsaDataLib.hpp 61
IV
RsaDataLib.cpp 61 File in C 64 DhLib.h 64 DhLib.c 65 DhDataLib.h 69 DhDataLib.c 70 Dh_c.c 73 Dh_s.c 74 RsaLib.h 75 RsaLib.c 76 RsaDataLib.h 81 RsaDataLib.c 81 Rsa_s.c 85 Rsa_c.c 86
V
Indice delle figure Figura 1.1 – Cygwin Setup 4 Figura 1.2 – Schema comunicazione via UDP 6 Figura 2.1 – Scambio di chiavi Diffie-Hellman 11 Figura 2.2 – Funzioni per lo scambio della chiave 15 Figura 3.1 – Funzioni per la creazione delle chiavi 31
VI
VII
Introduzione Lo scopo di questa tesi è di implementare in linguaggio C e C++ gli algoritmi crittografici Diffie-Hellman e RSA e di realizzare le librerie e i programmi necessari al loro utilizzo per cifrare le comunicazioni che avvengono attraverso la rete. È importante sottolineare la finalità didattica del lavoro svolto: per permettere la comprensione degli algoritmi sopracitati, tutto il codice è stato scritto in modo tale da presentare in dettaglio i passaggi da essi previsti. Nel primo capitolo vengono descritte le motivazioni che hanno portato all’utilizzo dei linguaggi di programmazione appena citati e gli strumenti necessari per la realizzazione della tesi. Il secondo capitolo è interamente dedicato all’algoritmo Diffie-Hellman. Innanzitutto l’algoritmo viene descritto in dettaglio e vengono analizzati i requisiti fondamentali per la sua sicurezza. Dopodichè viene illustrata la classe Dh che implementa in C++ sia l’algoritmo che le funzioni di cifratura e decifratura dei dati. Infine, viene descritta la libreria DhDataLib che consente di realizzare comunicazioni criptate grazie alla classe Dh. L’ultima parte del capitolo comprende i programmi (scritti in C++) che utilizzano questi strumenti e il confronto con la versione realizzata in linguaggio C. Il terzo capitolo, infine, è dedicato all’algortimo RSA. Anche in questo caso, come per il Diffie-Hellman, nella prima parte del capitolo si ha la descrizione dell’algortimo RSA e l’analisi della sua sicurezza. Dopodichè vengono illustrate la classe Rsa (scritta in linguaggio C++) che implementa l’algoritmo e la libreria RsaDataLib che la utilizza per cifrare le comunizioni in rete. Nell’ultima parte del capitolo vengono descritti i programmi (in C++) che utilizzano questi strumenti e viene analizzata la versione realizzata in linguaggio C.
VIII
1
1 Linguaggio e librerie utilizzate 1.1 Scelte di progetto Il linguaggio di programmazione utilizzato per l’implementazione degli algoritmi di crittografia trattati in questa tesi è il C++. Questa scelta è dettata da alcune caratteristiche offerte dal tale linguaggio che saranno illustrate brevemente in questo capitolo. Tuttavia, poiché lo scopo della tesi è un utilizzo didattico degli algoritmi implementati, è stata realizzata una versione parallela in C rivolta a chi non avesse studiato il C++. Data la natura didattica dell’implementazione, inoltre, i programmi realizzati (sia in C che in C++) sono stati sviluppati in modo tale da presentare esplicitamente tutti i passaggi degli algoritmi di cifratura: le operazioni svolte vengono, quindi, opportunamente evidenziate per permettere di comprendere il funzionamento degli algoritmi stessi. Gli algoritmi di crittografia richiedono la gestione di numeri interi molto grandi, non rappresentabili con i tipi di dato standard messi a disposizione da linguaggi di programmazione come il C e il C++. È stato, quindi, necessario l’utilizzo della libreria GMP (GNU Multiple Precision Arithmetic Library). Essa permette di lavorare agevolmente con numeri interi, razionali e in virgola mobile ponendo come unico limite la memoria disponibile del calcolatore. L’overloading degli operatori del C++ permette una naturale integrazione della libreria GMP. I tipi di dato introdotti da tale libreria possono, quindi, essere trattati allo stesso modo di quelli standard senza la necessità di utilizzare funzioni specifiche per le operazioni aritmetiche, logiche e di input/output, contrariamente a quanto accade in C. Tutto ciò si traduce in una maggiore semplicità e leggibilità del codice scritto in C++. Questo può essere considerato uno dei motivi per cui si è deciso di utilizzare tale linguaggio. Un’ulteriore motivazione alla scelta del C++ è rappresentata dalla gestione ottimale delle stringhe di caratteri offerta da questo linguaggio, aspetto rilevante nella programmazione connessa alla crittografia. Questa caratteristica è ancora più evidente nel confronto col C, linguaggio in cui la gestione delle stringhe è limitata ai soli vettori di caratteri. La scelta del C++ è stata dettata, inoltre, da motivazioni riguardanti la sicurezza. Le classi introdotte dal C++ supportano il principio dell'Information Hiding (nascondere le informazioni), comune ai linguaggi di programmazione Object-Oriented. All’interno di una classe, infatti, possono essere presenti sia sezioni pubbliche sia private. I dati e i metodi contenuti nella sezione pubblica di una classe sono gli unici accessibili dall’esterno. Le funzioni e i dati presenti nella sezione privata, invece, sono accessibili solamente ai metodi di classe, mentre risultano invisibili all’esterno. È quindi possibile proteggere le chiavi di cifratura e le altre variabili inserendole nella sezione privata della classe. Così facendo le chiavi di cifratura non sono direttamente accessibili dall’esterno ma possono essere generate, modificate e utilizzate solamente attraverso i metodi pubblici della classe. Questo determina un livello di sicurezza più elevato e una maggiore robustezza dal punto di vista dell’ingegneria del software.
2
Nella realizzazione della tesi è stato necessario utilizzare altre due librerie, oltre alla già citata GMP:
• La libreria udpsocketlib, appartenente alla libreria socketlib sviluppata dal prof. Montessoro. Quest’ultima è una libreria di funzioni per la scrittura semplificata di applicazioni client/server con i protocolli TCP e UDP. Gli algoritmi implementati prevedono, infatti, la presenza di due parti che devono generare e scambiare una chiave di crittografia attraverso la rete (quindi un canale non sicuro) per poi condividere dati cifrati. È stata quindi utilizzata la libreria udpsocketlib per permettere alle due parti di dialogare attraverso la rete tramite il protocollo UDP. In conformità con tale libreria, possiamo pensare a esse come a un client e a un server che comunicano.
• La libreria OpenSSL, utilizzata per generare numeri primi pseudo-casuali da 512 e da 1024 bit.
Le principali funzioni utilizzate delle librerie GMP e OpenSSL saranno analizzate, se necessario, nei capitoli successivi, non appena le si incontrerà nel codice scritto. Viceversa, si ritiene opportuno descrivere più in dettaglio le funzioni della libreria udpsocketlib utilizzate per lo scambio di dati in rete attraverso il protocollo UDP. Questo argomento verrà trattato nel punto 1.3.
3
1.2 Installazione del compilatore e delle librerie in Windows Per l’implementazione degli algoritmi considerati in C/C++, ma più in generale per la realizzazione di programmi scritti in tali linguaggi, un primo passo fondamentale è l’installazione del compilatore e dei pacchetti necessari al suo funzionamento. Il compilatore, infatti, è fondamentale per tradurre il codice scritto in un programma eseguibile. In ambiente Windows la scelta è stata di installare il compilatore Cygwin GNU C/C++. Nel processo d’installazione, come vedremo tra breve, è possibile selezionare tra i diversi pacchetti disponibili anche la libreria OpenSSL. La presenza del compilatore, infine, è fondamentale per poter installare la libreria GMP che rappresenta una componente di grande rilevanza per la realizzazione della tesi. Le operazioni fondamentali da eseguire sono, nell’ordine:
• Installazione del compilatore Cygwin GNU C/C++, compresa la libreria OpenSSL.
• Installazione della libreria GMP. Per quanto riguarda la libreria “socketlib”, è sufficiente scaricarla dal sito del docente [1] e includere i file sorgenti necessari, ovvero “udpsocketlib.hpp” e “udpsocketlib.cpp” per il C++ e “udpsocketlib.h” e “udpsocketlib.c” per il C. 1.2.1 Installazione del compilatore C/C++ in ambiente Cygwin Innanzitutto è necessario collegarsi al sito di Cygwin [2] per ottenere i file di installazione. È necessario, quindi, scaricare il programma d’installazione “setup.exe” nella versione corretta per la propria architettura, 32 o 64 bit. Fatto ciò, si deve avviare il setup e selezionare la voce “Install from Internet” (ovviamente è richiesta una connessione a internet per scaricare i pacchetti necessari). Dopodiché è sufficiente avanzare senza modificare nulla fino alla schermata “Select packages”. In questa pagina è necessario selezionare alcuni pacchetti aggiuntivi rispetto alla configurazione di default prevista dal programma d’installazione. In particolare, devono essere selezionati i seguenti pacchetti all’interno delle corrispondenti categorie:
• nella categoria “Devel”: cmake, gcc-core, gcc-g++, gdb, git, make • nella categoria “Interpreters”: m4 • nella categoria “Net”: openssl-devel
Per selezionare questi pacchetti aggiuntivi basta espandere la relativa categoria, individuare il pacchetto desiderato e cliccare una sola volta su “Skip”, così da far apparire il numero indicante l’ultima versione disponibile.
4
Vediamo un esempio in figura 1.1:
Questa configurazione permette, quindi, di installare il compilatore GNU C/C++ (gcc-core, gcc-g++), la libreria OpenSSL (openssl-devel) e alcuni componenti aggiuntivi necessari per un corretto funzionamento. Una volta aggiunti i pacchetti indicati è sufficiente avanzare senza modificare nulla fino a quando il download e l’installazione saranno terminati. Nella pagina immediatamente successiva a quella di selezione dei pacchetti, ovvero la pagina con titolo “Resolving dependancies”, è fondamentale che sia selezionata l’opzione “Select required packages”. L'installazione crea così un'icona, un collegamento indicato con "Cygwin Terminal", che consente di eseguire una shell (finestra di comandi) in cui è possibile utilizzare alcuni comandi Unix e il compilatore C/C++. 1.2.2 Installazione libreria GMP La libreria GMP può essere reperita dal sito di GMP[3]. Dopo aver scaricato ed estratto gli archivi, si deve aprire la shell “Cygwin Terminal”, installata come visto in precedenza. Si deve ora cambiare il direttorio corrente (tramite il comando “cd”) spostandosi nella cartella contenente i file della libreria GMP appena estratti. Per maggior chiarezza, si deve spostare il direttorio corrente nella cartella della libreria GMP
Figura 1.1 - Cygwin Setup
5
contenente il file “configure”. A questo punto si deve digitare nel terminale il comando “./configure --enable-cxx ”, premere invio e attendere il completamento dell’operazione. Dopodiché si deve fare lo stesso col comando “make”. Per verificare la corretta esecuzione delle operazioni precedenti si deve digitare “make check” nel terminale e premere invio. Terminati i diversi test avviati dal comando precedente, se non sono stati riscontrati errori, si deve digitare “make install” nel terminale e attendere il completamento di quest’ultima operazione. La libreria GMP dovrebbe, così, essere correttamente installata per entrambi i linguaggi C e C++.
6
1.3 Libreria udpsocketlib Il protocollo UDP (User Datagram Protocol) è un protocollo di livello di trasporto non orientato alla connessione. Il protocollo UDP si appoggia direttamente sopra IP, i dati vengono inviati in forma di datagram e non ne è assicurata né l’effettiva ricezione né l'arrivo nell'ordine in cui vengono trasmessi. Non viene gestito, inoltre, né il riordinamento dei pacchetti né la ritrasmissione di quelli non ricevuti. Di conseguenza il protocollo UDP è considerato non affidabile. In compenso è molto veloce ed efficiente: minimizza i ritardi poiché non si ha ritrasmissione e riordinamento dei dati ed essendo non connesso non presenta overhead dovuto ai pacchetti di servizio necessari per stabilire, mantenere e chiudere una connessione. Queste caratteristiche fanno sì che l’UDP sia particolarmente conveniente quando si opera su rete locale. La probabilità estremamente bassa di perdita di dati che contraddistingue le reti locali permette, infatti, di sfruttare appieno la rapidità e l’efficienza del protocollo. Le reti locali rappresentano esattamente l’ambito applicativo per cui sono stati implementati gli algoritmi trattati in questa tesi. Di conseguenza, nei programmi realizzati si è deciso di utilizzare il protocollo UDP per lo scambio di dati in rete. Come detto, i socket UDP supportano una comunicazione di tipo datagram. Tutto ciò che avviene, quindi, in una comunicazione attraverso socket UDP è la trasmissione di un pacchetto da un client a un server o viceversa, secondo lo schema in figura 1.2.
Come illustrato in figura 1.2, un server UDP, dopo aver creato il socket tramite la funzione socket, esegue la primitiva bind per associare un indirizzo di rete al socket stesso. Un client UDP, invece, deve creare solamente il socket. Dopodiché entrambe le parti utilizzano direttamente le funzioni sendto e recvfrom per inviare e ricevere dati.
Figura 1.2 - Schema comunicazione via UDP
client socket()
recvfrom()
sendto()
server socket()
bind()
sendto()
recvfrom()
7
Nelle applicazioni realizzate in questa tesi, come detto in precedenza, ci si è serviti della libreria udpsocketlib per utilizzare i socket UDP. È opportuno, quindi, analizzare le funzioni messe a disposizione da tale libreria per permettere a un client e a un server di comunicare in rete attraverso il protocollo UDP secondo lo schema visto. Innanzitutto il server esegue la funzione create_udp_server di cui riportiamo di seguito il codice. 1 int create_udp_server(char *ip_address, int port) 2 { 3 int sk; 4 struct sockaddr_in srv; 5 /* create a socket descriptor */ 6 if ((sk = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 7 { 8 error_handler("socket() [create_udp_server()]"); 9 return -‐1; 10 } 11 /* fill the (used) fields of the socket address with 12 the server information (local socket address) */ 13 bzero((char *) &srv, sizeof(srv)); 14 srv.sin_family = AF_INET; 15 srv.sin_addr.s_addr = inet_addr(ip_address); 16 srv.sin_port = htons(port); 17 /* add the local socket address to the socket descriptor */ 18 if (bind(sk, (struct sockaddr *) &srv, sizeof(srv)) < 0) 19 { 20 error_handler("bind() [create_udp_server()]"); 21 return -‐1; 22 } 23 return sk; 24 } Da notare la chiamata alla primitiva socket (linea 6), la quale crea il socket e i cui parametri specificano:
• La famiglia di protocolli da usare. “AF_INET” indica il protocollo IPv4. • Il tipo di servizio. “SOCK_DGRAM” indica l’utilizzo dei datagram. • Il protocollo da usare con il socket. “0” indica che il protocollo usato è
quello di default per la combinazione di dominio e tipo specificata dagli altri due parametri.
La funzione socket, restituisce un intero (assegnato a sk) che sarà usato per i riferimenti al socket stesso. Dopodiché viene eseguita la funzione bind (linea 21) che associa al socket un indirizzo di rete costituito dall’indirizzo IPv4 del server e dalla porta locale su cui attende i dati. Questi dati sono inseriti all’interno di una struct sockaddr (linee 14÷16) passata come parametro alla funzione bind insieme al riferimento sk al socket. A questo punto, il client esegue la funzione create_udp_client con cui viene creato il socket (linea 5) attraverso la primitiva socket nello stesso modo visto per il server.
8
Il codice della funzione è riportato di seguito. 1 int create_udp_client(void) 2 { 3 int sk; 4 /* create a socket descriptor */ 5 if ((sk = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 6 { 7 error_handler("socket() [create_udp_client()]"); 8 return -‐1; 9 } 10 return sk; 11 } Client e server possono, quindi, utilizzare le funzioni di libreria udp_send e udp_receive per scambiare dati tra loro. Riportiamo di seguito il codice di entrambe. Da notare in udp_send l’utilizzo della funzione sendto (linea 12) i cui parametri principali sono: una struct sockaddr_in ska in cui sono stati inseriti l’indirizzo IPv4 e la porta del destinatario (linee 8÷10), il riferimento al socket sk e la stringa buffer da inviare. 1 int udp_send(int sk, char *buffer, char *ip_address, int port) 2 { 3 struct sockaddr_in ska; 4 int msg_len = strlen(buffer); 5 /* fill the (used) fields of the socket address with 6 the server information (remote socket address) */ 7 bzero((char *) &ska, sizeof(ska)); 8 ska.sin_family = AF_INET; 9 ska.sin_addr.s_addr = inet_addr(ip_address); 10 ska.sin_port = htons((unsigned short) port); 11 12 if (sendto(sk, buffer, msg_len, 0, (struct sockaddr *) &ska,
sizeof(ska)) != msg_len) 13 { 14 error_handler("sendto() [udp_send()]"); 15 return 0; 16 } 17 return 1; 18 } In udp_receive si osserva la chiamata della funzione recvfrom (linea 24). I parametri più rilevanti sono: il numero intero sk (riferimento al socket), la stringa di caratteri buffer in cui saranno inseriti i dati ricevuti e la struct reply_to_socket_address in cui è memorizzato l’indirizzo del mittente. 1 int udp_receive(int sk, char *buffer) 2 { 3 int dim; 4 len_of_reply_to_socket_address = sizeof(reply_to_socket_address); 5 if ((dim = recvfrom(sk, buffer, BUFSIZ, 0, (struct sockaddr *)
&reply_to_socket_address, (int *) &len_of_reply_to_socket_address)) < 0)
6 {
9
7 error_handler("recvfrom() [udp_receive()]"); 8 return -‐1; 9 } 10 buffer[dim] = '\0'; 11 return dim; 12 } Quest’ultimo dato, inoltre, è usato della funzione udp_reply per rispondere al mittente dell’ultimo pacchetto ricevuto. Nel codice si nota, infatti, come alla funzione sendto è passata la struct reply_to_socket_address (linea 5). 1 int udp_reply(int sk, char *buffer) 2 { 3 int msg_len = strlen(buffer); 4 if (sendto(sk, buffer, msg_len, 0, (struct sockaddr *)
&reply_to_socket_address, len_of_reply_to_socket_address) < 0) 5 { 6 error_handler("sendto() [udp_reply()]"); 7 return 0; 8 } 9 return 1; 10 } È importante, quindi, notare come, a differenza della funzione udp_send, a udp_reply non è passato alcun indirizzo di rete come parametro. Questa può, quindi, essere usata solamente a seguito della ricezione di un pacchetto per rispondere al mittente. Terminato lo scambio di dati tra client e server, entrambe chiamano la funzione close_udp_socket. La funzione close chiude il socket creato e rilascia tutte le risorse ad esso allocate (linea 3). 1 int close_udp_socket(int sk) 2 { 3 if (close(sk) != 0) 4 { 5 error_handler("close() [close_udp_socket()]"); 6 return 0; 7 } 8 return 1; 9 }
10
11
2 Algoritmo Diffie-Hellman 2.1 Descrizione L’algoritmo Diffie-Hellman è stato proposto nel 1976 da Whitfield Diffie e Martin E. Hellman, due crittografi statunitensi, nell’articolo “New Directions in Cryptography” [4]. Questo algoritmo ha rappresentato una soluzione innovativa ad uno dei problemi classici della crittografia, ovvero la distribuzione delle chiavi di cifratura. Uno dei principali ambiti applicativi della crittografia è la garanzia della privacy. La criptazione delle comunicazioni che avvengono attraverso un canale non sicuro (pubblico) diventa, quindi, fondamentale per prevenire l’estrazione non autorizzata di informazioni da esse. Di conseguenza, è necessario che le parti in comunicazione condividano una chiave crittografica che non sia nota a nessun altro. Si presenta, dunque, il problema della generazione e della distribuzione di tale chiave. Questo può essere risolto inviando la chiave di cifratura in anticipo alle parti coinvolte attraverso un canale sicuro come ad esempio un corriere o la posta raccomandata. Tuttavia, i ritardi e i costi introdotti da questa modalità di distribuzione non sono accettabili nella maggior parte delle applicazioni pratiche, specialmente in ambito commerciale. L’algoritmo proposto nell’articolo [4] offre un approccio differente al problema, eliminando, di fatto, la necessità di un canale sicuro per la condivisione delle chiavi. Noto come “Diffie-Hellman key exchange” (scambio di chiavi Diffie-Hellman), tale algoritmo crittografico consente a due parti di stabilire una chiave di cifratura condivisa e segreta attraverso un canale di comunicazione insicuro (pubblico), senza la necessità che queste si siano scambiate informazioni in precedenza. La figura 2.1 illustra i passi dell’algoritmo che portano allo scambio della chiave di cifratura K condivisa fra le due parti.
Figura 2.1 - Scambio di chiavi Diffie-Hellman
12
Come visto nel capitolo 1, possiamo pensare alle due parti come a un client e a un server che si scambiano una chiave di cifratura. Il client, innanzitutto, genera un numero primo p molto elevato di almeno 1024 bit (cioè circa 300 cifre decimali) e un generatore g di Zp
* (che deve esistere essendo p primo). In aritmetica modulare un generatore modulo p (detto semplicemente generatore) è un intero g le cui potenze modulo p sono congruenti con i numeri coprimi a p. Ovvero g è un intero che elevato a potenza genera tutti i numeri coprimi a p. In particolare, è possibile dimostrare che se p è primo esiste sicuramente un generatore g che produce tutti gli elementi di Zp
*, in altre parole i numeri compresi tra 1 e (p -1). Vedremo nel seguito del capitolo il metodo implementato per trovare un generatore di Zp
*. Il client genera quindi un numero casuale a < p e calcola A = ga mod p. Come evidenziato in figura 2.1, i numeri p, g, A sono comunicati pubblicamente al server mentre a non viene condiviso. In modo analogo il server genera un numero casuale b < p, calcola B = gb mod p e lo invia al client. Entrambe le parti possono quindi calcolare la chiave di cifratura K:
• Il client calcola K = Ba mod p. • Il server calcola K = Ab mod p.
Come indica l’ultimo passaggio in figura 2.1, client e server trovano lo stesso valore per K. Per le proprietà dell’aritmetica modulare, infatti, si ha che: K = Ab mod p = (ga mod p)b mod p = gab mod p = (gb mod p)a mod p = Ba mod p La chiave ottenuta può essere, quindi, utilizzata per crittografare le successive comunicazioni fra le due parti mediante un algoritmo di cifratura a chiave simmetrica. In questa tesi, a titolo di esempio applicativo, è stato implementato un algoritmo di cifratura simmetrica che, come vedremo nel seguito del capitolo, esegue l’XOR bit a bit fra la chiave crittografica generata e i dati in chiaro da inviare. 2.1.1 Sicurezza La sicurezza dell’algoritmo si basa sulla complessità computazionale del calcolo del logaritmo discreto. Una terza parte può intercettare i parametri p, g, A, B ma non è in grado di calcolare la chiave di cifratura K non conoscendo a o b. Non essendo stati condivisi, l’unico modo per determinare questi numeri è risolvere il problema del logaritmo discreto, infatti:
• a = logg A mod p • b = logg B mod p
La sicurezza dell’algoritmo, quindi, è garantita fino a quando il calcolo del logaritmo discreto è computazionalmente proibitivo.
13
A tale scopo, il generatore g gioca un ruolo fondamentale. Come documentato nei siti [5] e [6], affinché l’algoritmo Diffie-Hellman sia sicuro non è necessario che il generatore produca tutti gli interi modulo p non nulli, ma è sufficiente che il sottogruppo di Zp
* generato da g sia tale da rendere elevata la complessità computazionale del logaritmo discreto. Per ottenere ciò devono essere soddisfatte le seguenti condizioni:
• p sia un numero primo generato in modo casuale e sufficientemente grande, almeno 1024 bit.
• il generatore g ha ordine r che è il più piccolo intero per cui gr = 1 mod p. Il
più grande divisore primo di r deve essere elevato, di almeno 2k bit, dove k bit è il livello di sicurezza desiderato.
La prima condizione può essere facilmente soddisfatta grazie alle numerose librerie disponibili per la generazione di numeri primi casuali, come vedremo nell’implementazione realizzata dell’algoritmo. Fare in modo che l’ordine r del generatore scelto abbia un divisore primo sufficientemente grande, invece, può essere più complesso. È conveniente, quindi, procedere nel modo descritto di seguito (come indicato nel sito [6]). Si deve, innanzitutto, generare un numero primo casuale p “sicuro”, cioè tale che (p-1)/2 sia anch’esso primo. Indichiamo con q il numero (p-1)/2 e quindi possiamo esprimere p come p = 2q +1. Poiché p deve essere un numero primo sufficientemente grande, anche q lo sarà. Si può dimostrare che un qualunque intero casuale g modulo p non nullo ha ordine r pari a 1, 2, q o 2q. Gli ordini pari a 1 e 2 si ottengono rispettivamente per g uguale a 1 e (p-1). Qualunque altro intero casuale g compreso nell’intervallo [2, (p-2)] avrà, quindi, ordine r pari a q o 2q e può quindi essere usato come generatore per l’algoritmo Diffie-Hellman. Infatti, essendo q un numero primo sufficientemente grande, l’ordine r del generatore sarà elevato, come richiesto per la sicurezza dell’algoritmo. Soddisfatte queste condizioni, per una terza parte che intercetta la comunicazione è pressoché impossibile ricostruire la chiave scambiata dalle informazioni ottenute. Purtroppo l'algoritmo Diffie-Hellman è vulnerabile all'attacco "Man in the middle" (del terzo uomo interposto). Supponiamo che una terza parte intercetti il parametro A che il client invia al server con cui vuole comunicare. Questa può generare, fingendosi il server, un suo numero B e inviarlo al client scambiando così una chiave di cifratura condivisa. In modo del tutto analogo, questa terza parte può generare una chiave condivisa col server e, da qui in avanti, intercettare e leggere tutta la corrispondenza tra i due. Per prevenire simili attacchi, la soluzione è di usare un ente certificatore che garantisca l'identità dei corrispondenti.
14
2.2 Scambio della chiave di cifratura: classe Dh Analizziamo, quindi, l’implementazione dell’algoritmo Diffie-Hellman realizzata in questa tesi. Nella versione in linguaggio C++ è stata creata la classe Dh, la quale consente lo scambio di una chiave crittografica secondo le modalità previste dall’algoritmo e, inoltre, gestisce la cifratura e la decifratura dei dati attraverso tale chiave. L’interfaccia della classe Dh e l’implementazione dei suoi metodi sono interamente riportate in appendice, rispettivamente in ClassDh.hpp e ClassDh.cpp. Tuttavia, per semplicità di consultazione, ne richiamiamo di seguito la dichiarazione. 14 class Dh 15 { 16 public: 17 mpz_class ReturnKey() const { return K; } 18 int SizeOfK() const { return mpz_sizeinbase(K.get_mpz_t(),
10);} 19 bool GenerateEncryptionKey_Client(int socket, string
ipAddress, int udpPort); 20 bool GenerateEncryptionKey_Server(int socket); 21 string DhEncrypt(const string& toEncryptStr); 22 string DhDecrypt(const char* toDecrypt); 23 24 private: 25 mpz_class p, g, a, b, A, B; 26 mpz_class K; //key 27 mpz_class GetVariable(string varName); 28 mpz_class GeneratePrimeNumber(string name, unsigned numBits); 29 mpz_class FindGenerator(); 30 void GetPgaAndSetA(int choice); 31 bool SendPGA(int socket, string ipAddress, int udpPort); 32 bool ReceiveBandComputeK(int socket); 33 bool ReceivePGA(int socket); 34 bool SendB(int socket); 35 void ComputeBandKey(); 36 int rdtsc(); 37 }; Da notare, innanzitutto, come le variabili dell’algoritmo p, g, a, b, A, B nonché la chiave di cifratura K siano inserite nella parte privata, secondo il principio dell’information hiding (linee 25 e 26). Chi utilizza la classe, quindi, non può accedere direttamente a questi dati ma può solamente ricorrere ai metodi presenti nella sua parte pubblica. Tutto ciò si traduce in una maggior sicurezza poiché la chiave di cifratura e tutte le variabili che permettono di generarla non possono essere modificate in modo indiscriminato ma solamente secondo le modalità previste dalle funzioni pubbliche della classe. Osserviamo, inoltre, come questi parametri siano di tipo mpz_class. Questa, infatti, è la classe messa a disposizione dalla libreria GMP per la gestione di numeri interi molto elevati.
15
Lo scambio della chiave crittografica fra due parti, qui pensate come un client e un server, è realizzato dai metodi pubblici GenerateEncryptionKey_Client() e GenerateEncryptionKey_Server(). Essi utilizzano le funzioni della parte privata della classe Dh per accedere alle variabili dell’algoritmo e impostarne i valori, come evidenziato in figura 2.2. La figura 2.2, riportata di seguito, rappresenta in dettaglio la successione delle chiamate alle funzioni di classe.
Lo scambio della chiave ha inizio col client che chiama il metodo pubblico GenerateEncryptionKey_Client() (linee 182÷209 in appendice). Come evidenziato in figura 2.2, questo metodo prevede l’esecuzione di tre funzioni private della classe Dh, la prima delle quali è GetPgaAndSetA() che imposta i parametri p, g, a, A. L’utente del client può lasciare che sia il calcolatore a generare i due parametri principali p, g oppure inserirli manualmente (linee 184÷194). La seconda possibilità è certamente utile dal punto di vista didattico poiché permette all’utente di osservare il funzionamento dell’algoritmo con diverse combinazioni dei suoi parametri. Tuttavia, la generazione manuale di numeri interi molto elevati che soddisfino i criteri di sicurezza richiesti risulta estremamente complessa. A questo scopo è, quindi, possibile selezionare la prima opzione che garantisce l’affidabilità dell’algoritmo. La funzione GetPgaAndSetA() riceve come parametro d’ingresso un intero choice che rappresenta la scelta fatta dall’utente: 1 se viene scelta la prima opzione, 2 per la seconda (linea 196).
Figura 2.2 - Funzioni per lo scambio della chiave
GenerateEncryptionKey_Client()
GetPgaAndSetA()
SendPGA()
ReceiveBandComputeK()
GenerateEncryptionKey_Server()
ReceivePGA()
ComputeBandKey()
SendB()
client server
16
Nel primo caso, la generazione dei parametri avviene in questo modo:
• p viene creato dalla funzione GeneratePrimeNumber() (linea 24 in appendice) a cui viene passato in ingresso un intero numBits, oltre ad una stringa name che indica il nome della variabile (p in questo caso). La funzione utilizza il metodo BN_generate_prime_ex() fornito dalla libreria OpenSSL per generare un numero primo pseudo-casuale di almeno numBits bit di lunghezza; qui è stato utilizzato un valore di 1024 bit (linea 78 in appendice). Il terzo parametro passato a BN_generate_prime_ex() è un intero di valore uno; questo indica alla funzione di generare un numero primo “sicuro”. Da notare come la libreria OpenSSL utilizzi il tipo di dato BIGNUM e sia, quindi, necessaria una conversione intermedia in una stringa per tradurlo in un intero mpz_class (linea 80 in appendice). In C++ questo passaggio risulta, di fatto, trasparente: grazie all’overload dell’operatore “=”, è, infatti, possibile assegnare all’intero mpz_class la stringa restituita dalla funzione BN_bn2dec() corrispondente al numero primo generato. Il parametro p così creato è, quindi, un numero primo pseudo-casuale di almeno 1024 bit “sicuro”, cioè tale che anche (p-1)/2 è primo.
• g viene generato dalla funzione FindGenerator(). Come visto in precedenza, poiché p è un numero primo “sicuro”, è possibile utilizzare come generatore un numero intero casuale di valore compreso fra 2 e p-2. Questo è esattamente il compito svolto dalla funzione sopraccitata. Innanzitutto viene prodotto un intero casuale size minore o uguale al numero di cifre di p-2. Questa sarà la dimensione del generatore. Infatti, viene generato un numero size di interi casuali che vanno a costituire le cifre di g nel seguente modo: se size è uguale alla dimensione di p-2, allora ogni cifra di g è generata come intero casuale minore di quella corrispondente in p-2 altrimenti come intero casuale compreso tra 0 e 9. Questo garantisce che il generatore sia un numero casuale minore di p-2 (linee 48÷65 in appendice). Se si dovesse ottenere un valore di g pari a 2, la procedura verrebbe ripetuta. Da notare l’utilizzo del numero di cicli di clock della CPU come seme per srand(). Questa informazione è fornita dall’istruzione assembly rdtsc (eseguita dall’omonima funzione della classe Dh) e garantisce una migliore randomizzazione essendo un numero che varia in modo estremamente rapido e difficilmente prevedibile.
Nel secondo caso, invece, viene chiamata la funzione GetVariable() per permettere all’utente del client di inserire i valori di p e g desiderati. Dopodiché, indipendentemente da come sono stati generati i due parametri, all’utente spetta il compito di scegliere e inserire un valore casuale a minore di p (sempre attraverso la funzione GetVariable()); a questo punto A viene calcolato come A = ga mod p (linee 32 e 34 in appendice). Da notare come la funzione mpz_powm() fornita dalla libreria GMP per l’elevamento a potenza esegua interamente il calcolo precedente, compresa l’operazione di modulo. Come si vede nel codice in appendice, infatti, la funzione accetta quattro parametri: rispettivamente A (in cui è salvato il risultato dell’operazione), la base g, l’esponente a e il divisore p.
17
Al client non resta che comunicare p, g e A al server tramite il metodo privato SendPGA(). Come evidenziato in figura 2.2, infatti, contemporaneamente al client, il server esegue la funzione GenerateEncryptionKey_Server() e si mette in attesa di questi tre parametri chiamando ReceivePGA(). Se entrambe le operazioni hanno successo, il server procede generando b, calcolando B e la chiave di cifratura K tramite la funzione privata ComputeBandKey(). All’utente del server viene chiesto, quindi, di inserire un numero intero casuale b diverso da uno ma minore di p; dopodichè si procede al calcolo di B = gb mod p e quindi di K = Ab mod p (linee 174÷178 in appendice). Il server mantiene segreto il numero b scelto e la chiave K ma condivide B chiamando la funzione SendB(). Il client tramite il metodo ReceiveBandComputeK() può infine ricevere B e calcolare K = Ba mod p (linee 109÷118 in appendice). Le chiavi così calcolate dalle due parti risultano identiche come dimostrato in precedenza. Da notare come lo scambio dei dati tra le due parti avvenga, attraverso la rete, secondo le modalità descritte in precedenza al punto 1.3. A questo proposito si evidenziano le chiamate alle funzioni udp_send(), udp_reply() e udp_receive() (linee 95, 109, 163 e 133 in appendice) rispettivamente per inviare e ricevere i dati. Ricordiamo come sia necessario per il client e il server creare il socket prima di utilizzare tali funzioni. Questo passaggio non è gestito direttamente dalla classe Dh e deve, quindi, essere svolto dall’applicazione che la utilizza, come vedremo nell’esempio che sarà illustrato nel seguito del capitolo. 2.2.1 Funzioni crittografiche della classe Dh Come si nota nella sua dichiarazione (linee 21 e 22), la classe Dh comprende le due funzioni DhEncrypt() e DhDecrypt() che utilizzano la chiave crittografica condivisa rispettivamente per la cifratura e la decifratura dei dati. Come detto in precedenza, nell’esempio applicativo realizzato la cifratura avviene eseguendo l’XOR tra la chiave e i dati da trasmettere. La funzione DhEncrypt() riceve in ingresso il parametro toEncryptStr, cioè la stringa di dati da crittografare. Partendo dal primo, viene eseguito l’XOR tra il valore decimale di ciascun carattere di toDecrypt e quello del carattere corrispondente della chiave di cifratura; il risultato è poi memorizzato nella stringa encrypted (linee 230÷235 in appendice). Da notare il cast verso il tipo unsigned char (linea 233), utilizzato per ottenere solamente valori positivi dall’operazione di XOR, contrariamente a quanto accadrebbe se toEncryptStr contenesse caratteri non appartenenti alla tabella ASCII standard. Terminata l’elaborazione, la stringa encrypted che contiene i dati crittografati viene restituita come output dalla funzione. È fondamentale osservare come la lunghezza di toEncryptStr debba essere minore o uguale a quella della chiave, altrimenti una parte delle informazioni non sarebbe cifrata. La classe Dh fornisce, quindi, il selettore SizeOfK() che restituisce esattamente il numero di caratteri della chiave crittografica. Questa informazione permette di suddividere correttamente i dati da trasmettere prima di utilizzare DhEncrypt().
18
La funzione DhDecrypt() esegue, invece, l’operazione di decifratura (linee 239÷258 in appendice). Essa riceve in ingresso la stringa toDecrypt che contiene i dati iniziali crittografati, ovvero una successione di cifre decimali. Ogni terna di caratteri della stringa costituisce, quindi, un numero intero, risultato dell’operazione di XOR eseguita dalla funzione DhEncrypt() appena illustrata. Eseguendo nuovamente l’XOR tra questi interi e i caratteri della chiave di cifratura si ottengono i dati di partenza che vengono memorizzati in decrypted (linea 253). La funzione DhDecrypt() restituisce, quindi, la stringa decrypted contenente i dati decriptati.
19
2.3 Comunicazione criptata: libreria DhDataLib La libreria DhDataLib è stata creata per la trasmissione e la ricezione di dati cifrati utilizzando la classe Dh. Il codice relativo è riportato interamente in appendice (si faccia riferimento alle voci DhDataLib.hpp e DhDataLib.cpp). Di seguito ne richiamiamo l’header: 1 #ifndef DHDATALIB_HPP 2 #define DHDATALIB_HPP 3 4 #include "ClassDh.hpp" 5 #include <fstream> 6 7 bool ReadAndSendData(Dh& srv, int socket, string ipAddress, int
udpPort); 8 bool ReceiveAndDecryptData(Dh& srv, int socket); 9 void SendData(const string& data, Dh& srv, int socket); 10 string ReadFromFile(); 11 string ReadMessage(); 12 void printStrInDec(const string& toPrint); 13 void printStrInHex(const string& toPrint, bool crypted); 14 15 #endif Le due funzioni principali della libreria DhDataLib sono ReadAndSendData() e ReceiveAndDecryptData(). La prima è utilizzata per inviare dati criptati mentre la seconda è impiegata per riceverli e decifrarli. La funzione ReadAndSendData() (linee 71÷105 in appendice) permette, innanzitutto, di scegliere fra tre opzioni:
1) Inviare i dati inseriti da tastiera. 2) Inviare i dati contenuti in un file di testo. 3) Terminare la trasmissione.
Nel primo caso viene chiamata il metodo ReadMessage() che legge i caratteri inseriti da testiera, accoda ad essi la stringa di controllo "EOF~EOF~EOF" che ne indica la fine e memorizza il tutto nella stringa restituita come output. All’interno di ReadAndSendData() i caratteri letti dal metodo appena citato sono assegnati alla stringa message che viene poi inviata grazie alla funzione SendData(), che verrà descritta nel seguito (linee 87 e 88 in appendice). La seconda opzione prevede la trasmissione del contenuto di un file di testo. L’unica differenza rispetto al caso precedente, quindi, sta nel fatto che i dati del file sono letti dalla funzione ReadFromFile() e non da ReadMessage() (linea 93). Nel terzo caso, invece, la stringa di controllo "EOTS~EOTS~EOTS" viene inviata tramite SendData() per indicare al ricevitore la fine della trasmissione. ReadAndSendData() restituisce, infine, un valore booleano che indica se è stata scelta o meno la terza opzione, ovvero se la trasmissione deve essere interrotta (linea 104).
20
Come detto, quindi, è SendData() la funzione che invia effettivamente i dati da trasmettere, dopo averli crittografati grazie alla classe Dh (linee 140÷163 in appendice). Nel punto 2.2.1 del capitolo 2 abbiamo visto come i dati da cifrare debbano essere suddivisi in blocchi di caratteri di lunghezza minore o uguale a quella della chiave crittografica per utilizzare la funzione DhEncrypt(). SendData() deve gestire, quindi, anche questa operazione. Come si evince dal codice, la funzione riceve in ingresso la stringa data da trasmettere e, partendo dal primo, ne copia i caratteri in encryptedStr fino a quando la lunghezza di quest’ultima è pari a quella della chiave crittografica. A questo punto encryptedStr viene cifrata tramite DhEncrypt() ed inoltrata grazie alla funzione udp_reply() della libreria udpsocketlib (linee 152 e 156). Tutti i caratteri dalla stringa encryptedStr vengono, quindi, eliminati e la procedura viene iterata fino a quando data non è stata completamente inviata. La funzione ReceiveAndDecryptData() (linee 35÷69 in appendice) deve svolgere quindi l’operazione inversa e riscostruire interamente i dati iniziali a partire dai frammenti cifrati ricevuti, che vengono prodotti come visto sopra. Ognuna di queste stringhe criptate è ricevuta attraverso la funzione udp_receive() e assegnata al vettore di caratteri buffer (linea 45). Quest’ultimo viene decifrato dalla funzione DhDecrypt() e il risultato è salvato nella stringa bufferDecrypted (linea 49) che viene poi aggiunta a messageDecripted (linea 52). Quando in bufferDecrypted è presente la successione di caratteri "EOF~EOF~EOF" (che viene rimossa), la stringa messageDecripted contiene i dati iniziali trasmessi completamente ricostruiti (linee 59÷66). La funzione ReceiveAndDecryptData() restituisce un valore booleano che sarà vero solamente quando viene ricevuta la stringa "EOTS~EOTS~EOTS" che indica il termine della comunicazione. La libreria DhDataLib presenta, infine, i due metodi printStrInDec() printStrInHex() che permettono di stampare a video rispettivamente i valori decimali ed esadecimali dei caratteri delle stringhe fornite in input (linee 3÷33).
21
2.4 Esempio di utilizzo Vediamo ora un esempio di utilizzo della libreria DhDataLib e della classe Dh. Come illustrato nei capitoli precedenti, due parti, che indichiamo come client e server, grazie alla classe Dh generano una chiave di cifratura condivisa che viene usata dalle funzioni della libreria DhDataLib per crittografare la comunicazione tra di esse. 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 "ClassDh.hpp" 2 #include "udpsocketlib.hpp" 3 #include "DhDataLib.hpp" 4 5 int main(int argc, char *argv[]) 6 { 7 Dh srv; 8 int sk, udpPort; 9 string serverIpAdrress, message; 10 bool done = false; 11 12 if (argc == 1) 13 { 14 cout<<"Server ip address:"; 15 cin>>serverIpAdrress; 16 cout<<"Udp port:"; 17 cin>>udpPort; 18 } 19 else if (argc == 3) 20 { 21 serverIpAdrress = argv[1]; 22 udpPort = atoi(argv[2]); 23 } 24 else 25 { 26 cout<<"Required arguments: server ip address, udp port"<<endl; 27 exit(1); 28 } 29 30 if ((sk = create_udp_server((char*)serverIpAdrress.c_str(),
udpPort)) < 0) 31 { 32 cout<<"Cannot open server socket"<<endl; 33 exit(1); 34 } 35 36 if (!srv.GenerateEncryptionKey_Server(sk)) 37 exit(1); 38 39 do
22
40 { 41 done = ReceiveAndDecryptData(srv, sk); 42 if (!done) 43 done = ReadAndSendData(srv, sk); 44 }while (!done); 45 46 close_udp_socket(sk); 47 cout<<"\n\nExit..\n\n"; 48 return 0; 49 }
Client 1 #include "ClassDh.hpp" 2 #include "udpsocketlib.hpp" 3 #include "DhDataLib.hpp" 4 5 int main(int argc, char *argv[]) 6 { 7 Dh clnt; 8 int sk, udpPort; 9 string serverIpAdrress; 10 bool done = false; 11 12 if (argc == 1) 13 { 14 cout<<"Server ip address:"; 15 cin>>serverIpAdrress; 16 cout<<"Udp port:"; 17 cin>>udpPort; 18 } 19 else if (argc == 3) 20 { 21 serverIpAdrress = argv[1]; 22 udpPort = atoi(argv[2]); 23 } 24 else 25 { 26 cout<<"Required arguments: server ip address, udp port"<<endl; 27 exit(1); 28 } 29 30 if ((sk = create_udp_client()) < 0) 31 { 32 cout<<"Cannot open client socket"<<endl; 33 exit(1); 34 } 35 36 if (!clnt.GenerateEncryptionKey_Client(sk, serverIpAdrress
,udpPort)) 37 exit(1); 38 39 do 40 { 41 done = ReadAndSendData(clnt, sk); 42 if (!done)
23
43 done = ReceiveAndDecryptData(clnt, sk); 44 }while (!done); 45 46 close_udp_socket(sk); 47 cout<<"\n\nExit.."<<endl<<endl; 48 return 0; 49 } Innanzitutto, entrambe le parti creano il socket UDP grazie alle due funzioni create_udp_server() e create_udp_client() messe a disposizione dalla libreria udpsocketlib. Come visto nel punto 1.3 del capitolo 1, create_udp_server() riceve in ingresso l’indirizzo IPv4 del server e la porta locale su cui attenderà i dati per associarle al socket (linea 30, Server). Queste informazioni devono essere fornite come argomento della funzione main del server direttamente dalla linea di comando, altrimenti il loro inserimento viene richiesto esplicitamente dal programma (linee 12÷28, Server). Le stesse informazioni devono essere fornite all’applicazione del client; in questo caso però non saranno associate al socket ma saranno utilizzate, quando necessario, solamente per inviare dati al server. Da notare, infatti, come create_udp_client() non riceva parametri in ingresso (linea 30, Client). Una volta creato il socket, client e server possono procedere alla generazione di una chiave crittografica condivisa, come illustrato in precedenza in questo capitolo. Vengono chiamate, infatti, da entrambe le parti le funzioni della classe Dh GenerateEncryptionKey_Server() e GenerateEncryptionKey_Client() per lo scambio della chiave (linea 36, Client e Server). Se l’operazione ha successo, le due parti possono, infine, utilizzare la libreria DhDataLib per comunicare in modo protetto attraverso la rete. Come si vede nel codice, entrambe le parti entrano in un ciclo do..while in cui vengono eseguite alternativamente le due funzioni ReadAndSendData() ReceiveAndDecryptData() (linee 39÷44). La comunicazione ha inizio con il client che chiama ReadAndSendData() per inviare i dati al server, il quale a sua volta esegue ReceiveAndDecryptData() per riceverli. Dopodiché le parti si scambiano i ruoli e il ciclo continua fino a quando una delle due non termina la comunicazione. Ricordiamo, infatti, che entrambe le funzioni restituiscono un valore booleano che indica il raggiungimento di tale condizione, come visto nel punto 2.3 del capitolo 2. A questo punto da entrambe le parti il socket viene chiuso e le applicazioni terminano (linee 46 e 48).
24
2.5 Confronto con la versione in C Nella versione in linguaggio C è stata mantenuta la stessa struttura di quella in C++ in modo tale che lo scambio di una chiave crittografica così come la cifratura, la decifratura e la trasmissione dei dati avvengano sempre secondo le modalità descritte in precedenza. Tuttavia esistono alcune differenze fra le due versioni, dovute principalmente alle diverse caratteristiche dei linguaggi di programmazione, che si ritiene utile analizzare. Innanzitutto, non essendo presente il concetto di classe, nella versione in linguaggio C è stato necessario creare la libreria DhLib per lo scambio della chiave crittografica secondo l’algoritmo Diffie-Hellman. Il codice è riportato interamente in appendice (si faccia riferimento alle voci DhLib.h e DhLib.c). Di seguito ne richiamiamo solamente l’header. 13 // Create type bool (not present in c). 14 typedef enum { false, true } bool; 15 16 struct dhStruct 17 { 18 mpz_t p, g, a, b, A, B, K; 19 }; 20 21 void GeneratePrimeNumber(char* name, mpz_t num, unsigned numBits); 22 void FindGenerator(mpz_t q, mpz_t g); 23 bool GenerateEncryptionKey_Client(int socket, char* ipAddress, int
udpPort, struct dhStruct* dh); 24 void GetPgaAndSetA(struct dhStruct* dh, int choice); 25 bool SendPGA(int socket, char* ipAddress, int udpPort, struct
dhStruct* dh); 26 bool ReceiveBandComputeK(int socket, struct dhStruct* dh); 27 bool GenerateEncryptionKey_Server(int socket, struct dhStruct* dh); 28 bool ReceivePGA(int socket, struct dhStruct* dh); 29 void ComputeBandKey(struct dhStruct* dh); 30 bool SendB(int socket, struct dhStruct* dh); 31 char* DhEncrypt(char* toEncryptStr, char* key); 32 char* DhDecrypt(char* toDecrypt, char* key); 33 void GetVariable(char* varName, mpz_t var); 34 int rdtsc(); Dal confronto con la versione in C++ si nota come le funzioni della libreria DhLib siano le stesse presenti nella classe Dh (ma opportunamente tradotte in linguaggio C) e svolgano le stesse operazioni. Lo scambio della chiave di cifratura, quindi, avviene sempre attraverso i due metodi GenerateEncryptionKey_Server() e GenerateEncryptionKey_Client() e la successione delle funzioni chiamate è la stessa della versione in C++. All’inizio del capitolo 2, al punto 2.2 si è vista l’importanza delle classi dal punto di vista della sicurezza e robustezza del software: essendo contenute nella parte privata, le variabili dell’algoritmo e la chiave di cifratura sono invisibili a chi utilizza la classe ed è possibile accedervi solo attraverso i suoi metodi pubblici.
25
Data l’assenza delle classi in C, viene meno anche la possibilità di sfruttare il principio dell’information hiding. Nella libreria DhLib le variabili dell’algoritmo non sono più campi privati di una classe ma vengono solamente inserite all’interno di una struct dhStruct (linee 16÷19). Chi utilizza la libreria può, quindi, accedervi direttamente in modo analogo a quanto avviene per le funzioni di libreria a cui la struct contenente i dati viene passata per riferimento. Tutto ciò si traduce in un livello di sicurezza minore rispetto al caso precedente. Questo aspetto è evidente nei programmi scritti come esempio di utilizzo delle librerie DhLib e DhDataLib. Il codice è riportato in appendice alle voci Dh_s.c Dh_c.c. In entrambi i programmi, dopo aver scambiato la chiave crittografica, questa viene convertita in una stringa (linea 39 in appendice). Per fare questo si accede direttamente al dato contenuto nella struct tramite l’operatore “.”. In questo caso il valore della chiave K viene solamente letto, ma nello stesso modo è possibile modificare il dato. Questo vale ovviamente per tutte le altre variabili. Da notare, quindi, la differenza con l’utilizzo della classe Dh in cui la chiave crittografica possa essere solamente letta attraverso il selettore ReturnKey() e non sia possibile modificare direttamente nessuna delle variabili. Da notare, inoltre, come le variabili dell’algoritmo siano rappresentate da oggetti di tipo mpz_t (linea 18) a differenza della classe Dh in cui sono di tipo mpz_class. La classe mpz_class presente nella libreria GMP è stata creata, infatti, in C++ per la gestione degli oggetti mpz_t: in altre parole la loro inizializzazione, il rilascio della memoria loro riservata e l’overloading degli operatori. Per fare ciò tale classe utilizza le funzioni specifiche della libreria GMP che operano su oggetti di tipo mpz_t. Questo semplifica notevolmente l’utilizzo di tale tipo di dato in C++; nel codice scritto, infatti, mpz_powm() è l’unica funzione specifica che è stato necessario utilizzare esplicitamente. L’overloading degli operatori d’input/output, di quelli relazionali e di quelli matematici ha permesso di trattare gli oggetti mpz_t come i tipi di dato nativi del C++. Tutto ciò non è più vero per il linguaggio C. Da notare, a questo proposito, nel codice dei programmi Dh_c.c e Dh_s.c come le variabili mpz_t della struct dhStruct debbano essere inizializzate tramite la funzione mpz_inits() prima di utilizzarle e la memoria debba, infine, essere liberata chiamando mpz_clears() (linee 11 e 48 in appendice). Nel codice della libreria DhLib si nota, inoltre, l’utilizzo delle seguenti funzioni:
• mpz_set() che svolge il compito dell’operatore di assegnazione “=” • mpz_sub_ui() per sottrarre un intero unsigned long ad uno mpz_t • mpz_cmp_ui() e mpz_cmp() per comparare un intero mpz_t rispettivamente a
un intero unsigned long e a un altro mpz_t. queste funzioni svolgono, quindi, il compito degli opertatori relazionali “>”, “>=”, “<”, “<=” e “==”
• gmp_printf() per visualizzare gli interi mpz_t sullo standard output • gmp_scanf() per acquisire interi mpz_t dallo standard input
L’uso di queste funzioni non incrementa notevolmente la complessità nella scrittura del codice in linguaggio C; in alcuni casi, tuttavia, la sua leggibilità può risentirne, specialmente se confrontata al C++.
26
È stato, infine, necessario tradurre in C anche la libreria DhDataLib, il cui codice è riportato in appendice alle voci DhDataLib.h DhDataLib.c. Come fatto in precedenza, richiamiamo di seguito la dichiarazione delle funzioni: 6 bool ReadAndSendData(char* key, int socket); 7 bool ReceiveAndDecryptData(char *key, int socket); 8 void SendData(char* data, char* key, int socket); 9 char* ResizeDinamicString(char* toResize, int newDim); 10 char* ReadMessage(); 11 char* ReadFromFile(); 12 void printStrInDec(char* toPrint); 13 void printStrInHex(char* toPrint, bool crypted); Anche in questo caso sono state mantenute tutte le funzioni presenti nella versione in C++ e quindi la comunicazione criptata fra due parti avviene esattamente nello stesso modo descritto in precedenza. È importante, tuttavia, sottolineare come il C++ consenta un utilizzo migliore e più semplice delle stringhe di caratteri rispetto al C, in particolare quelle di dimensione variabile. In C++ abbiamo utilizzato la classe string che gestisce autonomamente il ridimensionamento delle stringhe per l’aggiunta di singoli caratteri o di altre stringhe tramite la funzione push_back() e l’operatore “+” sovraccaricato. Queste caratteristiche sono state utili nelle funzioni ReadMessage() e ReadFromFile() in quanto hanno reso particolarmente semplice l’inserimento dei caratteri letti rispettivamente dallo standard input e da un file in una stringa e l’aggiunta in coda della sequenza di controllo "EOF~EOF~EOF". In C, invece, le stringhe sono trattate come vettori di caratteri ed è necessario gestire esplicitamente il loro ridimensionamento. A questo scopo si è scelto di ricorrere all’allocazione dinamica della memoria tramite la funzione malloc(). È stato così possibile creare la funzione ResizeDinamicString() (linee 113÷126 in appendice) per ridimensionare una stringa di caratteri in modo che possa contenere tutti i dati, senza perdita di informazioni. Come si vede nel codice delle funzioni ReadMessage() e ReadFromFile() (linee 138, 143, 165 e 170 in appendice), ResizeDinamicString() viene chiamata tutte le volte che i caratteri letti in input devono essere aggiunti a una stringa, prima di poter utilizzare la funzione strcat(). L’allocazione dinamica richiede, infine, di prestare particolare attenzione al corretto rilascio della memoria occupata. Da notare a questo proposito le chiamate alla funzione free() (linee 75, 79 e 122) che svolge quest’ultima operazione.
27
3 Algoritmo RSA 3.1 Descrizione Nel 1978 Ronald Rivest, Adi Shamir e Leonard Adleman (tre ricercatori del MIT) proposero, nell’articolo “A Method for Obtaining Digital Signatures and Public-Key Cryptosystems” [7], l’algoritmo di crittografia a chiave asimmetrica noto come “algoritmo RSA” (il cui nome è costituito dalle iniziali dei suoi inventori). La crittografia asimmetrica è basata sull’utilizzo di due chiavi distinte per la cifratura e per la decifratura delle informazioni, dette rispettivamente chiave pubblica e chiave privata. Quando due parti devono comunicare tra loro, ciascuna di esse non deve fare altro che cifrare i dati con la chiave pubblica del destinatario, che, una volta ricevuti, sarà in grado di decifrarli con la propria chiave privata. In questo sistema, quindi, ogni parte possiede una coppia di chiavi: quella pubblica che deve essere, appunto, distribuita e resa di pubblico dominio perché consente di codificare le informazioni e quella privata (che deve essere mantenuta segreta) che consente di decifrarle. Da notare come la chiave pubblica possa essere condivisa attraverso un canale insicuro: chi la ottiene, infatti, può soltanto inviare informazioni cifrate al suo proprietario, ma non è in grado di ottenere e decodificare i dati diretti a quest’ultimo dal momento che non è a conoscenza della sua chiave privata. Poiché le due chiavi non sono indipendenti tra loro, la sicurezza del meccanismo di cifratura è garantita dalla difficoltà di ricostruire una delle due chiavi a partire dall’altra. Nell’algoritmo RSA la generazione delle chiavi avviene nel seguente modo:
1. si scelgono due numeri primi casuali p e q sufficientemente grandi in modo da garantire la sicurezza dell’algoritmo
2. si calcola n = p×q e φ = (p – 1)×(q – 1) 3. si sceglie un numero 1 < e < φ coprimo con φ. Come indicato dagli autori
dell’algoritmo nell’articolo [7], si può utilizzare, ad esempio, un numero primo casuale maggiore del max(p, q); l’importante è che questo sia scelto da un insieme sufficientemente grande.
4. si calcola il numero d tale che e×d = 1 mod φ La chiave pubblica è costituita dalla coppia (n, e), mentre quella privata da (n, d). Vediamo, quindi, come utilizzare le chiavi generate per la cifratura e decifratura delle informazioni. Innanzitutto, i dati da codificare devono essere suddivisi in blocchi di dimensione compatibile con l’operazione di cifratura e opportunamente convertiti in decimale (come vedremo nel seguito del capitolo). Indicando con m il valore decimale di ciascun blocco, la sua dimensione deve essere tale per cui 0 < m < n. La cifratura delle informazioni avviene, grazie alla chiave pubblica, attraverso l’operazione c = me mod n; c rappresenta, così, ciascun blocco di dati crittografato. Per decifrare c utilizzando la chiave privata, è necessario calcolare t = cd mod n, dove indichiamo con t il risultato dell’ultima operazione. Affinché la decifrazione abbia successo, t deve necessariamente coincidere con c. La dimostrazione della correttezza dell’algoritmo RSA verrà fornita nel seguito del capitolo.
28
La sua sicurezza, invece, è basata sulla difficoltà di scomporre in fattori primi i numeri interi molto elevati. Come evidenziato in [8], se si riuscisse a fattorizzare il numero n (noto pubblicamente), allora sarebbe possibile trovare p e q e da questi φ. Di conseguenza, conoscendo e, si potrebbe facilmente determinare d tramite l’algoritmo di Euclide e violare, quindi, il meccanismo di cifratura. Tuttavia non è stato ancora trovato un metodo per la fattorizzazione di numeri molto grandi che non sia computazionalmente proibitivo. Per questo motivo l’algoritmo RSA è considerato sicuro se si utilizzano p e q sufficientemente elevati, cioè di dimensioni tali per cui n sia un numero intero di almeno 200 cifre decimali (come suggerito dagli ideatori dell’algoritmo nell’articolo [7]). 3.1.1 Correttezza dell’algoritmo La correttezza dell’algoritmo viene dimostrata dagli autori nell’articolo [7] sfruttando il piccolo teorema di Fermat di cui riportiamo solamente l’enunciato. Sia p un numero primo e a un intero non divisibile da p, il teorema afferma che:
a(p – 1) = 1 mod p Viene utilizzato, inoltre, un importante risultato del teorema cinese del resto, il quale afferma che la relazione a = b mod pq risulta equivalente al sistema costituito da:
• a = b mod p • a = b mod q
Si vuole dimostrare, quindi, che i dati decifrati indicati con t coincidono con quelli originali m. Per le proprietà dell’aritmetica modulare possiamo scrivere:
t = cd mod n = (me mod n) d mod n = (me) d mod n = med mod n dove c indica i dati m cifrati, come visto in precedenza. Per come sono stati costruiti e e d, si ha che:
e×d = 1 mod φ = 1 mod ((p – 1)×(q – 1))
Di conseguenza:
e×d = 1 mod (p – 1) e analogamente
e×d = 1 mod (q – 1)
29
Per il piccolo teorema di Fermat si ha che:
med = m mod p e med = m mod q Siccome p e q sono numeri diversi e primi, possiamo applicare il teorema cinese del resto, ottenendo che:
med = m mod (p×q) = m mod n e quindi:
t = cd mod n = med mod n = m mod n
Abbiamo quindi dimostrato che, decrittando i dati cifrati con la chiave pubblica (n, e) tramite quella privata (n, d), si ottengono nuovamente i dati originali.
30
3.2 Generazione delle chiavi di cifratura: classe Rsa Analizziamo, quindi, l’implementazione dell’algoritmo RSA realizzata in questa tesi. Nella versione in linguaggio C++, la classe Rsa è stata creata per la generazione delle chiavi di cifratura pubblica e privata e per la gestione delle operazioni di criptazione e di decriptazione dei dati, secondo le modalità appena descritte. Come vedremo, sono presenti, inoltre, le due funzioni necessarie per la condivisione e per la ricezione della chiave pubblica. L’interfaccia della classe Rsa e l’implementazione dei suoi metodi sono interamente riportate in appendice, rispettivamente in ClassRsa.hpp e ClassRsa.cpp. Tuttavia, per semplicità di consultazione, ne richiamiamo di seguito la dichiarazione. 14 class Rsa 15 { 16 public: 17 mpz_class N() const { return n;} 18 int SizeOfN() const { return mpz_sizeinbase(n.get_mpz_t(),
10);} 19 mpz_class E() const { return e;} 20 mpz_class D() const { return d;} 21 void GenerateEncryptionKeys(); 22 bool SendPublicKey(int socket, string ipAddress, int udpPort); 23 bool ReceivePublicKey(int socket); 24 void RsaEncrypt(string& toEncryptStr); 25 string RsaDecrypt(const char* toDecrypt); 26 27 private: 28 mpz_class p, q, phi; 29 mpz_class n, e, d; //keys 30 mpz_class ComputeGCD(const mpz_class& x, const mpz_class& y); 31 mpz_class GetRandomE(); 32 void GeneratePQandComputeNPhi(); 33 mpz_class GeneratePrimeNumber(string name, unsigned numBits); 34 void FindE(); 35 void ComputeD(); 36 int rdtsc(); 37 }; Innanzitutto notiamo che, come già visto per la classe Dh, le variabili dell’algoritmo (ovvero p, q e phi) e le chiavi di cifratura (costituite da n, e e d) sono di tipo mpz_class e sono inserite nella parte privata della classe. Questo garantisce una maggior sicurezza poiché le chiavi non sono direttamente accessibili a chi usa la classe e, quindi, possono essere generate e modificate solo dalle funzioni della classe stessa. In particolare, la chiave pubblica e quella privata vengono create dalla funzione GenerateEncryptionKeys() (linea 21). Quest’ultima, come si evince dal codice (linee 131, 132 e 133 in appendice), sfrutta i metodi privati della classe Rsa per impostare i valori delle variabili dell’algoritmo e ottenere, infine, le chiavi crittografiche.
31
Il dettaglio delle funzioni di classe chiamate all’interno di GenerateEncryptionKeys() è riportato nella figura sottostante.
Come indicato in figura 3.1, la prima funzione che viene chiamata in GenerateEncryptionKeys() è GeneratePQandComputeNPhi(). L’algoritmo RSA, infatti, ha inizio con la generazione di due numeri primi elevati p e q e con il calcolo di n e φ (nel codice quest’ultima variabile è stata indicata con phi). Questo è esattamente il compito svolto da GeneratePQandComputeNPhi() che, come si può notare nel codice (linee 22, 23, 26 e 27 in appendice), utilizza la funzione GeneratePrimeNumber() per generare i due numeri primi casuali p e q e, infine, calcola n = p×q e phi = (p – 1) × (q – 1). La funzione GeneratePrimeNumber() è la stessa utilizzata nella classe Dh; si rimanda, quindi, al punto 2.2 del capitolo 2 per maggiori dettagli. Da notare, invece, come la dimensione scelta per p e q sia di 512 bit. In questo modo n sarà un numero intero di circa 308 cifre decimali tale, quindi, da garantire la sicurezza dell’algoritmo: la condizione di 200 cifre decimali indicata nell’articolo [7] risulta, infatti, ampiamente soddisfatta. A questo punto viene chiamata la funzione FindE() per calcolare il numero intero e che costituisce, in coppia con n, la chiave pubblica e che, come visto in precedenza, deve essere coprimo e minore di phi. La funzione FindE(), quindi, trova il numero e nel seguente modo. Innanzitutto viene chiamata la funzione GetRandomE() (linea 64
GenerateEncryptionKeys()
2) FindE()
3) ComputeD()
GeneratePrimeNumber()
GetRandomE()
ComputeGCD()
1) GeneratePQandComputeNPhi()
Figura 3.1 - Funzioni per la creazione delle chiavi
32
in appendice) che assegna ad e un numero intero casuale compreso tra il max(p, q) e (phi – 1). Dopodiché, come si può notare nel codice (linee 65÷72 in appendice), viene calcolato il massimo comun divisore fra e e phi tramite la funzione ComputeGCD() che implementa l’algoritmo di Euclide:
• se è pari ad 1, i due numeri sono coprimi e quindi e è corretto. • se è diverso da 1, allora i due numeri non sono coprimi. Si diminuisce e di 1 e
si ricalcola il massimo comun divisore fino ad ottenere un valore corretto per e. Se quest’ultimo dovesse diventare pari a p o a q, allora il ciclo si ripeterebbe ricominciando dalla chiamata a GetRandomE().
La funzione FindE() converge molto rapidamente ad un valore corretto per il numero e, cioè tale per cui 1 < e < phi ed è coprimo con phi. Inoltre, e viene individuato all’interno di un insieme molto ampio poiché il max(p, q) e (phi – 1) differiscono di molti ordini di grandezza. Ciò significa che la sicurezza dell’algoritmo RSA è garantita. A questo punto viene chiamata la funzione ComputeD() per calcolare il numero d e ottenere, quindi, la chiave privata costituita dalla coppia (d, n). Come detto in precedenza, d deve essere calcolato in modo tale che e×d = 1 mod φ ovvero d è l’inverso moltiplicativo di e. La funzione ComputeD() sfrutta, quindi, l’algoritmo di Euclide esteso per determinare d; senza entrare nei dettagli matematici, utilizziamo l’implementazione ripotata nel sito [9] ma opportunamente modificata per poter lavorare con numeri interi molto grandi, ovvero con dati di tipo mpz_class. Trovato d, la funzione GenerateEncryptionKeys() giunge al termine. Sono state generate, quindi, entrambe le chiavi di cifratura: la chiave pubblica (n, e) e quella privata (n, d). Tuttavia, per poterle utilizzare nella cifratura di una comunicazione che avviene tra due parti, è necessario condividere la chiave pubblica. Per questo motivo la classe Rsa comprende le due funzioni SendPublicKey() e ReceivePublicKey() che possono essere usate rispettivamente per inviare e ricevere la chiave pubblica. Come si vede dal codice (linee 142÷147 in appendice), la prima non fa altro che convertire n ed e in due stringhe che poi vengono inviate grazie alla funzione udp_send(); la seconda (linee 165÷173 in appendice) esegue l’operazione inversa ricevendo le due stringhe tramite udp_receive() e convertendole in numeri interi mpz_class. Un esempio di utilizzo delle funzioni della classe Rsa verrà proposto nel seguito del capitolo. La classe Rsa comprende, infine, i due metodi pubblici RsaEncrypt() e RsaDecrypt() (line 24 e 25) per la cifratura e la decifratura dei dati. La prima riceve in ingresso la stringa toEncryptStr da criptare, la converte nel numero intero encrypted di tipo mpz_class e calcola, grazie alla funzione mpz_powm() della libreria GMP, encrypted = encryptede mod n (linee 187 e 190 in appendice). È importante notare come la funzione RsaEncrypt() richieda che toEncryptStr contenga il valore numerico dei dati da cifrare e che questo sia minore di n (come visto in precedenza nel capitolo). Di conseguenza, per poter utilizzare correttamente tale funzione, è necessario che i dati siano già stati convertiti in decimale e opportunamente suddivisi. A questo punto encrypted, dopo essere stato tradotto in una stringa, viene memorizzato in toEncryptStr (linea 192 in appendice) che, in questo modo, conterrà i dati iniziali cifrati.
33
Per decifrare i dati, la funzione RsaDecrypt() deve necessariamente svolgere l’operazione inversa. Essa riceve in ingresso la stringa toDecrypt che viene convertita nel numero intero decrypted (linea 200 in appendice). Dopodiché la funzione calcola, per mezzo di mpz_powm(), decrypted = decryptedd mod n e restituisce il risultato di tale operazione sotto forma di stringa di caratteri. Poiché i dati vengono convertiti in decimale e suddivisi prima di essere cifrati, dopo la decifratura dovrà essere gestita la loro ricostruzione. Di seguito vedremo come tutte queste operazioni siano realizzate dalle funzioni della libreria RsaDataLib.
34
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
35
(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.
36
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";
37
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 ReceivePublicKey() (linee 33 e 34, server). Ovviamente, nel caso in cui venga
38
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.
39
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
40
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.
41
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.
42
43
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.
44
45
Bibliografia [1] «Corso di Reti di Calcolatori», 18/12/2014,
http://web.diegm.uniud.it/pierluca/public_html/teaching/reti_di_calcolatori/reti_di_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. [9] «Modular inverse», 18/12/2014, http://rosettacode.org/wiki/Modular_inverse
46
47
Appendici File in C++ ClassDh.hpp 1 #ifndef DH_HPP 2 #define DH_HPP 3 4 #include <cassert> 5 #include <iostream> 6 #include <gmpxx.h> 7 #include <string> 8 #include <unistd.h> 9 #include "udpsocketlib.hpp" 10 #include "openssl/bn.h" 11 12 using namespace std; 13 14 class Dh 15 { 16 public: 17 mpz_class ReturnKey() const { return K; } 18 int SizeOfK() const { return mpz_sizeinbase(K.get_mpz_t(),
10); } 19 bool GenerateEncryptionKey_Client(int socket, string
ipAddress, int udpPort); 20 bool GenerateEncryptionKey_Server(int socket); 21 string DhEncrypt(const string& toEncryptStr); 22 string DhDecrypt(const char* toDecrypt); 23 24 private: 25 mpz_class p, g, a, b, A, B; 26 mpz_class K; //key 27 mpz_class GetVariable(string varName); 28 mpz_class GeneratePrimeNumber(string name, unsigned numBits); 29 mpz_class FindGenerator(); 30 void GetPgaAndSetA(int choice); 31 bool SendPGA(int socket, string ipAddress, int udpPort); 32 bool ReceiveBandComputeK(int socket); 33 bool ReceivePGA(int socket); 34 bool SendB(int socket); 35 void ComputeBandKey(); 36 int rdtsc(); 37 }; 38 #endif
48
ClassDh.cpp 1 #include "ClassDh.hpp" 2 3 mpz_class Dh::GetVariable(string varName) 4 { 5 mpz_class var; 6 string varStr; 7 8 do 9 { 10 cout<<"\nEnter "<<varName<<": "; 11 cin>>varStr; 12 var.set_str(varStr, 10); 13 if (var <= 0) 14 cout<<"\nError. Please insert a number > 0"<<endl; 15 }while (var <= 0); 16 return var; 17 } 18 19 void Dh::GetPgaAndSetA(int choice) 20 { 21 if (choice == 1) 22 { 23 cout<<"\n\nGenerating 'p' and 'g'...\n\n"; 24 p = GeneratePrimeNumber("p", 1024); 25 g = FindGenerator(); 26 } 27 else if (choice == 2) 28 { 29 p = GetVariable("'p'"); 30 g = GetVariable("'g'"); 31 } 32 a = GetVariable("'a', such that 1<a<p"); 33 assert(a < p && a > 1); 34 mpz_powm(A.get_mpz_t(), g.get_mpz_t(), a.get_mpz_t(),
p.get_mpz_t()); 35 cout<<"\n\tA=g^a (mod p)="<<A<<endl; 36 } 37 38 mpz_class Dh::FindGenerator() 39 { 40 mpz_class g; 41 string pStr, gStr; 42 int size, temp; 43 bool sameSize; 44 g = p – 2; 45 pStr = p.get_str(10); 46 do 47 { 48 srand(rdtsc()); 49 size = (rand() % (pStr.size() – 1 )) + 1; 50 sameSize = (size == (int)pStr.size()); 51 for (int i = 0; i < size; i++) 52 { 53 srand(rdtsc());
49
54 if (sameSize) 55 { 56 temp = pStr[i] -‐ '0'; 57 if (temp != 0) 58 temp = rand() % temp; 59 } 60 else 61 temp = rand() % 9; 62 gStr.push_back((char) temp + '0'); 63 } 64 g.set_str(gStr, 10); 65 }while (g == 2); 66 cout<<"\n\tg="<<g<<endl; 67 return g; 68 } 69 70 mpz_class Dh::GeneratePrimeNumber(string name, unsigned numBits) 71 { 72 bool ctrl = false; 73 mpz_class num; 74 BIGNUM primeNum; 75 BN_init(&primeNum); 76 do 77 { 78 ctrl = BN_generate_prime_ex(&primeNum, numBits, 1, NULL, NULL,
NULL); 79 }while (!ctrl); 80 num = BN_bn2dec(&primeNum); 81 BN_free(&primeNum); 82 cout<<"\n\t"<<name<<"="<<num<<endl; 83 return num; 84 } 85 86 bool Dh::SendPGA(int socket, string ipAddress, int udpPort) 87 { 88 string toSend[3]; 89 toSend[0] = p.get_str(10); 90 toSend[1] = g.get_str(10); 91 toSend[2] = A.get_str(10); 92 cout<<"\nSending p,g and A..."<<endl<<endl; 93 for (unsigned count = 0; count < 3; count++) 94 { 95 if (!udp_send(socket, (char*)toSend[count].c_str(),
(char*)ipAddress.c_str(), udpPort)) 96 { 97 cout<<"Unable to send p, g and A..."<<endl<<endl; 98 return false; 99 } 100 sleep(1); 101 } 102 return true; 103 } 104 105 bool Dh::ReceiveBandComputeK(int socket) 106 { 107 char strB[BUFSIZ+1]; 108
50
109 if (udp_receive(socket, strB) == -‐1) 110 { 111 cout<<"Unable to receive B!"<<endl<<endl; 112 return false; 113 } 114 cout<<"B received!\n\n\tB="<<strB<<endl<<endl; 115 B.set_str(strB, 10); 116 if (B > 0) 117 { 118 mpz_powm(K.get_mpz_t(), B.get_mpz_t(), a.get_mpz_t(),
p.get_mpz_t()); 119 return true; 120 } 121 else 122 return false; 123 } 124 125 bool Dh::ReceivePGA(int socket) 126 { 127 char strPGA[3][BUFSIZ+1]; 128 unsigned count = 0; 129 130 cout<<"\n\nWaiting for p, g and A...\n\n"; 131 do 132 { 133 if (udp_receive(socket, strPGA[count]) == -‐1) 134 { 135 cout<<"Unable to receive p,g and A!"<<endl<<endl; 136 return false; 137 } 138 count++; 139 }while (count < 3); 140 141 p.set_str(strPGA[0], 10); 142 g.set_str(strPGA[1], 10); 143 A.set_str(strPGA[2], 10); 144 145 if (p != 0 && g != 0 && A !=0) 146 { 147 cout<<"Data received:"<<endl<<endl; 148 cout<<"\n\tp="<<p<<"\n\n\tg="<<g<<"\n\n\tA="<<A<<endl<<endl; 149 return true; 150 } 151 else 152 { 153 cout<<"Error. Wrong data."<<endl<<endl; 154 return false; 155 } 156 } 157 158 bool Dh::SendB(int socket) 159 { 160 string toSend = B.get_str(10); 161 162 cout<<"Sending B..."<<endl<<endl; 163 if (!udp_reply(socket, (char*)toSend.c_str())) 164 {
51
165 cout<<"Unable to send B..."<<endl<<endl; 166 return false; 167 } 168 cout<<"Data sent!"<<endl<<endl; 169 return true; 170 } 171 172 void Dh::ComputeBandKey() 173 { 174 b = GetVariable("'b', such that 1<b<p"); 175 assert(b > 1 && b < p); 176 177 mpz_powm(B.get_mpz_t(), g.get_mpz_t(), b.get_mpz_t(),
p.get_mpz_t()); 178 mpz_powm(K.get_mpz_t(), A.get_mpz_t(), b.get_mpz_t(),
p.get_mpz_t()); 179 cout<<"\n\tB=g^b (mod p)="<<B<<endl<<endl; 180 } 181 182 bool Dh::GenerateEncryptionKey_Client(int socket, string ipAddress,
int udpPort) 183 { 184 int choice; 185 cout<<"\n\nAutomatically generate p,g?\n"; 186 cout<<"1)Yes.\n"; 187 cout<<"2)No, I insert them.\n"; 188 do 189 { 190 cout<<"\nChoose an option:"; 191 cin>>choice; 192 if (choice != 1 && choice != 2) 193 cout<<"\nInvalid choice!\n\n"; 194 }while (choice != 1 && choice != 2); 195 196 GetPgaAndSetA(choice); 197 198 if (SendPGA(socket, (char*)ipAddress.c_str(), udpPort)) 199 { 200 cout<<"Data sent!\n\nWaiting for B..."<<endl<<endl; 201 if (ReceiveBandComputeK(socket)) 202 { 203 cout<<"\n\nThe encryption key is:\n\n\tK=B^a (mod
p)="<<K<<endl<<endl; 204 return true; 205 } 206 } 207 cout<<"\nUnable to create the key."<<endl; 208 return false; 209 } 210 211 bool Dh::GenerateEncryptionKey_Server(int socket) 212 { 213 if (ReceivePGA(socket)) 214 { 215 ComputeBandKey(); 216 if (SendB(socket)) 217 {
52
218 cout<<"\n\nThe encryption key is:\n\n\tK=A^b (mod p)="<<K<<endl<<endl;
219 return true; 220 } 221 } 222 cout<<"\nUnable to create the key."<<endl; 223 return false; 224 } 225 226 string Dh::DhEncrypt(const string& toEncryptStr) 227 { 228 string key, encrypted; 229 char ch[4]; 230 key = K.get_str(10); 231 for (unsigned i = 0; i < toEncryptStr.size(); i++) 232 { 233 sprintf(ch, "%.3d", (unsigned char)toEncryptStr[i]^ (unsigned
char)key[i]); 234 encrypted += ch; 235 } 236 return encrypted; 237 } 238 239 string Dh::DhDecrypt(const char* toDecrypt) 240 { 241 string key, decrypted; 242 char ch[4]; 243 unsigned aux = 0, count = 0; 244 key = K.get_str(10); 245 decrypted.append(toDecrypt); 246 for (unsigned i = 0; i < strlen(toDecrypt); i++) 247 { 248 ch[aux] = toDecrypt[i]; 249 aux++; 250 if (aux == 3) 251 { 252 ch[3] = '\0'; 253 decrypted.push_back((unsigned char) atoi(ch)^key[count]); 254 aux = 0, count++; 255 } 256 } 257 return decrypted; 258 } 259 260 int Dh::rdtsc() 261 { 262 __asm__ __volatile__("rdtsc"); 263 } 264 265 void error_handler(char *message) 266 { 267 cout<<"fatal error: "<<message<<endl; 268 return; 269 }
53
DhDataLib.hpp 1 #ifndef DHDATALIB_HPP 2 #define DHDATALIB_HPP 3 4 #include "ClassDh.hpp" 5 #include <fstream> 6 7 bool ReadAndSendData(Dh& srv, int socket); 8 bool ReceiveAndDecryptData(Dh& srv, int socket); 9 void SendData(const string& data, Dh& srv, int socket); 10 string ReadFromFile(); 11 string ReadMessage(); 12 void printStrInDec(const string& toPrint); 13 void printStrInHex(const string& toPrint, bool crypted); 14 15 #endif DhDataLib.cpp 1 #include "DhDataLib.hpp" 2 3 void printStrInDec(const string& toPrint) 4 { 5 for (unsigned i = 0; i < toPrint.size(); i++) 6 { 7 cout<<toPrint[i]; 8 if (((i + 1) % 3) == 0) 9 cout<<” “; 10 } 11 cout<<endl; 12 } 13 14 void printStrInHex(const string& toPrint, bool crypted) 15 { 16 char ch[4]; 17 unsigned k = 0; 18 cout<<"\nIn hex: "; 19 for (unsigned i = 0; i < toPrint.size(); i++) 20 if (crypted) 21 { 22 ch[k] = toPrint[i], k++; 23 if (k == 3) 24 { 25 ch[3] = '\n', k = 0; 26 cout<<hex<<atoi(ch)<<” “; 27 } 28 } 29 else 30 cout<<hex<<(int)((unsigned char)toPrint[i])<<" "; 31 32 cout<<endl; 33 } 34
54
35 bool ReceiveAndDecryptData(Dh& srv, int socket) 36 { 37 bool exitFunction = false; 38 bool exitProgram = false; 39 char buffer[BUFSIZ+1]; 40 string messageDecripted, bufferDecrypted; 41 42 cout<<"\n\n\nWaiting for data.."<<endl<<endl; 43 while (!exitFunction) 44 { 45 udp_receive(socket, buffer); 46 cout<<"\n\n\nEncrypted data received (in decimal): "; 47 printStrInDec(buffer); 48 printStrInHex(buffer, true); 49 bufferDecrypted = srv.DhDecrypt(buffer); 50 cout<<"\n\nDecrypted data received: "<<bufferDecrypted<<endl; 51 printStrInHex(bufferDecrypted, false); 52 messageDecripted += bufferDecrypted; 53 54 if (messageDecripted.find("EOTS~EOTS~EOTS",
messageDecripted.size() -‐14)!= string::npos) 55 { 56 exitProgram = true; 57 exitFunction = true; 58 } 59 else if (messageDecripted.find("EOF~EOF~EOF",
messageDecripted.size() -‐ 11)!= string::npos) 60 { 61 messageDecripted =
messageDecripted.erase(messageDecripted.size() -‐ 11, 11); 62 cout<<"\n\n\nDecripted data:"<<endl<<endl; 63 cout<<messageDecripted<<endl<<endl; 64 messageDecripted.clear(); 65 exitFunction = true; 66 } 67 } 68 return exitProgram; 69 } 70 71 bool ReadAndSendData(Dh& srv, int socket) 72 { 73 string message; 74 int choice; 75 cout<<"\n\nWhat do you want to do?"<<endl<<endl; 76 cout<<"1)Send a message"<<endl; 77 cout<<"2)Send data from file"<<endl; 78 cout<<"3)Exit"<<endl; 79 do 80 { 81 cout<<"Choose an option:"; 82 cin>>choice; 83 switch (choice) 84 { 85 case 1: 86 { 87 message = ReadMessage(); 88 SendData(message, srv, socket);
55
89 break; 90 } 91 case 2: 92 { 93 message = ReadFromFile(); 94 SendData(message, srv, socket); 95 break; 96 } 97 case 3: 98 SendData("EOTS~EOTS~EOTS", srv, socket); 99 break; 100 default: 101 cout<<"\nInvalid choice!"<<endl<<endl; 102 } 103 }while (choice != 1 && choice != 2 && choice != 3); 104 return choice == 3; 105 } 106 107 string ReadMessage() 108 { 109 string message; 110 char ch; 111 cout<<"\nEnter the message to send: "; 112 ch = cin.get(); // clean the stream. 113 do 114 { 115 ch = cin.get(); 116 message += ch; 117 } 118 while (ch != '\n'); 119 message[message.size() -‐ 1]= '\0'; 120 message += "EOF~EOF~EOF"; 121 return message; 122 } 123 124 string ReadFromFile() 125 { 126 ifstream is; 127 string nameFile, fileData; 128 char ch; 129 cout<<"\nEnter file name with extension: "; 130 cin>>nameFile; 131 is.open(nameFile.c_str()); 132 while (is.get(ch)) 133 fileData += ch; 134 135 is.close(); 136 fileData += "EOF~EOF~EOF"; 137 return fileData; 138 } 139 140 void SendData(const string& data, Dh& srv, int socket) 141 { 142 string encryptedStr; 143 int keyLen = srv.SizeOfK(); 144 int counter = 0; 145 for (unsigned i = 0; i < data.size(); i++)
56
146 { 147 encryptedStr.push_back(data[i]); 148 if (counter == keyLen -‐ 1 || i == data.size() -‐ 1) 149 { 150 cout<<"\n\n\nData to send: "<<encryptedStr<<endl; 151 printStrInHex(encryptedStr, false); 152 encryptedStr = srv.DhEncrypt(encryptedStr); 153 cout<<"\n\nEncrypted data to send (in decimal): "; 154 printStrInDec(encryptedStr); 155 printStrInHex(encryptedStr, true); 156 udp_reply(socket, (char*)encryptedStr.c_str()); 157 sleep(1); 158 encryptedStr.clear(); 159 counter = 0; 160 } 161 counter++; 162 } 163 } ClassRsa.hpp 1 #ifndef RSA_HPP 2 #define RSA_HPP 3 4 #include <cassert> 5 #include <iostream> 6 #include <gmpxx.h> 7 #include <string> 8 #include <unistd.h> 9 #include "udpsocketlib.hpp" 10 #include "openssl/bn.h" 11 12 using namespace std; 13 14 class Rsa 15 { 16 public: 17 mpz_class N() const { return n;} 18 int SizeOfN() const { return mpz_sizeinbase(n.get_mpz_t(),
10);} 19 mpz_class E() const { return e;} 20 mpz_class D() const { return d;} 21 void GenerateEncryptionKeys(); 22 bool SendPublicKey(int socket, string ipAddress, int udpPort); 23 bool ReceivePublicKey(int socket); 24 void RsaEncrypt(string& toEncryptStr); 25 string RsaDecrypt (const char* toDecrypt); 26 27 private: 28 mpz_class p, q, phi; 29 mpz_class n, e, d; //keys 30 mpz_class ComputeGCD(const mpz_class& x, const mpz_class& y); 31 mpz_class GetRandomE(); 32 void GeneratePQandComputeNPhi(); 33 mpz_class GeneratePrimeNumber(string name, unsigned numBits); 34 void FindE();
57
35 void ComputeD(); 36 int rdtsc(); 37 }; 38 39 #endif ClassRsa.cpp 1 #include "ClassRsa.hpp" 2 3 mpz_class Rsa::GeneratePrimeNumber(string name, unsigned numBits) 4 { 5 bool ctrl = false; 6 mpz_class num; 7 BIGNUM primeNum; 8 BN_init(&primeNum); 9 do 10 { 11 ctrl = BN_generate_prime_ex(&primeNum, numBits, 1, NULL, NULL,
NULL); 12 }while (!ctrl); 13 num = BN_bn2dec(&primeNum); 14 BN_free(&primeNum); 15 cout<<"\t"<<name<<"="<<num<<endl<<endl; 16 return num; 17 } 18 19 void Rsa::GeneratePQandComputeNPhi() 20 { 21 cout<<"\n\nGenerating prime numbers 'p' and 'q'.."<<endl<<endl; 22 p = GeneratePrimeNumber("p", 512); 23 q = GeneratePrimeNumber("q", 512); 24 cout<<"\n\nComputing 'n' and 'phi'.."<<endl<<endl; 25 assert(p != 0 && q != 0); 26 n = p * q; 27 phi = (p -‐ 1) * (q – 1); 28 cout<<"\tn=p*q="<<n<<"\n\n\tphi=(p-‐1)*(q-‐1)="<<phi<<endl<<endl; 29 } 30 31 mpz_class Rsa::GetRandomE() 32 { 33 mpz_class aux; 34 string str; 35 int max, min, temp, size; 36 aux = phi -‐ 1; 37 max = mpz_sizeinbase(aux.get_mpz_t(), 10); 38 if (p >= q) 39 min = mpz_sizeinbase(p.get_mpz_t(), 10) + 1; 40 else 41 min = mpz_sizeinbase(q.get_mpz_t(), 10) + 1; 42 do 43 { 44 srand(rdtsc()); 45 size = (rand() % (max -‐ min)) + min; 46 for (int i = 0; i < size; i++) 47 {
58
48 srand(rdtsc()); 49 temp = rand() % 9; 50 str.push_back((char)temp + '0'); 51 } 52 aux.set_str(str, 10); 53 }while (aux >= phi -‐ 1); 54 return aux; 55 } 56 57 void Rsa::FindE() 58 { 59 mpz_class temp = 0; 60 bool eFound = false; 61 assert(phi != 0); 62 do 63 { 64 e = GetRandomE(); 65 while (temp!= 1 && e > p && e > q) 66 { 67 temp = ComputeGCD(e, phi); 68 if (temp == 1) 69 eFound = true; 70 else 71 e -‐= 1; 72 } 73 }while (!eFound); 74 cout<<"\n\n\t'e' such that GCD(e,phi)=1 is:"<<e<<endl<<endl; 75 } 76 77 mpz_class Rsa::ComputeGCD(const mpz_class& x, const mpz_class& y) 78 { 79 mpz_class m = 0, n = 0, r = 0; 80 m = x; 81 n = y; 82 if (m > n) 83 r = n; 84 else 85 { 86 r = m; 87 m = n; 88 n = r; 89 } 90 while (r != 0) 91 { 92 r = m % n; 93 m = n; 94 n = r; 95 } 96 r = m; 97 return r; 98 } 99 100 void Rsa::ComputeD() 101 { 102 mpz_class t, nt, r, nr, q, tmp, a, b; 103 a = e; b = phi; 104 if (b < 0)
59
105 b = -‐b; 106 if (a < 0) 107 a = b -‐ (-‐a % b); 108 t = 0; 109 nt = 1; 110 r = b; 111 nr = a % b; 112 while (nr != 0) 113 { 114 q = r / nr; 115 tmp = nt; 116 nt = t – (q * nt); 117 t = tmp; 118 tmp = nr; 119 nr = r – (q * nr); 120 r = tmp; 121 } 122 assert(r <= 1); /* No inverse */ 123 if (t < 0) 124 t += b; 125 d = t; 126 cout<<"\t'd' such that (e*d= 1 mod phi) is:"<<d<<endl<<endl; 127 } 128 129 void Rsa::GenerateEncryptionKeys() 130 { 131 GeneratePQandComputeNPhi(); 132 FindE(); 133 ComputeD(); 134 cout<<"\nThe private key
is(d,n):\n\n\td="<<d<<"\n\n\tn="<<n<<endl<<endl; 135 cout<<"\nThe public key
is(e,n):\n\n\te="<<e<<"\n\n\tn="<<n<<endl<<endl; 136 } 137 138 bool Rsa::SendPublicKey(int socket, string ipAddress, int udpPort) 139 { 140 string public_key[2]; 141 assert(e != 0 && n != 0); 142 public_key[0] = e.get_str(10); 143 public_key[1] = n.get_str(10); 144 cout<<"Sharing the public key..Sending 'e' and
'n'..."<<endl<<endl; 145 for (unsigned count = 0; count < 2; count++) 146 { 147 if (!udp_send(socket, (char*)public_key[count].c_str(),
(char*)ipAddress.c_str(), udpPort)) 148 { 149 cout<<"Unable to send the public key..."<<endl<<endl; 150 return false; 151 } 152 sleep(1); 153 } 154 cout<<"Data sent!\n\nWaiting..."<<endl<<endl; 155 return true; 156 } 157
60
158 bool Rsa::ReceivePublicKey(int socket) 159 { 160 char public_key[2][BUFSIZ+1]; 161 unsigned count = 0; 162 cout<<"\n\nWaiting for public key..."<<endl<<endl; 163 do 164 { 165 if (udp_receive(socket, public_key[count]) == -‐1) 166 { 167 cout<<"Unable to receive the public key..."<<endl<<endl; 168 return false; 169 } 170 count++; 171 }while (count < 2); 172 e.set_str(public_key[0], 10); 173 n.set_str(public_key[1], 10); 174 if (n == 0 || e == 0) 175 { 176 cout<<"Error. Wrong public key."<<endl<<endl; 177 return false; 178 } 179 cout<<"The public key(e,n) is:"<<endl<<endl; 180 cout<<"\te="<<e<<"\n\n\tn="<<n<<endl<<endl; 181 return true; 182 } 183 184 void Rsa::RsaEncrypt(string& toEncryptStr) 185 { 186 mpz_class encrypted; 187 encrypted.set_str(toEncryptStr, 10); 188 cout<<"\n\nData to send(in
decimal):"<<endl<<toEncryptStr<<endl<<endl; 189 190 mpz_powm(encrypted.get_mpz_t(), encrypted.get_mpz_t() ,
e.get_mpz_t(), n.get_mpz_t()); 191 192 toEncryptStr = encrypted.get_str(10); 193 cout<<"Encrypted data to send(in
decimal):"<<endl<<toEncryptStr<<endl<<endl; 194 } 195 196 string Rsa::RsaDecrypt(const char* toDecrypt) 197 { 198 mpz_class decrypted; 199 cout<<"\nEncrypted data received(in
decimal):"<<endl<<toDecrypt<<endl<<endl; 200 decrypted.set_str(toDecrypt, 10); 201 mpz_powm(decrypted.get_mpz_t(), decrypted.get_mpz_t() ,
d.get_mpz_t(), n.get_mpz_t()); 202 return decrypted.get_str(10); 203 } 204 205 void error_handler(char *message) 206 { 207 cout<<"fatal error: "<<message<<endl; 208 return; 209 }
61
210 211 int Rsa::rdtsc() 212 { 213 __asm__ __volatile__("rdtsc"); 214 } RsaDataLib.hpp 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 RsaDataLib.cpp 1 #include "RsaDataLib.hpp" 2 3 void ReceiveAndDecryptData(Rsa& clt, int socket) 4 { 5 bool exit = false; 6 char buffer[BUFSIZ + 1]; 7 string messageDecripted, aux; 8 while (!exit) 9 { 10 udp_receive(socket, buffer); 11 aux = clt.RsaDecrypt(buffer); 12 messageDecripted += RebuildData(aux); 13 if (messageDecripted.find("EOTS~EOTS~EOTS",
messageDecripted.size() -‐ 14)!= string::npos) 14 { 15 cout<<"\n\nExit.."<<endl<<endl; 16 exit = true; 17 } 18 else if (messageDecripted.find("EOF~EOF~EOF",
messageDecripted.size() -‐ 14)!= string::npos) 19 { 20 messageDecripted =
messageDecripted.erase(messageDecripted.size() -‐ 11, 11); 21 cout<<"\n\nDecripted data:"<<endl<<endl; 22 cout<<messageDecripted<<endl<<endl; 23 messageDecripted.clear(); 24 cout<<"\nWaiting for new data.."<<endl<<endl; 25 } 26 } 27 }
62
28 29 string RebuildData(string& decryptedStr) 30 { 31 int r, strLen, k = 0; 32 char ch[4]; 33 string finalStr; 34 strLen = decryptedStr.size(); 35 r = strLen % 3; 36 if (r !=0) 37 for (int i = 0; i < (3 – r); i++) 38 decryptedStr = "0" + decryptedStr; 39 cout<<"Received data decrypted(in
decimal):"<<endl<<decryptedStr<<endl<<endl; 40 do 41 { 42 int length = 0; 43 if (strLen >= 3) 44 length = decryptedStr.copy(ch, 3, k); 45 else 46 length = decryptedStr.copy(ch, strLen, k); 47 ch[length]= '\0'; 48 char c = (char)(atoi(ch)); 49 finalStr += c; 50 k += 3; 51 }while (k < strLen); 52 cout<<"Received data decrypted:"<<endl<<finalStr<<endl<<endl; 53 return finalStr; 54 } 55 56 bool ReadAndSendData(Rsa& srv, int socket) 57 { 58 string message; 59 int choice; 60 cout<<"\n\nWhat do you want to do?"<<endl<<endl; 61 cout<<"1)Send a message"<<endl; 62 cout<<"2)Send data from file"<<endl; 63 cout<<"3)Exit"<<endl; 64 do 65 { 66 cout<<"Choose an option:"; 67 cin>>choice; 68 switch (choice) 69 { 70 case 1: 71 { 72 message = ReadMessage(); 73 SendData(message, srv, socket); 74 break; 75 } 76 case 2: 77 { 78 message = ReadFromFile(); 79 SendData(message, srv, socket); 80 break; 81 } 82 case 3: 83 SendData("EOTS~EOTS~EOTS", srv, socket);
63
84 break; 85 default: 86 cout<<"\nInvalid choice!"<<endl<<endl; 87 } 88 }while (choice != 1 && choice != 2 && choice != 3); 89 return choice == 3; 90 } 91 92 string ReadMessage() 93 { 94 string message; 95 char ch; 96 cout<<"\nEnter the message to send: "; 97 ch = cin.get(); // Clean the stream 98 do 99 { 100 ch = cin.get(); 101 message += ch; 102 } 103 while (ch != '\n'); 104 message[message.size() -‐ 1]= '\0'; 105 message += "EOF~EOF~EOF"; 106 return message; 107 } 108 109 string ReadFromFile() 110 { 111 ifstream is; 112 string nameFile, fileData; 113 char ch; 114 cout<<"\nEnter file name with extension: "; 115 cin>>nameFile; 116 is.open(nameFile.c_str()); 117 while (is.get(ch)) 118 fileData += ch; 119 is.close(); 120 fileData += "EOF~EOF~EOF"; 121 return fileData; 122 } 123 124 void SendData(const string& data, Rsa& srv, int socket) 125 { 126 string encryptedStr, toSend; 127 mpz_class encrypted, n; 128 char ch[4]; 129 n = srv.N(); 130 for (unsigned i = 0; i <= data.size(); i++) 131 { 132 sprintf(ch, "%.3d", (unsigned char)data[i]); 133 encryptedStr += ch; 134 encrypted.set_str(encryptedStr, 10); 135 toSend += data[i]; 136 if (encrypted > n || i == data.size()) 137 { 138 toSend.erase(toSend.size() -‐ 1); 139 cout<<"\n\nData to send:"<<endl<<toSend;
64
140 encryptedStr = encryptedStr.erase(encryptedStr.size() -‐ 3, 3);
141 srv.RsaEncrypt(encryptedStr); 142 udp_reply(socket, (char*)encryptedStr.c_str()); 143 sleep (1); 144 encryptedStr.clear(); 145 toSend.clear(); 146 toSend += data[i]; 147 encryptedStr += ch; 148 } 149 } 150 } File in C DhLib.h 1 #ifndef DHLIB_H 2 #define DHLIB_H 3 4 #include <stdio.h> 5 #include <stdlib.h> 6 #include <gmp.h> 7 #include <string.h> 8 #include <unistd.h> 9 #include <openssl/bn.h> 10 #include <assert.h> 11 #include "udpsocketlib.h" 12 13 // Create type bool (not present in c). 14 typedef enum { false, true } bool; 15 16 struct dhStruct 17 { 18 mpz_t p, g, a, b, A, B, K; 19 }; 20 21 void GeneratePrimeNumber(char* name, mpz_t num, unsigned numBits); 22 void FindGenerator(mpz_t q, mpz_t g); 23 bool GenerateEncryptionKey_Client(int socket, char* ipAddress, int
udpPort, struct dhStruct* dh); 24 void GetPgaAndSetA(struct dhStruct* dh, int choice); 25 bool SendPGA(int socket, char* ipAddress, int udpPort, struct
dhStruct* dh); 26 bool ReceiveBandComputeK(int socket, struct dhStruct* dh); 27 bool GenerateEncryptionKey_Server(int socket, struct dhStruct* dh); 28 bool ReceivePGA(int socket, struct dhStruct* dh); 29 void ComputeBandKey(struct dhStruct* dh); 30 bool SendB(int socket, struct dhStruct* dh); 31 char* DhEncrypt(char* toEncryptStr, char* key); 32 char* DhDecrypt(char* toDecrypt, char* key); 33 void GetVariable(char* varName, mpz_t var); 34 int rdtsc();
65
35 36 #endif DhLib.c 1 #include "DhLib.h" 2 3 bool GenerateEncryptionKey_Server(int socket, struct dhStruct* dh) 4 { 5 if (ReceivePGA(socket, dh)) 6 { 7 ComputeBandKey(dh); 8 if (SendB(socket, dh)) 9 { 10 gmp_printf("\n\nThe encryption key is:\n\n\tK=A^b (mod
p)=%Zd\n\n", dh-‐>K); 11 return true; 12 } 13 } 14 printf("\nUnable to create the key.\n"); 15 return false; 16 } 17 18 bool ReceivePGA(int socket, struct dhStruct* dh) 19 { 20 char strPGA [3][BUFSIZ+1]; 21 int count = 0; 22 printf("\n\nWaiting for p, g and A...\n\n"); 23 do 24 { 25 if (udp_receive(socket, strPGA [count]) == -‐1) 26 { 27 printf("Unable to receive p,g and A!\n\n"); 28 return false; 29 } 30 count++; 31 }while (count < 3); 32 mpz_set_str(dh-‐>p, strPGA [0], 10); 33 mpz_set_str(dh-‐>g, strPGA [1], 10); 34 mpz_set_str(dh-‐>A, strPGA [2], 10); 35 if (mpz_cmp_ui(dh-‐>p, 0) !=0 && mpz_cmp_ui(dh-‐>g, 0) !=0 &&
mpz_cmp_ui(dh-‐>A, 0) !=0) 36 { 37 printf("Data received:\n\n"); 38 gmp_printf("\n\tp=%Zd\n\n\tg=%Zd\n\n\tA=%Zd\n\n", dh-‐>p, dh-‐
>g, dh-‐>A); 39 return true; 40 } 41 else 42 { 43 printf("Error. Wrong data.\n\n"); 44 return false; 45 } 46 } 47
66
48 void ComputeBandKey(struct dhStruct* dh) 49 { 50 GetVariable("'b', such that 1<b<p", dh-‐>b); 51 assert(mpz_cmp_ui(dh-‐>b, 1) > 0 && mpz_cmp(dh-‐>p, dh-‐>b) > 0); 52 mpz_powm(dh-‐>B, dh-‐>g, dh-‐>b, dh-‐>p); 53 mpz_powm(dh-‐>K, dh-‐>A, dh-‐>b, dh-‐>p); 54 gmp_printf("\n\tB=g^b (mod p)=%Zd\n\n", dh-‐>B); 55 } 56 57 bool SendB(int socket, struct dhStruct* dh) 58 { 59 char *toSend = mpz_get_str(NULL, 10, dh-‐>B); 60 printf("Sending B...\n\n"); 61 if (!udp_reply(socket, toSend)) 62 { 63 printf("Unable to send B...\n\n"); 64 return false; 65 } 66 printf("Data sent!\n\n"); 67 return true; 68 } 69 70 bool GenerateEncryptionKey_Client(int socket, char* ipAddress, int
udpPort, struct dhStruct* dh) 71 { 72 int choice; 73 printf("\n\nAutomatically generate p,g?\n"); 74 printf("1)Yes.\n"); 75 printf("2)No, I insert them.\n"); 76 do 77 { 78 printf("\nChoose an option:"); 79 scanf("%d", &choice); 80 if (choice != 1 && choice != 2) 81 printf("\nInvalid choice!\n\n"); 82 }while (choice != 1 && choice != 2); 83 GetPgaAndSetA(dh, choice); 84 if (SendPGA(socket, ipAddress, udpPort, dh)) 85 { 86 printf("Data sent!\n\nWaiting for B...\n\n"); 87 if (ReceiveBandComputeK(socket, dh)) 88 { 89 gmp_printf("\n\nThe encryption key is:\n\n\tK=B^a (mod
p)=%Zd\n\n", dh-‐>K); 90 return true; 91 } 92 } 93 printf("\nUnable to create the key.\n"); 94 return false; 95 } 96 97 void FindGenerator(mpz_t p, mpz_t g) 98 { 99 char *pStr; 100 int size, temp, i; 101 bool sameSize; 102 mpz_set(g, p);
67
103 mpz_sub_ui(g, g, 2); 104 pStr = mpz_get_str(NULL, 10, p); 105 do 106 { 107 srand(rdtsc()); 108 size = (rand() % ((int) strlen(pStr) -‐ 1)) + 1; 109 sameSize = (size == (int) strlen(pStr)); 110 for (i = 0; i < size; i++) 111 { 112 srand(rdtsc()); 113 if (sameSize) 114 { 115 temp = pStr[i] -‐ '0'; 116 if (temp != 0) 117 temp = rand() % temp; 118 } 119 else 120 temp = rand() % 9; 121 pStr[i] = ((char) temp + '0'); 122 } 123 pStr[size] = '\0'; 124 mpz_set_str(g, pStr, 10); 125 }while (mpz_cmp_ui(g, 2) == 0); 126 gmp_printf("\n\tg=%Zd\n", g); 127 } 128 129 void GeneratePrimeNumber(char* name, mpz_t num, unsigned numBits) 130 { 131 bool ctrl = false; 132 BIGNUM primeNum; 133 BN_init(&primeNum); 134 do 135 { 136 ctrl = BN_generate_prime_ex(&primeNum, numBits, 1, NULL, NULL,
NULL); 137 }while (!ctrl); 138 mpz_set_str(num, BN_bn2dec(&primeNum), 10); 139 BN_free(&primeNum); 140 gmp_printf("\n\t%s=%Zd\n", name, num); 141 } 142 143 void GetPgaAndSetA(struct dhStruct* dh, int choice) 144 { 145 if (choice == 1) 146 { 147 printf("\n\nGenerating 'p' and 'g'...\n\n"); 148 GeneratePrimeNumber("p", dh-‐>p, 1024); 149 FindGenerator(dh-‐>p, dh-‐>g); 150 } 151 else if (choice == 2) 152 { 153 GetVariable("'p'", dh-‐>p); 154 GetVariable("'g'", dh-‐>g); 155 } 156 GetVariable("'a', such that 1<a<p", dh-‐>a); 157 assert(mpz_cmp_ui(dh-‐>a, 1) > 0 && mpz_cmp(dh-‐>p, dh-‐>a) > 0); 158 mpz_powm(dh-‐>A, dh-‐>g, dh-‐>a, dh-‐>p);
68
159 gmp_printf("\n\tA=g^a (mod p)=%Zd\n", dh-‐>A); 160 } 161 162 bool SendPGA(int socket, char* ipAddress, int udpPort, struct
dhStruct* dh) 163 { 164 char *toSend[3]; 165 int count = 0; 166 toSend[0] = mpz_get_str(NULL, 10, dh-‐>p); 167 toSend[1] = mpz_get_str(NULL, 10, dh-‐>g); 168 toSend[2] = mpz_get_str(NULL, 10, dh-‐>A); 169 printf("\nSending p,g and A...\n\n"); 170 do 171 { 172 if (!udp_send(socket, toSend[count], ipAddress, udpPort)) 173 { 174 printf("Unable to send p, g and A...\n\n"); 175 return false; 176 } 177 count++; 178 sleep(1); 179 }while (count < 3); 180 return true; 181 } 182 183 bool ReceiveBandComputeK(int socket, struct dhStruct* dh) 184 { 185 char strB[BUFSIZ+1]; 186 if (udp_receive(socket, strB) == -‐1) 187 { 188 printf("Unable to receive B!\n\n"); 189 return false; 190 } 191 printf("B received!\n\n\tB=%s\n\n", strB); 192 mpz_set_str(dh-‐>B, strB, 10); 193 if (mpz_cmp_ui(dh-‐>B, 0) > 0) 194 { 195 mpz_powm(dh-‐>K, dh-‐>B, dh-‐>a, dh-‐>p); 196 return true; 197 } 198 else 199 return false; 200 } 201 202 void error_handler(char *message) 203 { 204 printf("fatal error: %s\n", message); 205 return; 206 } 207 208 char* DhEncrypt(char* toEncryptStr, char* key) 209 { 210 char ch[4]; 211 char *aux = malloc((strlen(toEncryptStr) * 3 + 2)*sizeof(char)); 212 int i; 213 aux[0] = '\0'; 214 for (i = 0; i < strlen (toEncryptStr); i++)
69
215 { 216 sprintf(ch,"%.3d", (unsigned char)toEncryptStr[i]^(unsigned
char)key[i]); 217 strcat(aux, ch); 218 } 219 return aux; 220 } 221 222 char* DhDecrypt (char* toDecrypt, char* key) 223 { 224 int i, aux = 0, count= 0; 225 char ch[4]; 226 char *decrypted = malloc ((strlen(key) + 2)*sizeof(char)); 227 for (i = 0; i < strlen(toDecrypt); i++) 228 { 229 ch[aux] = toDecrypt[i]; 230 aux++; 231 if (aux == 3) 232 { 233 ch[aux]='\0'; 234 decrypted[count] = (unsigned char) atoi(ch)^key[count]; 235 aux = 0, count++; 236 } 237 } 238 decrypted[count] = '\0'; 239 return decrypted; 240 } 241 242 void GetVariable(char* varName, mpz_t var) 243 { 244 do 245 { 246 printf("\nEnter %s: ", varName); 247 gmp_scanf("%Zd", var); 248 if (mpz_cmp_ui(var, 0) <= 0) 249 printf("\nError. Please insert a number > 0\n"); 250 }while (var <= 0); 251 } 252 253 int rdtsc() 254 { 255 __asm__ __volatile__("rdtsc"); 256 } DhDataLib.h 1 #ifndef DHDATALIB_HPP 2 #define DHDATALIB_HPP 3 4 #include "DhLib.h" 5 6 bool ReadAndSendData(char* key, int socket); 7 bool ReceiveAndDecryptData(char *key, int socket); 8 void SendData(char* data, char* key, int socket); 9 char* ResizeDinamicString(char* toResize, int newDim); 10 char* ReadMessage();
70
11 char* ReadFromFile(); 12 void printStrInDec(char* toPrint); 13 void printStrInHex(char* toPrint, bool crypted); 14 #endif DhDataLib.c 1 #include "DhDataLib.h" 2 3 bool ReadAndSendData(char* key, int socket) 4 { 5 char* message; 6 int choice; 7 printf("\n\nWhat do you want to do?\n\n"); 8 printf("1)Send a message\n"); 9 printf("2)Send data from file\n"); 10 printf("3)Exit\n"); 11 do 12 { 13 printf("Choose an option:"); 14 scanf("%d", &choice); 15 switch (choice) 16 { 17 case 1: 18 { 19 message = ReadMessage(); 20 SendData(message, key, socket); 21 free(message); 22 message = NULL; 23 break; 24 } 25 case 2: 26 { 27 message = ReadFromFile(); 28 SendData(message, key, socket); 29 free(message); 30 message = NULL; 31 break; 32 } 33 case 3: 34 SendData("EOTS~EOTS~EOTS", key, socket); 35 break; 36 default: 37 printf("\nInvalid choice!\n\n"); 38 } 39 }while (choice != 1 && choice != 2 && choice != 3); 40 return choice == 3; 41 } 42 43 bool ReceiveAndDecryptData(char *key, int socket) 44 { 45 bool exitFunction = false, exitProgram = false; 46 char buffer[BUFSIZ + 1], *decrypted, *messageDecript = NULL; 47 int messLen = 0; 48 49 printf("\n\n\nWaiting for data...\n\n");
71
50 while (!exitFunction) 51 { 52 udp_receive(socket, buffer); 53 printf("\n\n\nEncrypted data received (in decimal): "); 54 printStrInDec(buffer); 55 printStrInHex(buffer, true); 56 decrypted = DhDecrypt(buffer, key); 57 messageDecript = ResizeDinamicString(messageDecript, messLen +
strlen(decrypted)); 58 printf("\n\nDecrypted data received: %s\n", decrypted); 59 printStrInHex(decrypted, false); 60 strcat(messageDecript, decrypted); 61 messLen = strlen(messageDecript); 62 63 if (strstr(messageDecript, "EOTS~EOTS~EOTS") != NULL) 64 { 65 exitProgram = true; 66 exitFunction = true; 67 } 68 else if (strstr(messageDecript, "EOF~EOF~EOF") != NULL) 69 { 70 messageDecript[strlen(messageDecript) -‐ 11] = '\0'; 71 printf("\n\nDecripted data:\n\n%s\n\n", messageDecript); 72 exitFunction = true; 73 } 74 if (decrypted != NULL) 75 free(decrypted); 76 decrypted = NULL; 77 } 78 if (messageDecript != NULL) 79 free(messageDecript); 80 messageDecript = NULL; 81 82 return exitProgram; 83 } 84 85 void SendData(char* data, char* key, int socket) 86 { 87 int dataLen = strlen(data), keyLen = strlen(key), i, counter = 0; 88 char encryptedStr[keyLen + 1], *encrypted; 89 for (i = 0; i < dataLen; i++) 90 { 91 encryptedStr[counter] = data[i]; 92 counter++; 93 if (counter == keyLen || i == dataLen -‐ 1) 94 { 95 encryptedStr[counter] = '\0'; 96 printf("\n\n\nData to send: %s\n", encryptedStr); 97 printStrInHex(encryptedStr, false); 98 encrypted = DhEncrypt(encryptedStr, key); 99 printf("\n\nEncrypted data to send (in decimal): "); 100 printStrInDec(encrypted); 101 printStrInHex(encrypted, true); 102 udp_reply(socket, encrypted); 103 encryptedStr[0] = '\0'; 104 counter = 0; 105 if (encrypted != NULL)
72
106 free(encrypted); 107 encrypted = NULL; 108 sleep(1); 109 } 110 } 111 } 112 113 char* ResizeDinamicString(char* toResize, int newDim) 114 { 115 char*resized = malloc((newDim + 1)*sizeof(char)); 116 if (resized == NULL) 117 printf("\n\nFailed to realloc.\n\n"); 118 resized[0] = '\0'; 119 if (toResize != NULL) 120 { 121 strcpy(resized, toResize); 122 free(toResize); 123 toResize = NULL; 124 } 125 return resized; 126 } 127 128 char* ReadMessage() 129 { 130 char *message = NULL; 131 char buffer[(BUFSIZ + 1)]; 132 int messLen = 0; 133 printf("\nEnter the message to send: "); 134 getchar(); 135 do 136 { 137 fgets(buffer, BUFSIZ, stdin); 138 message = ResizeDinamicString(message, messLen +
strlen(buffer)); 139 strcat(message, buffer); 140 messLen = strlen(message); 141 }while (buffer[strlen(buffer) -‐ 1] != '\n'); 142 message[messLen-‐1]= '\0'; 143 message = ResizeDinamicString(message, strlen(message) + 11); 144 strcat(message, "EOF~EOF~EOF"); 145 return message; 146 } 147 148 char* ReadFromFile() 149 { 150 FILE* source; 151 char fileName[1024], buffer[(BUFSIZ + 1)]; 152 char *fileData = NULL; 153 int dataLen = 0; 154 do 155 { 156 printf("\nInsert file name with extension: "); 157 scanf("%s",fileName); 158 if ((source = fopen(fileName, "r")) == NULL) 159 printf("\nCannot read the file...Try again\n"); 160 }while ( source == NULL ); 161
73
162 while (!feof(source)) 163 { 164 fgets(buffer,BUFSIZ, source); 165 fileData = ResizeDinamicString(fileData, dataLen +
strlen(buffer)); 166 strcat( fileData, buffer ); 167 dataLen = strlen(fileData); 168 } 169 fileData[strlen(fileData)]= '\0'; 170 fileData = ResizeDinamicString(fileData, strlen(fileData) + 11); 171 strcat(fileData, "EOF~EOF~EOF"); 172 fclose(source); 173 return fileData; 174 } 175 176 void printStrInDec( char* toPrint ) 177 { 178 int i; 179 for (i = 0; i < strlen(toPrint); i++) 180 { 181 printf("%c", toPrint[i]); 182 if (((i + 1) % 3) == 0) 183 printf(" "); 184 } 185 printf("\n"); 186 } 187 188 void printStrInHex(char* toPrint, bool crypted) 189 { 190 int i; 191 char ch[4]; 192 unsigned k = 0; 193 printf("\nIn hex: "); 194 for (i = 0; i < strlen(toPrint); i++) 195 if (crypted) 196 { 197 ch[k] = toPrint[i], k++; 198 if (k == 3) 199 { 200 ch[3] = '\0', k =0; 201 printf("%x ", atoi(ch)); 202 } 203 } 204 else 205 printf("%x ", (unsigned char) toPrint[i]); 206 printf ( "\n" ); 207 } Dh_c.c 1 #include "DhLib.h" 2 #include "DhDataLib.h" 3 4 int main(int argc, char *argv[]) 5 {
74
6 struct dhStruct clnt; 7 int sk, udpPort; 8 char serverIpAdrress[1024]; 9 char *key; 10 bool done = false; 11 mpz_inits(clnt.p, clnt.g, clnt.a, clnt.b, clnt.A, clnt.B, clnt.K,
NULL); 12 13 if (argc == 1) 14 { 15 printf("Server ip address: "); 16 scanf("%s", serverIpAdrress); 17 printf("Udp port:"); 18 scanf("%d", &udpPort); 19 } 20 else if (argc == 3) 21 { 22 strcpy(serverIpAdrress, argv[1]); 23 udpPort = atoi(argv[2]); 24 } 25 else 26 { 27 printf("Required arguments: server ip address, udp port\n"); 28 exit(1); 29 } 30 31 if ((sk = create_udp_client()) < 0) 32 { 33 printf("Cannot open client socket\n"); 34 exit(1); 35 } 36 37 if (!GenerateEncryptionKey_Client(sk, serverIpAdrress ,udpPort,
&clnt)) 38 exit(1); 39 key = mpz_get_str(NULL, 10, clnt.K); 40 do 41 { 42 done = ReadAndSendData(key, sk); 43 if (!done) 44 done = ReceiveAndDecryptData(key, sk); 45 }while (!done); 46 47 printf("\n\nExit...\n\n"); 48 close_udp_socket(sk); 49 mpz_clears(clnt.p, clnt.g, clnt.a, clnt.b, clnt.A, clnt.B, clnt.K,
NULL); 50 return EXIT_SUCCESS; 51 } Dh_s.c 1 #include "DhLib.h" 2 #include "DhDataLib.h" 3 4 int main(int argc, char *argv[])
75
5 { 6 struct dhStruct srv; 7 int sk, udpPort; 8 char serverIpAdrress[1024]; 9 char *key; 10 bool done = false; 11 mpz_inits(srv.p, srv.g, srv.b, srv.B, srv.K, srv.A, NULL); 12 13 if (argc == 1) 14 { 15 printf("Server ip address: "); 16 scanf("%s", serverIpAdrress); 17 printf("Udp port:"); 18 scanf("%d", &udpPort); 19 } 20 else if (argc == 3) 21 { 22 strcpy(serverIpAdrress, argv[1]); 23 udpPort = atoi(argv[2]); 24 } 25 else 26 { 27 printf("Required arguments: server ip address, udp port\n"); 28 exit(1); 29 } 30 31 if ((sk = create_udp_server(serverIpAdrress, udpPort)) < 0) 32 { 33 printf("Cannot open server socket\n"); 34 exit(1); 35 } 36 37 if (!GenerateEncryptionKey_Server(sk, &srv)) 38 exit(1); 39 key = mpz_get_str(NULL, 10, srv.K); 40 do 41 { 42 done = ReceiveAndDecryptData(key, sk); 43 if (!done) 44 done = ReadAndSendData(key, sk); 45 }while (!done); 46 47 printf("\n\nExit...\n\n"); 48 close_udp_socket(sk); 49 mpz_clears(srv.p, srv.g, srv.b, srv.B, srv.K, srv.A, NULL); 50 return EXIT_SUCCESS; 51 } RsaLib.h 1 #ifndef RSALIB_H 2 #define RSALIB_H 3 4 #include <stdio.h> 5 #include <stdlib.h> 6 #include <gmp.h>
76
7 #include <string.h> 8 #include <openssl/bn.h> 9 #include <unistd.h> 10 #include "udpsocketlib.h" 11 #include <assert.h> 12 // Create type bool (not present in c). 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(); 38 39 #endif RsaLib.c 1 #include "RsaLib.h" 2 3 bool ReceivePublicKey(struct publicKeyRsa* pbk, int socket) 4 { 5 char public_key[2][BUFSIZ + 1]; 6 unsigned count = 0; 7 printf("\n\nWaiting for public key...\n\n"); 8 do 9 { 10 if (udp_receive(socket, public_key[count]) == -‐1) 11 { 12 printf("Unable to receive the public key...\n\n"); 13 return false; 14 } 15 count++; 16 }while (count < 2); 17 mpz_set_str(pbk-‐>e, public_key[0], 10); 18 mpz_set_str(pbk-‐>n, public_key[1], 10); 19 if (mpz_cmp_ui(pbk-‐>n, 0) <= 0 || mpz_cmp_ui(pbk-‐>e, 0) <= 0)
77
20 { 21 printf("Error. Wrong public key.\n\n"); 22 return false; 23 } 24 printf("The public key(e,n) is:\n\n"); 25 gmp_printf("\te=%Zd\n\n\tn=%Zd\n\n", pbk-‐>e, pbk-‐>n); 26 return true; 27 } 28 29 30 void GenerateEncryptionKeys(struct keysRsa* keys) 31 { 32 GeneratePQandComputeNPhi(keys); 33 FindE(keys); 34 ComputeD(keys); 35 gmp_printf("\nThe private key is(d,n):\n\n\td=%Zd\n\n\tn=%Zd\n",
keys-‐>d, keys-‐>n); 36 gmp_printf("\n\nThe public key is(e,n):\n\n\te=%Zd\n\n\tn=%Zd\n",
keys-‐>e, keys-‐>n); 37 } 38 39 void GeneratePrimeNumber(char* name, mpz_t num, int numBits) 40 { 41 bool ctrl = false; 42 BIGNUM primeNum; 43 BN_init(&primeNum); 44 do 45 { 46 ctrl = BN_generate_prime_ex(&primeNum, numBits, 1, NULL, NULL,
NULL); 47 }while (!ctrl); 48 mpz_set_str(num, BN_bn2dec(&primeNum), 10); 49 BN_free(&primeNum); 50 gmp_printf("\t%s=%Zd\n\n", name, num); 51 } 52 53 void GeneratePQandComputeNPhi(struct keysRsa* var) 54 { 55 mpz_t aux, temp; 56 mpz_inits(aux, temp, NULL); 57 printf("\n\nGenerating prime numbers 'p' and 'q'...\n\n"); 58 GeneratePrimeNumber("p", var-‐>p, 512); 59 GeneratePrimeNumber("q", var-‐>q, 512); 60 printf("\n\nComputing 'n' and 'phi'...\n\n"); 61 mpz_mul(var-‐>n, var-‐>p, var-‐>q); 62 mpz_sub_ui(aux, var-‐>p, 1); 63 mpz_sub_ui(temp, var-‐>q, 1); 64 mpz_mul(var-‐>phi, aux, temp); 65 gmp_printf("\tn=p*q=%Zd\n\n\tphi=(p-‐1)*(q-‐1)=%Zd\n\n", var-‐>n,
var-‐>phi); 66 mpz_clears(aux, temp, NULL); 67 } 68 69 void GetRandomE(struct keysRsa* var) 70 { 71 mpz_t aux; 72 char str[mpz_sizeinbase(var-‐>phi, 10)];
78
73 int max, min, temp, size, i; 74 mpz_init(aux); 75 mpz_set(aux, var-‐>phi); 76 mpz_sub_ui(aux, aux, 1); 77 max = mpz_sizeinbase(aux, 10); 78 if ( mpz_cmp(var-‐>p, var-‐>q) >= 0) 79 min = mpz_sizeinbase(var-‐>p, 10) + 1; 80 else 81 min = mpz_sizeinbase(var-‐>q, 10) + 1; 82 do 83 { 84 srand(rdtsc()); 85 size = (rand() % (max -‐ min)) + min; 86 for (i = 0; i < size; i++) 87 { 88 srand(rdtsc()); 89 temp = rand() % 9; 90 str[i] = (char)temp + '0'; 91 } 92 str[i] = '\0'; 93 mpz_set_str(var-‐>e, str, 10); 94 }while (mpz_cmp(var-‐>e, aux) >= 0); 95 mpz_clears(aux, NULL); 96 } 97 98 void FindE(struct keysRsa* var) 99 { 100 bool eFound = false; 101 mpz_t t, random; 102 mpz_inits(t, random, NULL); 103 do 104 { 105 GetRandomE(var); 106 while (mpz_cmp_ui(t, 1) != 0 && mpz_cmp(var-‐>e, var-‐>p) > 0 &&
mpz_cmp(var-‐>e, var-‐>q) > 0) 107 { 108 ComputeGCD(t, var-‐>e, var-‐>phi); 109 if (mpz_cmp_ui(t, 1) == 0) 110 eFound = true; 111 else 112 mpz_sub_ui(var-‐>e, var-‐>e, 1); 113 } 114 }while (!eFound); 115 gmp_printf("\n\n\t'e' such that GCD(e,phi)=1 is:%Zd\n\n", var-‐>e); 116 mpz_clears(t, random, NULL); 117 } 118 119 void ComputeGCD(mpz_t r, mpz_t e, mpz_t phi) 120 { 121 mpz_t m, n; 122 mpz_inits(m, n, NULL); 123 mpz_set(m, e); 124 mpz_set(n, phi); 125 if (mpz_cmp(m, n) > 0) 126 mpz_set(r, n); 127 else 128 {
79
129 mpz_set(r, m); 130 mpz_set(m, n); 131 mpz_set(n, r); 132 } 133 while (mpz_cmp_ui(r, 0) != 0) 134 { 135 mpz_fdiv_r(r, m, n); 136 mpz_set(m, n); 137 mpz_set(n, r); 138 } 139 mpz_set(r, m); 140 mpz_clears(m, n, NULL); 141 } 142 143 void ComputeD(struct keysRsa* var) 144 { 145 mpz_t t, nt, r, nr, q, tmp, a, b; 146 mpz_inits(t, nt, r, nr, q, tmp, a, b, NULL); 147 mpz_set(a, var-‐>e); 148 mpz_set(b, var-‐>phi); 149 if (mpz_cmp_ui(b, 0) < 0) 150 mpz_ui_sub(b, 0, b); 151 if (mpz_cmp_ui(a, 0) < 0) 152 { 153 mpz_ui_sub(a, 0, a); 154 mpz_fdiv_r(a, a, b); 155 mpz_sub(a, b, a); 156 } 157 mpz_set_ui(t, 0); 158 mpz_set_ui(nt, 1); 159 mpz_set(r, b); 160 mpz_fdiv_r(nr, a, b); 161 while (mpz_cmp_ui(nr , 0) != 0) 162 { 163 mpz_fdiv_q(q, r, nr); 164 mpz_set(tmp, nt); 165 mpz_mul(nt, q, nt); 166 mpz_sub(nt, t, nt); 167 mpz_set(t, tmp); 168 mpz_set(tmp, nr); 169 mpz_mul(nr, q, nr); 170 mpz_sub(nr, r, nr); 171 mpz_set(r, tmp); 172 } 173 assert(mpz_cmp_ui(r , 1) <= 0); /* No inverse */ 174 if (mpz_cmp_ui(t , 0) < 0) 175 mpz_add(t, t, b); 176 mpz_set(var-‐>d, t); 177 mpz_clears(t, nt, r, nr, q, tmp, a, b, NULL); 178 gmp_printf(" \t'd' such that (e*d= 1 mod phi) is:%Zd\n\n ", var-‐
>d); 179 } 180 181 bool SendPublicKey(int socket, char* ipAddress, int udpPort , struct
keysRsa* pubK) 182 { 183 int count = 0;
80
184 char *public_key[2]; 185 public_key[0] = mpz_get_str(NULL, 10, pubK-‐>e); 186 public_key[1] = mpz_get_str(NULL, 10, pubK-‐>n); 187 printf("\nSharing the public key...Sending 'e' and 'n'...\n\n"); 188 do 189 { 190 if (!udp_send(socket, public_key[count], ipAddress, udpPort)) 191 { 192 printf("Unable to send the public key...\n\n"); 193 return false; 194 } 195 sleep(1); 196 count++; 197 }while (count < 2); 198 printf("Data sent!\n\nWaiting...\n\n"); 199 return true; 200 } 201 202 char* RsaEncrypt(char* toEncryptStr, struct publicKeyRsa* pbk) 203 { 204 mpz_t encrypted; 205 char *encryptedStr; 206 mpz_init(encrypted); 207 mpz_set_str(encrypted, toEncryptStr, 10); 208 mpz_powm(encrypted, encrypted, pbk-‐>e, pbk-‐>n); 209 encryptedStr = malloc((mpz_sizeinbase(encrypted, 10) +
1)*sizeof(char)); 210 encryptedStr = mpz_get_str(NULL, 10, encrypted); 211 mpz_clear(encrypted); 212 return encryptedStr; 213 } 214 215 char* RsaDecrypt(char* toDecrypt, struct keysRsa* key) 216 { 217 mpz_t decrypted; 218 char *decryptedStr; 219 mpz_init(decrypted); 220 mpz_set_str(decrypted, toDecrypt, 10); 221 mpz_powm(decrypted, decrypted, key-‐>d, key-‐>n); 222 decryptedStr = mpz_get_str(NULL, 10, decrypted); 223 mpz_clear(decrypted); 224 return decryptedStr; 225 } 226 227 int rdtsc() 228 { 229 __asm__ __volatile__("rdtsc"); 230 } 231 232 void error_handler(char *message) 233 { 234 printf("fatal error: %s\n", message); 235 return; 236 }
81
RsaDataLib.h 1 #ifndef RSADATALIB_HPP 2 #define RSADATALIB_HPP 3 4 #include "RsaLib.h" 5 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); 13 #endif RsaDataLib.c 1 #include "RsaDataLib.h" 2 3 void ReceiveAndDecryptData(struct keysRsa* privateKey, int socket) 4 { 5 bool exit = false; 6 int messLen = 0; 7 char buffer[BUFSIZ + 1]; 8 char *messageDecript = NULL, *decrypted; 9 while (!exit) 10 { 11 udp_receive(socket, buffer); 12 printf("\nEncrypted data received(in decimal):\n%s\n\n",
buffer); 13 decrypted = RsaDecrypt(buffer, privateKey); 14 RebuildData(decrypted); 15 printf("Received data decrypted:\n%s\n\n", decrypted); 16 messageDecript = ResizeDinamicString(messageDecript, messLen +
strlen(decrypted)); 17 strcat(messageDecript, decrypted); 18 messLen = strlen(messageDecript); 19 if (strstr(messageDecript, "EOTS~EOTS~EOTS") != NULL) 20 { 21 printf("\n\nExit...\n\n"); 22 exit = true; 23 } 24 else if (strstr(messageDecript, "EOF~EOF~EOF") != NULL) 25 { 26 messageDecript[strlen(messageDecript) -‐ 11] = '\0'; 27 printf("\n\nDecripted data:\n\n%s\n\n", messageDecript); 28 messageDecript[0] = '\0'; 29 printf("\nWaiting for new data...\n\n"); 30 } 31 if (decrypted != NULL) 32 free(decrypted); 33 decrypted = NULL; 34 } 35 if (messageDecript != NULL)
82
36 free(messageDecript); 37 messageDecript = NULL; 38 } 39 40 void RebuildData(char* decryptedStr) 41 { 42 char ch[4], *toReturn; 43 int r, len, i = 0, j = 0, k; 44 len = strlen(decryptedStr); 45 r = len % 3; 46 if (r !=0) 47 { 48 i = 3 -‐ r; 49 for (k = 0; k < i; k ++) 50 ch[k] = '0'; 51 } 52 printf("Received data decrypted(in decimal):\n%s\n\n",
decryptedStr); 53 len = strlen(decryptedStr); 54 toReturn = malloc(len*sizeof(char)); 55 k = 0; 56 while (k < len) 57 { 58 ch[i] = decryptedStr[k]; 59 k++, i++; 60 if (i == 3 || k == len) 61 { 62 ch[3] = '\0'; 63 char c = (char)(atoi(ch)); 64 toReturn[j] = c; 65 j++, i = 0; 66 } 67 } 68 toReturn[j] = '\0'; 69 strcpy(decryptedStr, toReturn); 70 } 71 72 bool ReadAndSendData(struct publicKeyRsa* rk, int socket) 73 { 74 char* message; 75 int choice; 76 printf("\n\nWhat do you want to do?\n\n"); 77 printf("1)Send a message\n"); 78 printf("2)Send data from file\n"); 79 printf("3)Exit\n"); 80 do 81 { 82 printf("Choose an option:"); 83 scanf("%d", &choice); 84 switch (choice) 85 { 86 case 1: 87 { 88 message = ReadMessage(); 89 SendData(message, rk, socket); 90 free(message); 91 message = NULL;
83
92 break; 93 } 94 case 2: 95 { 96 message = ReadFromFile(); 97 SendData(message, rk, socket); 98 free(message); 99 message = NULL; 100 break; 101 } 102 case 3: 103 SendData("EOTS~EOTS~EOTS", rk, socket); 104 break; 105 default: 106 printf("\nInvalid choice!\n\n"); 107 } 108 }while (choice != 1 && choice != 2 && choice != 3); 109 return choice == 3; 110 } 111 112 char* ReadMessage() 113 { 114 char *message = NULL; 115 char buffer[BUFSIZ + 1]; 116 int messLen = 0; 117 printf("\nEnter the message to send: "); 118 getchar(); 119 do 120 { 121 fgets(buffer, BUFSIZ, stdin); 122 message = ResizeDinamicString(message, messLen +
strlen(buffer)); 123 strcat(message, buffer); 124 messLen = strlen(message); 125 }while (buffer[strlen(buffer) -‐ 1] != '\n'); 126 message[messLen -‐ 1]= '\0'; 127 message = ResizeDinamicString(message, strlen(message) + 11); 128 strcat(message, "EOF~EOF~EOF"); 129 return message; 130 } 131 132 char* ReadFromFile() 133 { 134 FILE* source; 135 char fileName[1024], buffer[BUFSIZ + 1]; 136 char *fileData = NULL; 137 int dataLen = 0; 138 do 139 { 140 printf("\nInsert file name with extension: "); 141 scanf("%s", fileName); 142 if ((source = fopen(fileName, "r")) == NULL) 143 printf("\nCannot read the file...Try again\n"); 144 }while (source == NULL); 145 146 while (!feof(source)) 147 {
84
148 fgets(buffer, BUFSIZ, source); 149 fileData = ResizeDinamicString(fileData, dataLen +
strlen(buffer)); 150 strcat(fileData, buffer); 151 dataLen = strlen(fileData); 152 } 153 fileData[strlen(fileData)]= '\0'; 154 fileData = ResizeDinamicString(fileData, strlen(fileData) + 11); 155 strcat(fileData, "EOF~EOF~EOF"); 156 fclose(source); 157 return fileData; 158 } 159 160 void SendData(char* data, struct publicKeyRsa* pbk, int socket) 161 { 162 int i = 0, ii = 0; 163 int nSize = mpz_sizeinbase(pbk-‐>n, 10); 164 char toEncrypt[(nSize * 4) + 1 ], *encryptedStr; 165 char toSend[nSize + 1]; 166 mpz_t encrypted; 167 char ch[4]; 168 mpz_init(encrypted); 169 toEncrypt[0] = '\0'; 170 while (i <= strlen(data)) 171 { 172 sprintf(ch, "%.3d", (unsigned char)data[i]); 173 strcat(toEncrypt, ch); 174 mpz_set_str(encrypted, toEncrypt, 10); 175 toSend[ii] = data[i]; 176 if (mpz_cmp(encrypted, pbk-‐>n) >= 0 || i == strlen(data)) 177 { 178 toSend[ii] = '\0'; 179 printf("\n\nData to send:\n%s", toSend); 180 toEncrypt[strlen(toEncrypt) -‐ 3] = '\0'; 181 printf("\n\nData to send(in decimal):\n%s\n\n",
toEncrypt); 182 encryptedStr = RsaEncrypt(toEncrypt, pbk); 183 printf("Encrypted data to send(in decimal):\n%s\n\n",
encryptedStr); 184 udp_reply(socket, encryptedStr); 185 ii=0; 186 toSend[ii] = data[i]; 187 strcpy(toEncrypt, ch); 188 if (encryptedStr != NULL) 189 free(encryptedStr); 190 encryptedStr = NULL; 191 sleep(1); 192 } 193 ii++; 194 i++; 195 } 196 mpz_clear(encrypted); 197 } 198 199 char* ResizeDinamicString(char* toResize, int newDim) 200 { 201 char*resized = malloc((newDim + 1)*sizeof(char));
85
202 if (resized == NULL) 203 printf("\n\nFailed to realloc.\n\n"); 204 resized[0] = '\0'; 205 if (toResize != NULL) 206 { 207 strcpy(resized, toResize); 208 free(toResize); 209 toResize = NULL; 210 } 211 return resized; 212 } Rsa_s.c 1 #include "RsaLib.h" 2 #include "RsaDataLib.h" 3 4 int main(int argc, char *argv[]) 5 { 6 struct publicKeyRsa srv; 7 int sk, udpPort; 8 bool done; 9 char serverIpAdrress[1024]; 10 mpz_inits(srv.e, srv.n, NULL); 11 if (argc == 1) 12 { 13 printf("Server ip address: "); 14 scanf("%s", serverIpAdrress); 15 printf("Udp port:"); 16 scanf("%d", &udpPort); 17 } 18 else if (argc == 3) 19 { 20 strcpy(serverIpAdrress, argv[1]); 21 udpPort = atoi(argv[2]); 22 } 23 else 24 { 25 printf("Required arguments: server ip address, udp port\n"); 26 exit(1); 27 } 28 if ((sk = create_udp_server(serverIpAdrress, udpPort)) < 0) 29 { 30 printf("Cannot open server socket\n"); 31 exit(1); 32 } 33 if (!ReceivePublicKey(&srv,sk)) 34 exit(1); 35 do 36 { 37 done = ReadAndSendData(&srv, sk); 38 }while (!done); 39 printf("\n\nExit...\n\n"); 40 close_udp_socket(sk); 41 mpz_clears(srv.e, srv.n, NULL); 42 return EXIT_SUCCESS;
86
43 } Rsa_c.c 1 #include "RsaLib.h" 2 #include "RsaDataLib.h" 3 4 int main(int argc, char *argv[]) 5 { 6 struct keysRsa clnt; 7 int sk; 8 int udpPort; 9 char serverIpAdrress[1024]; 10 mpz_inits(clnt.p, clnt.q, clnt.n, clnt.phi, clnt.e, clnt.d, NULL); 11 if (argc == 1) 12 { 13 printf("Server ip address: "); 14 scanf("%s", serverIpAdrress); 15 printf("Udp port:"); 16 scanf("%d", &udpPort); 17 } 18 else if (argc == 3) 19 { 20 strcpy(serverIpAdrress,argv[1]); 21 udpPort = atoi(argv[2]); 22 } 23 else 24 { 25 printf("Required arguments: server ip address, udp port\n"); 26 exit(1); 27 } 28 if ((sk = create_udp_client()) < 0) 29 { 30 printf("Cannot open client socket\n"); 31 exit(1); 32 } 33 GenerateEncryptionKeys(&clnt); 34 if (!SendPublicKey(sk, serverIpAdrress, udpPort, &clnt)) 35 exit(1); 36 ReceiveAndDecryptData(&clnt, sk); 37 close_udp_socket(sk); 38 mpz_clears(clnt.p, clnt.q, clnt.n, clnt.phi, clnt.e, clnt.d,
NULL); 39 return EXIT_SUCCESS; 40 }