9 votes

Exemple de cryptographie asymétrique en C#

Je dois envoyer des données confidentielles à un serveur via une connexion TCP. J'ai fait beaucoup de recherches et je comprends la partie théorique. Sur la base de mes recherches, je veux faire ce qui suit :

Notez qu'il y a un serveur et un client : (nous supposons que les clés publiques du client ou du serveur peuvent être obtenues par n'importe qui).

  1. le client crée sa clé publique et sa clé privée. Il peut chiffrer avec sa clé privée et déchiffrer avec sa clé publique.

  2. Le serveur crée ses clés publique et privée. La clé privée est utilisée pour décrypter les messages et la clé publique pour les crypter. (remarque : c'est l'inverse pour le client).

  3. le client obtient la clé publique du serveur. le client pourra alors crypter les messages avec cette clé et la seule personne qui pourra décrypter ce message sera la clé privée du serveur.

  4. Comme le serveur doit être certain que le message provient de ce client spécifique, ce dernier crypte son nom (signature) à l'aide de sa clé privée.

  5. le message du client contiendra donc : les données à envoyer, la clé publique du client, le nom du client crypté avec la clé privée du client.

  6. le client crypte le message avec la clé publique du serveur. le client envoie ensuite ce message au serveur.

  7. le serveur décryptera le message qu'il vient de recevoir avec sa clé privée.

  8. une fois que le message est décrypté, il contient les données (informations), la signature cryptée et la clé publique du client.

  9. enfin, le serveur décrypte la signature du client à l'aide de la clé publique contenue dans le message pour vérifier que le message provient bien de ce client.


Voici comment fonctionne la cryptographie asymétrique. J'ai également fait des recherches sur les classes qui vous permettent de créer ces paires de clés avec le cadre .NET. Les classes que j'ai recherchées et qui permettent de créer ces paires de clés publiques et privées sont les suivantes :

System.Security.Cryptography.DES
System.Security.Cryptography.DSACryptoServiceProvider 
System.Security.Cryptography.ECDsa 
System.Security.Cryptography.ECDsaCng 
System.Security.Cryptography.ECDiffieHellman 
System.Security.Cryptography.ECDiffieHellmanCng 
System.Security.Cryptography.RSA 
System.Security.Cryptography.RSACryptoServiceProvider 

Le problème est donc de savoir comment utiliser une de ces classes pour le faire en C#. Je comprends comment fonctionne la partie théorique mais comment faire ce que je viens de décrire avec du code. J'ai cherché des exemples mais j'ai du mal à les comprendre.

Voici un exemple que j'ai trouvé et qui, selon moi, fait ce que j'ai décrit :

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace Example
{
    class Program
    {
        static CngKey aliceKey;
        static CngKey bobKey;
        static byte[] alicePubKeyBlob;
        static byte[] bobPubKeyBlob;

        static void Main()
        {
            CreateKeys();
            byte[] encrytpedData = AliceSendsData("secret message");
            BobReceivesData(encrytpedData);

            Console.Read();

        }

        private static void CreateKeys()
        {
            aliceKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256);
            bobKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256);
            alicePubKeyBlob = aliceKey.Export(CngKeyBlobFormat.EccPublicBlob);
            bobPubKeyBlob = bobKey.Export(CngKeyBlobFormat.EccPublicBlob);
        }

        private static byte[] AliceSendsData(string message)
        {
            Console.WriteLine("Alice sends message: {0}", message);
            byte[] rawData = Encoding.UTF8.GetBytes(message);
            byte[] encryptedData = null;

            using (var aliceAlgorithm = new ECDiffieHellmanCng(aliceKey))
            using (CngKey bobPubKey = CngKey.Import(bobPubKeyBlob,
                  CngKeyBlobFormat.EccPublicBlob))
            {
                byte[] symmKey = aliceAlgorithm.DeriveKeyMaterial(bobPubKey);
                Console.WriteLine("Alice creates this symmetric key with " +
                      "Bobs public key information: {0}",
                      Convert.ToBase64String(symmKey));

                using (var aes = new AesCryptoServiceProvider())
                {
                    aes.Key = symmKey;
                    aes.GenerateIV();
                    using (ICryptoTransform encryptor = aes.CreateEncryptor())
                    using (MemoryStream ms = new MemoryStream())
                    {
                        // create CryptoStream and encrypt data to send
                        var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write);

                        // write initialization vector not encrypted
                        ms.Write(aes.IV, 0, aes.IV.Length);
                        cs.Write(rawData, 0, rawData.Length);
                        cs.Close();
                        encryptedData = ms.ToArray();
                    }
                    aes.Clear();
                }
            }
            Console.WriteLine("Alice: message is encrypted: {0}",
                  Convert.ToBase64String(encryptedData)); ;
            Console.WriteLine();
            return encryptedData;
        }

        private static void BobReceivesData(byte[] encryptedData)
        {
            Console.WriteLine("Bob receives encrypted data");
            byte[] rawData = null;

            var aes = new AesCryptoServiceProvider();

            int nBytes = aes.BlockSize >> 3;
            byte[] iv = new byte[nBytes];
            for (int i = 0; i < iv.Length; i++)
                iv[i] = encryptedData[i];

            using (var bobAlgorithm = new ECDiffieHellmanCng(bobKey))
            using (CngKey alicePubKey = CngKey.Import(alicePubKeyBlob,
                  CngKeyBlobFormat.EccPublicBlob))
            {
                byte[] symmKey = bobAlgorithm.DeriveKeyMaterial(alicePubKey);
                Console.WriteLine("Bob creates this symmetric key with " +
                      "Alices public key information: {0}",
                      Convert.ToBase64String(symmKey));

                aes.Key = symmKey;
                aes.IV = iv;

                using (ICryptoTransform decryptor = aes.CreateDecryptor())
                using (MemoryStream ms = new MemoryStream())
                {
                    var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write);
                    cs.Write(encryptedData, nBytes, encryptedData.Length - nBytes);
                    cs.Close();

                    rawData = ms.ToArray();

                    Console.WriteLine("Bob decrypts message to: {0}",
                          Encoding.UTF8.GetString(rawData));
                }
                aes.Clear();
            }
        }
    }
}

