Guida Crittografia Cifratura comunicazione Client - Server [C#]

Ti è piaciuta questa guida?

  • Si

    Voti: 1 100.0%
  • No

    Voti: 0 0.0%

  • Votanti totali
    1

omar.tornaghi

Utente Iron
21 Marzo 2019
1
1
6
18
Ultima modifica:
Guida:
Ciao a tutti, oggi voglio esporre a voi la mia guida riguardante l'utilizzo della cifratura simmetrica ed asimmetrica per la comunicazione client - server.

Concetti base prima di iniziare:
Cifratura simmetrica:

La cifratura simmetrica consiste nell'utilizzare la stessa chiave per cifrare e decifrare un messaggio. In genere prevede una chiave(key) ed un vettore di inizializzazione(IV).

Cifratura asimmetrica:
La cifratura asimmetrica prevede due chiavi:
- Chiave pubblica: chiave che possono conoscere tutti
- Chiave privata : chiave che conosce solo l'host
Le due chiavi sono tali che se cifro con la chiave pubblica posso decifrare con la chiave privata e viceversa ma, se cifro con la chiave pubblica, non posso decifrare con la chiave pubblica e così anche con la chiave privata.

Il programma:
Ora che sappiamo i concetti base base base delle due crittografie supponiamo di volerli applicare in un'applicazione client - server:
Il linguaggio da me utilizzato sarà il C# perché fornisce delle ottime classi per gestire le due cifrature.
La connessione verrà implementata utilizzando i socket TCP(TcpListener e TcpClient).

Algoritmo:
Per poter cifrare con successo una connessione dovremo utilizzare entrambe le due cifrature.
Per la cifratura simmetrica utilizzeremo un algoritmo chiamato Rijndael, mentre per la cifratura asimmetrica RSA.
L'algoritmo che ho pensato prevede le seguenti fasi:
1) Il client si connette al server
2) Il client genera la sua chiave privata e chiave pubblica
3) Il client invia la sua chiave pubblica in chiaro(non criptata) al server
4) Il server riceve la chiave pubblica
5) Il server genera chiave e vettore di inizializzazione Rijndael
6) Il server cripta la chiave e il vettore di inizializzazione utilizzando la chiave pubblica inviata dal client
7) Il server invia la chiave e il vettore al client
8) Il client riceve i due parametri
9) Il client decripta chiave e VI utilizzando la sua chiave privata
A questo punto il server ed il client hanno entrambi la stessa chiave e lo stesso vettore Rijndael, da questo punto fino alla fine della connessione utilizzeranno questi due parametri
per cifrare e decifrare i messaggi, inoltre siamo certi che nessuno potrà leggere la chiave Rijndael perché essa è stata criptata usando la chiave pubblica e come abbiamo visto in precedenza solamente con la chiave privata si può decriptare.

Codici:
Per l'invio e la ricezione dei messaggi ho creato una classe chiamata "Protocol" che contiene i metodi statici per farlo. Dato che lo scopo di questa guida è la crittografia, non spiegherò queste funzioni ma mi limito a riportarne il codice.
La classe "Sicurezza" conterrà invece tutti i metodi per la criptazione e la decriptazione dei messaggi.
Il codice del client e del server viene spiegato utilizzando i commenti.
Protocol:
C#:
public class Protocol
{
    public static string ACK = "RIS_POS";
    private static int BUFFER_LENGTH = 2048;

    public static void Invia(NetworkStream ns, string message)
    {
        byte[] buffer = Encoding.ASCII.GetBytes(message);
        ns.Write(buffer, 0, buffer.Length);
        byte[] bufferResp = new byte[BUFFER_LENGTH];
        int bytesRead = ns.Read(bufferResp, 0, bufferResp.Length);
        string response = Encoding.ASCII.GetString(bufferResp, 0, bytesRead);
        while (response != ACK)
        {
            //Riinvio del messaggio
            ns.Write(buffer, 0, buffer.Length);
            bytesRead = ns.Read(bufferResp, 0, bufferResp.Length);
            response = Encoding.ASCII.GetString(bufferResp, 0, bytesRead);
        }
    }

