Пример асимметричной криптографии в C#

мне нужно отправить конфиденциальные данные на сервер через TCP-соединение. Я провел много исследований, и я понимаю теоретическую часть. Основываясь на том, что я исследовал, я хочу сделать следующее:

обратите внимание, что есть сервер и клиент: (мы предполагаем, что открытые ключи клиента или сервера могут быть получены кем угодно)

  1. клиент создает свой открытый и закрытый ключи. Он способен шифровать с помощью своего закрытого ключа и расшифровывать с помощью своего публичного ключ.

  2. сервер создает свой открытый и закрытый ключи. закрытый ключ используется для расшифровки сообщений, а открытый ключ-для шифрования сообщений. (Примечание-это наоборот, как с клиентом)

  3. клиент get-это открытый ключ сервера. затем клиент сможет шифровать сообщения с помощью этого ключа, и единственным, который сможет расшифровать это сообщение, будет закрытый ключ сервера.

  4. С сервера необходимо быть уверенным, что сообщение исходит от этого конкретного клиента, тогда клиент зашифрует свое имя (подпись) своим закрытым ключом.

  5. таким образом, сообщение клиента будет содержать: данные для отправки, открытый ключ клиента, имя клиента, зашифрованное закрытым ключом клиента.

  6. клиент зашифрует сообщение открытым ключом с сервера. затем клиент отправит сообщение на сервер.

  7. в сервер расшифрует только что полученное сообщение с помощью своего закрытого ключа.

  8. как только сообщение будет расшифровано, оно будет содержать данные (информацию), зашифрованную подпись, открытый ключ от клиента.

  9. наконец, сервер расшифрует подпись клиента с открытым ключом, который содержался в сообщении, чтобы убедиться, что сообщение от этого клиента.


хорошо, вот как работает асимметричная криптография. Я также изучили классы, которые позволяют создавать эти пары ключей с .NET framework. Классы, которые я исследовал, которые позволяют создавать эти пары открытого и закрытого ключей:

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 

Итак, теперь мои проблемы связаны с тем, как я могу использовать один из этих классов для этого с C#? Я понимаю, как работает теоретическая часть, но как мне сделать то, что я только что описал с помощью кода. Я исследовал некоторые примеры, но мне трудно их понять.

вот один пример, который я нашел, что я считаю, делает то, что я описал:

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();
            }
        }
    }
}

в этой программе я считаю, что клиент-Алиса, а сервер-Боб. Я должен разделить эту программу на две части. Мне трудно понять это, и если я попробую, скорее всего, я заставлю его работать. В любом случае, как я могу разделить эту программу на код на стороне сервера и код на стороне клиента. Я знаю, как отправлять байты между сервером и клиентом. Но я не хочу, чтобы это сработало. не понимая, что происходит. может, вы, ребята, покажете мне более простой пример.


редактировать

мне удалось отделить код: вот код сервера (IP-адрес моего компьютера оказался 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();
            }
        }
    }
}

и вот код клиента:

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;
        }
    }
}

Я думаю, что это довольно безопасно. Каждый раз, когда он отправляет другой массив байтов, хотя отправляет ту же информацию!

2 ответов


Как вы заметили, вы новичок в криптографии. Если это забавный игрушечный проект, чтобы узнать о криптографии, отлично. Если это реальный производственный код вы собираетесь реализовать его небезопасно. Вы должны использовать готовые инструменты, такие как SSL/HTTPS/whatever, чтобы решить эту проблему, а не делать это неправильно.

Я воспользуюсь этой возможностью, чтобы указать области, где ваш эскиз смертельно слаб.

3) клиент получает публичный сервер ключ.

