Пример асимметричной криптографии в C#
мне нужно отправить конфиденциальные данные на сервер через TCP-соединение. Я провел много исследований, и я понимаю теоретическую часть. Основываясь на том, что я исследовал, я хочу сделать следующее:
обратите внимание, что есть сервер и клиент: (мы предполагаем, что открытые ключи клиента или сервера могут быть получены кем угодно)
клиент создает свой открытый и закрытый ключи. Он способен шифровать с помощью своего закрытого ключа и расшифровывать с помощью своего публичного ключ.
сервер создает свой открытый и закрытый ключи. закрытый ключ используется для расшифровки сообщений, а открытый ключ-для шифрования сообщений. (Примечание-это наоборот, как с клиентом)
клиент get-это открытый ключ сервера. затем клиент сможет шифровать сообщения с помощью этого ключа, и единственным, который сможет расшифровать это сообщение, будет закрытый ключ сервера.
С сервера необходимо быть уверенным, что сообщение исходит от этого конкретного клиента, тогда клиент зашифрует свое имя (подпись) своим закрытым ключом.
таким образом, сообщение клиента будет содержать: данные для отправки, открытый ключ клиента, имя клиента, зашифрованное закрытым ключом клиента.
клиент зашифрует сообщение открытым ключом с сервера. затем клиент отправит сообщение на сервер.
в сервер расшифрует только что полученное сообщение с помощью своего закрытого ключа.
как только сообщение будет расшифровано, оно будет содержать данные (информацию), зашифрованную подпись, открытый ключ от клиента.
наконец, сервер расшифрует подпись клиента с открытым ключом, который содержался в сообщении, чтобы убедиться, что сообщение от этого клиента.
хорошо, вот как работает асимметричная криптография. Я также изучили классы, которые позволяют создавать эти пары ключей с .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 в основном поддерживает два режима:
либо проверяется только идентификатор сервера, и любой клиент может общаться с ним; идентификатор клиента не проверяется, только то, что (после согласования соединения) это всегда один и тот же клиент, который взаимодействует с сервером. Это типичное использование для интернет-магазинов и т. д. - вы (как клиент) доверяете серверу и создаете доверенное соединение, но сервер не знает, кто вы являются.
двусторонняя аутентификация также может быть выполнена с помощью клиентских сертификатов. Сервер должен знать и доверять либо сертификату клиента напрямую, либо эмитенту сертификата клиента для успешного согласования соединения. В этом случае сервер действительно знает, кто клиент, но необходимое условие, как указано выше, должно быть выполнено.