    public static string Ricevi(NetworkStream ns)
    {
        byte[] buffer = new byte[BUFFER_LENGTH];
        int bytesRead = ns.Read(buffer, 0, buffer.Length);
        //Invio la conferma
        byte[] buffResp = Encoding.ASCII.GetBytes(ACK);
        ns.Write(buffResp, 0, buffResp.Length);
        return Encoding.ASCII.GetString(buffer, 0, bytesRead);
    }

    //Metodi per l'invio e la ricezione sicura dei messaggi
    public static void S_Invia(NetworkStream ns, Rijndael provider, string message)
    {
        Invia(ns, Sicurezza.RIJCrifraMessaggio(provider, message));
    }

    public static string S_Ricevi(NetworkStream ns, Rijndael provider)
    {
        return Sicurezza.RIJDecrifraMessaggio(provider, Ricevi(ns));
    }
}

Sicurezza:
C#:
public class Sicurezza
{
    public static string CONFERMA_CRYPTAZIONE = "###CRY-POSITIVE###";

    public static string RSACifraMessaggio(RSACryptoServiceProvider RSACry, string messaggio)
    {
        //Cifra una stringa utilizzando un provider RSA, ritorna la stringa cifrata
        byte[] bytesMessNCifr = System.Text.Encoding.Unicode.GetBytes(messaggio);
        byte[] bytesMessCifr = RSACry.Encrypt(bytesMessNCifr, false);
        return Convert.ToBase64String(bytesMessCifr);
    }

    public static string RSADecifraMessaggio(RSACryptoServiceProvider RSADecry, string messaggioCifr)
    {
        //Decifra una stringa utilizzando un provider RSA, ritorna la stringa decifrata
        byte[] byteMessCifr = Convert.FromBase64String(messaggioCifr);
        byte[] byteMessNCifr = RSADecry.Decrypt(byteMessCifr, false);
        return Encoding.Unicode.GetString(byteMessNCifr);
    }

    public static string RIJCrifraMessaggio(Rijndael rijAlg, string testoIn)
    {
        //Cifra una stringa utilizzando l'algoritmo Rijndael, ritorna la stringa cifrata
        byte[] byteTestoCri;
        ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
        using (MemoryStream msEncrypt = new MemoryStream())
        {
            using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
            {
                using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                {
                    swEncrypt.Write(testoIn);
                }
                byteTestoCri = msEncrypt.ToArray();
            }
        }
        return Convert.ToBase64String(byteTestoCri);
    }

    public static string RIJDecrifraMessaggio(Rijndael rijAlg, string cipherText)
    {
        //Decifra una stringa utilizzando l'algoritmo Rijndael, ritorna la stringa decifrata
        byte[] bytesCifr = Convert.FromBase64String(cipherText);
        string plaintext = null;
        ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
        using (MemoryStream msDecrypt = new MemoryStream(bytesCifr))
        {
            using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
            {
                using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                {
                    plaintext = srDecrypt.ReadToEnd();
                }
            }
        }
        return plaintext;
    }

}

Client:
C#:
public class Client
{
    public void Attivita()
    {
        //Mi connetto al server e creo lo stream
        TcpClient client = new TcpClient(Dns.GetHostName(), 10101);
        client.NoDelay = true;
        NetworkStream ns = client.GetStream();

        //Creo un oggetto RSACryptoServiceProvider
        //che mi permette di generare chiave pubblica e chiave privata
        RSACryptoServiceProvider RSACryp = new RSACryptoServiceProvider(2048);

        //Esporto la chiave pubblica in XML così che il server possa leggerla più facilmente
        string publicKeyXML = RSACryp.ToXmlString(false);

        //Invio la chiave al server
        Protocol.Invia(ns, publicKeyXML);

        //Ricevo chiave e vettore di inizializzazione rijndael e inizializzo il crypter
        string key = Sicurezza.RSADecifraMessaggio(RSACryp, Protocol.Ricevi(ns));
        string IV = Sicurezza.RSADecifraMessaggio(RSACryp, Protocol.Ricevi(ns));
        Rijndael rijndael = Rijndael.Create();
        rijndael.Key = Convert.FromBase64String(key);
        rijndael.IV = Convert.FromBase64String(IV);

        //Trasmetto una stringa di prova al server
        Protocol.S_Invia(ns, rijndael, Sicurezza.CONFERMA_CRYPTAZIONE);
    }
}