OK. Как? это самый важный шаг. безопасность всей системы зависит от этого шага, и вы полностью замазали, как это работает. как клиент получает открытый ключ сервера? что останавливает злого человека от вызова клиента и говоря: "Эй, клиент, я сервер. Вот мой открытый ключ!- А теперь клиент шифрует сообщения, которые может расшифровать только злодей. Злодей имеет открытый ключ реального сервера, поэтому злоумышленник повторно шифрует сообщение с реальным открытым ключом и отправляет его. Таким образом, вся ваша система скомпрометирована. Криптосистема с открытым ключом защищена только если есть безопасный механизм обмена ключами. (И тогда разумный вопрос: если у вас есть безопасный механизм обмена ключами, почему бы просто не использовать его для обмена сообщениями в первую очередь?)

4) так как сервер должен быть уверен, что сообщение приходит от этот конкретный клиент, тогда клиент зашифрует свое имя (подпись) с помощью своего закрытого ключа.

клиент должен зашифровать хэш всего сообщения в качестве подписи, а не только часть сообщения. Таким образом, у сервера есть доказательства того, что все сообщение было от клиента.

6) клиент зашифрует сообщение открытым ключом с сервера. затем клиент отправит сообщение на сервер.

Это крайне неэффективно. Лучше для сервера и клиента согласовать ключ для симметричной криптосистемы. Ключ может передаваться между сервером и клиентом с использованием открытого ключа криптосистемы. Теперь сервер и клиент имеют общий секретный ключ, который они могут использовать для этого сеанса связи.

9) наконец, сервер расшифрует подпись клиента с открытым ключом, который содержался в сообщении, чтобы убедиться, что сообщение от тот клиент.

Как это может помочь? Я хочу отправить тебе сообщение. Вы хотите знать, от кого он исходит. Поэтому я посылаю вам фотокопию моих водительских прав, чтобы вы могли сравнить подпись на лицензии с подписью На сообщении. Откуда ты знаешь, что я послал тебя?--11-->мой водительские права, а не ксерокопия чужих? это не решает проблему аутентификации клиента вообще. опять вам нужно решить ключ проблема распределения. система зависит от наличия защищенной инфраструктуры распределения ключей, которую вы не указали.


публикация в качестве ответа, так как это было бы слишком долго для комментария - это не специально отвечает на ваш вопрос.

Как упоминалось в комментарии driis, вы должны действительно полагаться на существующие решения, которые считаются безопасными. Тем не менее, ваш протокол имеет проблемы с безопасностью:

  • связь обычно двусторонняя, однако вы, похоже, обращаетесь только к односторонней связи (клиент-сервер). Это не имеет большого смысла, так как вы говорите, что собираетесь использовать TCP, который сам по себе является двусторонним протоколом.

  • шаги 4 и 5 ошибочны: поскольку вы отправляете открытый ключ клиента внутри сообщения, любой может создать пару и зашифровать идентификацию клиента с помощью этой пары. Из вашего описания сервер не имеет прямого знания ключей клиента, что заставляет эту подпись ничего не делать, кроме обеспечения целостности сообщения-в частности, это никоим образом не делает клиента идентификация достоверна.

для правильной идентификации у вас есть дополнительные предпосылки; сервер должен знать открытый ключ клиента заранее или он должен иметь возможность доверять заявлению клиента, чтобы быть самим собой, используя доверенную третью сторону. Вот что такое сертификаты и цепочки доверия сертификатов: если этот клиент представляет сертификат, выданный третьей стороной X, и сервер доверяет X, то он может предположить, что клиент-это тот, на кого он претендует быть.

SSL в основном поддерживает два режима:

  • либо проверяется только идентификатор сервера, и любой клиент может общаться с ним; идентификатор клиента не проверяется, только то, что (после согласования соединения) это всегда один и тот же клиент, который взаимодействует с сервером. Это типичное использование для интернет-магазинов и т. д. - вы (как клиент) доверяете серверу и создаете доверенное соединение, но сервер не знает, кто вы являются.

  • двусторонняя аутентификация также может быть выполнена с помощью клиентских сертификатов. Сервер должен знать и доверять либо сертификату клиента напрямую, либо эмитенту сертификата клиента для успешного согласования соединения. В этом случае сервер действительно знает, кто клиент, но необходимое условие, как указано выше, должно быть выполнено.