Dans ce programme, je pense que le client est Alice et le serveur Bob. Je dois diviser ce programme en deux parties. J'ai du mal à le comprendre et si j'essaie, il est fort probable que je parvienne à le faire fonctionner. En tout cas, comment puis-je diviser ce programme en un code côté serveur et un code côté client. Je sais comment envoyer des octets entre le serveur et le client. Je n'ai pas envie de le faire fonctionner sans comprendre ce qui se passe.


EDITAR

J'ai réussi à séparer le code : voici le code du serveur (l'adresse IP de mon ordinateur est 192.168.0.120) :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Security.Cryptography;
using System.IO;

namespace ServerListener
{
    class Program
    {
        static TcpListener server;

        //static CngKey aliceKey;
        static CngKey bobKey;
        static byte[] alicePubKeyBlob;
        static byte[] bobPubKeyBlob;

        static void Main(string[] args)
        {

            CreateKeys();

            IPAddress ipAddress = IPAddress.Parse("192.168.0.120");
            server = new TcpListener(ipAddress, 54540);
            server.Start();
            var client = server.AcceptTcpClient();
            var stream = client.GetStream();

            alicePubKeyBlob = new byte[bobPubKeyBlob.Length];
            stream.Read(alicePubKeyBlob, 0, alicePubKeyBlob.Length);

            stream.Write(bobPubKeyBlob, 0, bobPubKeyBlob.Length);

            byte[] encrytpedData = new byte[32];

            stream.Read(encrytpedData, 0, encrytpedData.Length);

            BobReceivesData(encrytpedData);

        }

        private static void CreateKeys()
        {
            //aliceKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256);
            bobKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256);
            //alicePubKeyBlob = aliceKey.Export(CngKeyBlobFormat.EccPublicBlob);
            bobPubKeyBlob = bobKey.Export(CngKeyBlobFormat.EccPublicBlob);
        }

        private static void BobReceivesData(byte[] encryptedData)
        {
            Console.WriteLine("Bob receives encrypted data");
            byte[] rawData = null;

            var aes = new AesCryptoServiceProvider();

            int nBytes = aes.BlockSize >> 3;
            byte[] iv = new byte[nBytes];
            for (int i = 0; i < iv.Length; i++)
                iv[i] = encryptedData[i];

            using (var bobAlgorithm = new ECDiffieHellmanCng(bobKey))
            using (CngKey alicePubKey = CngKey.Import(alicePubKeyBlob,
                  CngKeyBlobFormat.EccPublicBlob))
            {
                byte[] symmKey = bobAlgorithm.DeriveKeyMaterial(alicePubKey);
                Console.WriteLine("Bob creates this symmetric key with " +
                      "Alices public key information: {0}",
                      Convert.ToBase64String(symmKey));

                aes.Key = symmKey;
                aes.IV = iv;

                using (ICryptoTransform decryptor = aes.CreateDecryptor())
                using (MemoryStream ms = new MemoryStream())
                {
                    var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write);
                    cs.Write(encryptedData, nBytes, encryptedData.Length - nBytes);
                    cs.Close();

                    rawData = ms.ToArray();

                    Console.WriteLine("Bob decrypts message to: {0}",
                          Encoding.UTF8.GetString(rawData));
                }
                aes.Clear();
            }
        }
    }
}