Server:
C#:
public class Server
{
    public void Attivita()
    {
        //Inizializzo il listener e accetto le connessioni
        TcpListener listener = new TcpListener(IPAddress.Any, 10101);
        listener.Start();
        TcpClient client = listener.AcceptTcpClient();
        client.NoDelay = true;
        NetworkStream ns = client.GetStream();

        //Ricevo la chiave pubblica in XML e la importo nell'oggetto RSACryptoServiceProvider
        RSACryptoServiceProvider RSACryp = new RSACryptoServiceProvider(2048);
        string publicKeyXML = Protocol.Ricevi(ns);
        RSACryp.FromXmlString(publicKeyXML);

        //Genero la chiave Rijndael
        Rijndael rijndael = Rijndael.Create();

        //Converto chiave e vettore di inizializzazione in stringa
        string key = Convert.ToBase64String(rijndael.Key);
        string IV = Convert.ToBase64String(rijndael.IV);

        //Invio al client con la cifratura RSA
        Protocol.Invia(ns, Sicurezza.RSACifraMessaggio(RSACryp, key));
        Protocol.Invia(ns, Sicurezza.RSACifraMessaggio(RSACryp, IV));

        //Ricevo la stringa di prova
        string conferma = Sicurezza.RIJDecrifraMessaggio(rijndael, Protocol.Ricevi(ns));
        Console.WriteLine(conferma);
    }
}

Considerazioni:
Per testare questo codice potete creare le classi con il relativo codice e avviare due Thread separati.
Esempio:
C#:
using System.Threading;

class Program
{
    static void Main(string[] args)
    {
        //Creo client e server e li associo ai due thread
        Server s = new Server();
        Client c = new Client();
        Thread t1 = new Thread(new ThreadStart(s.Attivita));
        Thread t2 = new Thread(new ThreadStart(c.Attivita));
        //Il thread del server parte per primo
        t1.Start();
        while (!t1.IsAlive) ;
        t2.Start();
    }

}
 
Guida:
Ciao a tutti, oggi voglio esporre a voi la mia guida riguardante l'utilizzo della cifratura simmetrica ed asimmetrica per la comunicazione client - server.

Concetti base prima di iniziare:
Cifratura simmetrica:

La cifratura simmetrica consiste nell'utilizzare la stessa chiave per cifrare e decifrare un messaggio. In genere prevede una chiave(key) ed un vettore di inizializzazione(IV).

Cifratura asimmetrica:
La cifratura asimmetrica prevede due chiavi:
- Chiave pubblica: chiave che possono conoscere tutti
- Chiave privata : chiave che conosce solo l'host
Le due chiavi sono tali che se cifro con la chiave pubblica posso decifrare con la chiave privata e viceversa ma, se cifro con la chiave pubblica, non posso decifrare con la chiave pubblica e così anche con la chiave privata.

Il programma:
Ora che sappiamo i concetti base base base delle due crittografie supponiamo di volerli applicare in un'applicazione client - server:
Il linguaggio da me utilizzato sarà il C# perché fornisce delle ottime classi per gestire le due cifrature.
La connessione verrà implementata utilizzando i socket TCP(TcpListener e TcpClient).

