2 Algoritmo Diffie-Hellman 2.1 Descrizione
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.
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++.
È 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.