et voici le code client :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Security.Cryptography;
using System.IO;

namespace ClientAlice
{
    class Program
    {
        static CngKey aliceKey;
        //static CngKey bobKey;
        static byte[] alicePubKeyBlob;
        static byte[] bobPubKeyBlob;

        static void Main(string[] args)
        {

            CreateKeys();
            bobPubKeyBlob = new byte[alicePubKeyBlob.Length];

            TcpClient alice = new TcpClient("192.168.0.120", 54540);

            var stream = alice.GetStream();
            stream.Write(alicePubKeyBlob, 0, alicePubKeyBlob.Length);

            stream.Read(bobPubKeyBlob, 0, bobPubKeyBlob.Length);

            byte[] encrytpedData = AliceSendsData(":)");

            stream.Write(encrytpedData, 0, encrytpedData.Length);

        }

        private static void CreateKeys()
        {
            aliceKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256);
            //bobKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256);
            alicePubKeyBlob = aliceKey.Export(CngKeyBlobFormat.EccPublicBlob);
            //bobPubKeyBlob = bobKey.Export(CngKeyBlobFormat.EccPublicBlob);
        }

        private static byte[] AliceSendsData(string message)
        {
            Console.WriteLine("Alice sends message: {0}", message);
            byte[] rawData = Encoding.UTF8.GetBytes(message);
            byte[] encryptedData = null;

            using (var aliceAlgorithm = new ECDiffieHellmanCng(aliceKey))
            using (CngKey bobPubKey = CngKey.Import(bobPubKeyBlob,
                  CngKeyBlobFormat.EccPublicBlob))
            {
                byte[] symmKey = aliceAlgorithm.DeriveKeyMaterial(bobPubKey);
                Console.WriteLine("Alice creates this symmetric key with " +
                      "Bobs public key information: {0}",
                      Convert.ToBase64String(symmKey));

                using (var aes = new AesCryptoServiceProvider())
                {
                    aes.Key = symmKey;
                    aes.GenerateIV();
                    using (ICryptoTransform encryptor = aes.CreateEncryptor())
                    using (MemoryStream ms = new MemoryStream())
                    {
                        // create CryptoStream and encrypt data to send
                        var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write);

                        // write initialization vector not encrypted
                        ms.Write(aes.IV, 0, aes.IV.Length);
                        cs.Write(rawData, 0, rawData.Length);
                        cs.Close();
                        encryptedData = ms.ToArray();
                    }
                    aes.Clear();
                }
            }
            Console.WriteLine("Alice: message is encrypted: {0}",
                  Convert.ToBase64String(encryptedData)); ;
            Console.WriteLine();
            return encryptedData;
        }
    }
}

Je pense qu'il est assez sûr. Chaque fois qu'il envoie un tableau d'octets différent, il envoie la même information !

44voto

Eric Lippert Points 300275

Comme vous le soulignez, vous êtes un débutant en matière de crypto-monnaies. S'il s'agit d'un projet ludique pour s'initier à la cryptographie, c'est parfait. S'il s'agit d'un véritable code de production vous allez le mettre en œuvre de manière non sécurisée . Vous devriez utiliser des outils standard tels que SSL/HTTPS/whatever pour résoudre ce problème plutôt que de vous tromper vous-même.

Je profite de l'occasion pour souligner les points sur lesquels votre esquisse présente des faiblesses rédhibitoires.

3) le client obtient la clé publique du serveur.