Algoritmo:
Per poter cifrare con successo una connessione dovremo utilizzare entrambe le due cifrature.
Per la cifratura simmetrica utilizzeremo un algoritmo chiamato Rijndael, mentre per la cifratura asimmetrica RSA.
L'algoritmo che ho pensato prevede le seguenti fasi:
1) Il client si connette al server
2) Il client genera la sua chiave privata e chiave pubblica
3) Il client invia la sua chiave pubblica in chiaro(non criptata) al server
4) Il server riceve la chiave pubblica
5) Il server genera chiave e vettore di inizializzazione Rijndael
6) Il server cripta la chiave e il vettore di inizializzazione utilizzando la chiave pubblica inviata dal client
7) Il server invia la chiave e il vettore al client
8) Il client riceve i due parametri
9) Il client decripta chiave e VI utilizzando la sua chiave privata
A questo punto il server ed il client hanno entrambi la stessa chiave e lo stesso vettore Rijndael, da questo punto fino alla fine della connessione utilizzeranno questi due parametri
per cifrare e decifrare i messaggi, inoltre siamo certi che nessuno potrà leggere la chiave Rijndael perché essa è stata criptata usando la chiave pubblica e come abbiamo visto in precedenza solamente con la chiave privata si può decriptare.

Codici:
Per l'invio e la ricezione dei messaggi ho creato una classe chiamata "Protocol" che contiene i metodi statici per farlo. Dato che lo scopo di questa guida è la crittografia, non spiegherò queste funzioni ma mi limito a riportarne il codice.
La classe "Sicurezza" conterrà invece tutti i metodi per la criptazione e la decriptazione dei messaggi.
Il codice del client e del server viene spiegato utilizzando i commenti.
Protocol:
C#:
public class Protocol
{
public static string ACK = "RIS_POS";
private static int BUFFER_LENGTH = 2048;

public static void Invia(NetworkStream ns, string message)
{
byte[] buffer = Encoding.ASCII.GetBytes(message);
ns.Write(buffer, 0, buffer.Length);
byte[] bufferResp = new byte[BUFFER_LENGTH];
int bytesRead = ns.Read(bufferResp, 0, bufferResp.Length);
string response = Encoding.ASCII.GetString(bufferResp, 0, bytesRead);
while (response != ACK)
{
//Riinvio del messaggio
ns.Write(buffer, 0, buffer.Length);
bytesRead = ns.Read(bufferResp, 0, bufferResp.Length);
response = Encoding.ASCII.GetString(bufferResp, 0, bytesRead);
}
}

public static string Ricevi(NetworkStream ns)
{
byte[] buffer = new byte[BUFFER_LENGTH];
int bytesRead = ns.Read(buffer, 0, buffer.Length);
//Invio la conferma
byte[] buffResp = Encoding.ASCII.GetBytes(ACK);
ns.Write(buffResp, 0, buffResp.Length);
return Encoding.ASCII.GetString(buffer, 0, bytesRead);
}

//Metodi per l'invio e la ricezione sicura dei messaggi
public static void S_Invia(NetworkStream ns, Rijndael provider, string message)
{
Invia(ns, Sicurezza.RIJCrifraMessaggio(provider, message));
}

public static string S_Ricevi(NetworkStream ns, Rijndael provider)
{
return Sicurezza.RIJDecrifraMessaggio(provider, Ricevi(ns));
}
}

Sicurezza:
C#:
public class Sicurezza
{
public static string CONFERMA_CRYPTAZIONE = "###CRY-POSITIVE###";

public static string RSACifraMessaggio(RSACryptoServiceProvider RSACry, string messaggio)
{
//Cifra una stringa utilizzando un provider RSA, ritorna la stringa cifrata
byte[] bytesMessNCifr = System.Text.Encoding.Unicode.GetBytes(messaggio);
byte[] bytesMessCifr = RSACry.Encrypt(bytesMessNCifr, false);
return Convert.ToBase64String(bytesMessCifr);
}

public static string RSADecifraMessaggio(RSACryptoServiceProvider RSADecry, string messaggioCifr)
{
//Decifra una stringa utilizzando un provider RSA, ritorna la stringa decifrata
byte[] byteMessCifr = Convert.FromBase64String(messaggioCifr);
byte[] byteMessNCifr = RSADecry.Decrypt(byteMessCifr, false);
return Encoding.Unicode.GetString(byteMessNCifr);
}

public static string RIJCrifraMessaggio(Rijndael rijAlg, string testoIn)
{
//Cifra una stringa utilizzando l'algoritmo Rijndael, ritorna la stringa cifrata
byte[] byteTestoCri;
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(testoIn);
}
byteTestoCri = msEncrypt.ToArray();
}
}
return Convert.ToBase64String(byteTestoCri);
}