OK. Comment ? C'est l'étape la plus importante. La sécurité de l'ensemble du système repose sur cette étape, et vous avez complètement occulté son fonctionnement. Comment le client obtient-il la clé publique du serveur ? Qu'est-ce qui empêche une personne malveillante d'appeler le client et de lui dire "hé client, je suis le serveur. Voici ma clé publique !" Et maintenant, le client crypte des messages qui ne peuvent être décryptés que par le malfaiteur. Ce dernier possède la clé publique du véritable serveur, il recrypte donc le message avec la véritable clé publique et l'envoie. L'ensemble de votre système est ainsi compromis. Le système cryptographique à clé publique n'est sûr que s'il existe un mécanisme d'échange de clés sécurisé . (Une question raisonnable se pose alors : si l'on dispose d'un mécanisme d'échange de clés sécurisé, pourquoi ne pas l'utiliser tout simplement pour échanger le message).

4) comme le serveur doit être certain que le message provient de ce client spécifique, ce dernier cryptera son nom (signature) à l'aide de sa clé privée.

Le client doit chiffrer un hachage de l'ensemble du message en guise de signature, et pas seulement d'une partie du message. De cette façon, le serveur a la preuve que l'ensemble du message provient du client.

6) le client crypte le message avec la clé publique du serveur. le client envoie ensuite ce message au serveur.

Il s'agit de extrêmement inefficace . Il est préférable que le serveur et le client se mettent d'accord sur la clé d'un système de cryptage symétrique. La clé peut être transmise entre le serveur et le client à l'aide d'un système cryptographique à clé publique. Le serveur et le client disposent désormais d'une clé secrète partagée qu'ils peuvent utiliser pour cette session de communication.

9) enfin, le serveur décrypte la signature du client à l'aide de la clé publique contenue dans le message pour vérifier que le message provient bien de ce client.

En quoi cela peut-il être utile ? Je veux vous envoyer un message. Vous voulez savoir de qui il vient. Je vous envoie donc une photocopie de mon permis de conduire, afin que vous puissiez comparer la signature sur le permis avec la signature sur le message. Comment savez-vous que je vous ai envoyé mon et non une photocopie du permis de conduire de quelqu'un d'autre ? Cela ne résout en rien le problème de l'authentification du client. Encore une fois, vous devez résoudre le problème de la distribution des clés. Le système dépend de l'existence d'une infrastructure de distribution de clés sécurisée, que vous n'avez pas spécifiée.

5voto

Lucero Points 38928

Je le poste en tant que réponse car il serait trop long pour un commentaire - il ne répond pas spécifiquement à votre question cependant.

Comme mentionné dans le commentaire de driis, vous devriez vraiment vous fier aux solutions existantes qui sont considérées comme sûres. Cela dit, votre protocole présente des problèmes de sécurité :

  • La communication est généralement bidirectionnelle, mais vous ne semblez aborder que la communication unidirectionnelle (de client à serveur). Cela n'a pas beaucoup de sens, puisque vous dites que vous allez utiliser TCP, qui est un protocole bidirectionnel en soi.

  • Les étapes 4 et 5 sont boguées : étant donné que vous envoyez la clé publique du client dans le message, n'importe qui pourrait créer une paire et chiffrer l'identification du client à l'aide de cette paire. D'après votre description, le serveur n'a pas connaissance des clés du client, ce qui fait que cette signature ne fait rien d'autre que d'assurer l'intégrité du message - en particulier, elle ne rend en aucun cas l'identification du client digne de confiance.

Pour une identification correcte, il faut des conditions préalables supplémentaires : le serveur doit connaître à l'avance la clé publique du client ou il doit pouvoir se fier à la déclaration du client en utilisant une tierce partie de confiance. C'est la raison d'être des certificats et des chaînes de confiance : si le client présente un certificat délivré par le tiers X et que le serveur fait confiance à X, il peut supposer que le client est bien celui qu'il prétend être.

SSL prend essentiellement en charge deux modes :

  • Soit seule l'identité du serveur est vérifiée et n'importe quel client peut communiquer avec lui ; soit l'identité du client n'est pas vérifiée, mais il s'agit toujours (après la négociation de la connexion) du même client qui communique avec le serveur. C'est l'usage typique pour les achats en ligne, etc. - vous (en tant que client) faites confiance au serveur et créez une connexion de confiance, mais le serveur ne sait pas qui vous êtes.

  • L'authentification bidirectionnelle peut également être réalisée à l'aide de certificats clients. Le serveur doit connaître et faire confiance soit au certificat du client directement, soit à l'émetteur du certificat du client, afin de négocier la connexion avec succès. Dans ce scénario, le serveur sait effectivement qui est le client, mais la condition préalable mentionnée ci-dessus doit être remplie.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X