public static string RIJDecrifraMessaggio(Rijndael rijAlg, string cipherText)
{
//Decifra una stringa utilizzando l'algoritmo Rijndael, ritorna la stringa decifrata
byte[] bytesCifr = Convert.FromBase64String(cipherText);
string plaintext = null;
ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
using (MemoryStream msDecrypt = new MemoryStream(bytesCifr))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
plaintext = srDecrypt.ReadToEnd();
}
}
}
return plaintext;
}

}

Client:
C#:
public class Client
{
public void Attivita()
{
//Mi connetto al server e creo lo stream
TcpClient client = new TcpClient(Dns.GetHostName(), 10101);
client.NoDelay = true;
NetworkStream ns = client.GetStream();

//Creo un oggetto RSACryptoServiceProvider
//che mi permette di generare chiave pubblica e chiave privata
RSACryptoServiceProvider RSACryp = new RSACryptoServiceProvider(2048);

//Esporto la chiave pubblica in XML così che il server possa leggerla più facilmente
string publicKeyXML = RSACryp.ToXmlString(false);

//Invio la chiave al server
Protocol.Invia(ns, publicKeyXML);

//Ricevo chiave e vettore di inizializzazione rijndael e inizializzo il crypter
string key = Sicurezza.RSADecifraMessaggio(RSACryp, Protocol.Ricevi(ns));
string IV = Sicurezza.RSADecifraMessaggio(RSACryp, Protocol.Ricevi(ns));
Rijndael rijndael = Rijndael.Create();
rijndael.Key = Convert.FromBase64String(key);
rijndael.IV = Convert.FromBase64String(IV);

//Trasmetto una stringa di prova al server
Protocol.S_Invia(ns, rijndael, Sicurezza.CONFERMA_CRYPTAZIONE);
}
}

Server:
C#:
public class Server
{
public void Attivita()
{
//Inizializzo il listener e accetto le connessioni
TcpListener listener = new TcpListener(IPAddress.Any, 10101);
listener.Start();
TcpClient client = listener.AcceptTcpClient();
client.NoDelay = true;
NetworkStream ns = client.GetStream();

//Ricevo la chiave pubblica in XML e la importo nell'oggetto RSACryptoServiceProvider
RSACryptoServiceProvider RSACryp = new RSACryptoServiceProvider(2048);
string publicKeyXML = Protocol.Ricevi(ns);
RSACryp.FromXmlString(publicKeyXML);

//Genero la chiave Rijndael
Rijndael rijndael = Rijndael.Create();

//Converto chiave e vettore di inizializzazione in stringa
string key = Convert.ToBase64String(rijndael.Key);
string IV = Convert.ToBase64String(rijndael.IV);

//Invio al client con la cifratura RSA
Protocol.Invia(ns, Sicurezza.RSACifraMessaggio(RSACryp, key));
Protocol.Invia(ns, Sicurezza.RSACifraMessaggio(RSACryp, IV));

//Ricevo la stringa di prova
string conferma = Sicurezza.RIJDecrifraMessaggio(rijndael, Protocol.Ricevi(ns));
Console.WriteLine(conferma);
}
}

Considerazioni:
Per testare questo codice potete creare le classi con il relativo codice e avviare due Thread separati.
Esempio:
C#:
using System.Threading;

class Program
{
static void Main(string[] args)
{
//Creo client e server e li associo ai due thread
Server s = new Server();
Client c = new Client();
Thread t1 = new Thread(new ThreadStart(s.Attivita));
Thread t2 = new Thread(new ThreadStart(c.Attivita));
//Il thread del server parte per primo
t1.Start();
while (!t1.IsAlive) ;
t2.Start();
}

}
Ottima guida. Complimenti!