简体   繁体   English

C#中的非对称加密示例

[英]Asymmetric cryptography example in C#

I need to send confidential data to a server over a TCP connection. 我需要通过TCP连接将机密数据发送到服务器。 I have done a lot of researching and I understand the theoretical part. 我做了很多研究,理解了理论部分。 Based on what I have researched I want to do the following: 根据我研究的内容,我想做以下事情:

Note there is a server and a client: (we assume that public keys of either the client or server can be obtain by anyone) 注意有一个服务器和一个客户端:(我们假设任何人都可以获得客户端或服务器的公钥)

  1. client creates his public and private key. 客户端创建他的公钥和私钥。 He is able to encrypt with his private key and decrypt with his public key. 他能够用他的私钥加密并用他的公钥解密。

  2. server creates his public and private keys. 服务器创建他的公钥和私钥。 private key is used to decrypt messages and public key is used to encrypt messages. 私钥用于解密消息,公钥用于加密消息。 (note is the other way around as with the client) (注意与客户端相反)

  3. the client get's the server's public key. 客户端获取服务器的公钥。 client then will be able to encrypt messages with that key and the only one that will be able to decrypt that message would be the server's private key. 然后,客户端将能够使用该密钥加密消息,并且唯一能够解密该消息的消息将是服务器的私钥。

  4. since the server needs to be certain that the message comes from that specific client then the client will encrypt his name (signature) with his private key. 由于服务器需要确定消息来自该特定客户端,因此客户端将使用其私钥加密其名称(签名)。

  5. so the client message will contain: data to be send, client's public key, client name encrypted with the client's private key. 因此客户端消息将包含:要发送的数据,客户端的公钥,使用客户端私钥加密的客户端名称。

  6. the client will encrypt the message with the public key from the server. 客户端将使用服务器中的公钥加密消息。 client will then send that message to the server. 然后,客户端将该消息发送到服务器。

  7. the server will decrypt the message it just received with his private key. 服务器将使用他的私钥解密刚刚收到的消息。

  8. once the message is decrypted it will contain the data (info), encrypted signature, public key from client. 一旦消息被解密,它将包含来自客户端的数据(信息),加密签名,公钥。

  9. finally, the server will decrypt the client signature with the public key that was contained on the message to verify that the message is from that client. 最后,服务器将使用消息中包含的公钥解密客户端签名,以验证消息是否来自该客户端。


OK so this is how asymmetric cryptography works. 好的,这就是非对称加密的工作原理。 I have also researched about the classes that enable you to create this key pairs with the .NET framework. 我还研究过使用.NET框架创建这些密钥对的类。 The classes that I researched that enable you do create this public and private key pairs are: 我研究过的可以创建此公钥和私钥对的类是:

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 

so now my problems comes on how do I use one of this classes to do it with C#? 所以现在我的问题出现在如何使用这个类中的一个用C#来完成它? I understand how the theoretical part works but how do I do what I just described with code. 我理解理论部分是如何工作的,但我怎么做我刚才用代码描述的东西。 I have researched for some examples but I am having a hard time understanding them. 我研究了一些例子,但我很难理解它们。

here is one example that I found that I believe does what I described: 这是我发现我相信我所描述的一个例子:

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

In this program I believe the client is Alice and the server is Bob. 在这个程序中,我相信客户端是Alice,服务器是Bob。 I have to split this program into two parts. 我必须将这个程序分成两部分。 I am having a hard time understanding it and if I give it a try most likely I will make it work. 我很难理解它,如果我试一试,我很可能会让它发挥作用。 Anyways how can I split this program into a server side code and client side code. 无论如何,我如何将此程序拆分为服务器端代码和客户端代码。 I know how to send bytes between server and client. 我知道如何在服务器和客户端之间发送字节。 But I don't want to make it work without understanding what is going on. 但我不想在不了解正在发生的事情的情况下使其发挥作用。 maybe you guys can show me an easier example. 也许你们可以给我一个更简单的例子。


EDIT 编辑

I managed to separate the code: here is the server code (the ip address of my computer happened to be 192.168.0.120) : 我设法分开代码:这是服务器代码(我的电脑的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();
            }
        }
    }
}

and here is the client code: 这是客户端代码:

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

I thinks it is pretty secure. 我认为它非常安全。 Every time it sends a different byte array although sending the same info! 每次发送不同的字节数组,尽管发送相同的信息!

As you note, you are a beginner at crypto. 如你所知,你是加密的初学者。 If this is a fun toy project to learn about crypto, great. 如果这是一个有趣的玩具项目,了解加密,很棒。 If this is real production code you are going to implement it insecurely . 如果这是真正的生产代码, 那么您将不安全地实施它 You should be using off-the-shelf tools like SSL/HTTPS/whatever to solve this problem rather than doing it wrong yourself. 您应该使用现成的工具,如SSL / HTTPS /解决此问题,而不是自己做错。

I'll take this opportunity to point out areas where your sketch is fatally weak. 我将借此机会指出你的草图致命弱的区域。

3) the client get's the server's public key. 3)客户端获取服务器的公钥。

OK. 好。 How? 怎么样? This is the most important step. 这是最重要的一步。 The security of the entire system relies upon this step, and you have completely glossed over how it works. 整个系统的安全性依赖于这一步骤,您已经完全掩盖了它的工作原理。 How does the client obtain the public key of the server? 客户端如何获取服务器的公钥? What stops an evil person from calling up the client and saying "hey client, I'm the server. Here's my public key!" 什么阻止邪恶的人打电话给客户并说“嘿客户,我是服务器。这是我的公钥!” And now the client is encrypting messages that can only be decrypted by the evildoer. 现在,客户端正在加密只能由恶人解密的消息。 The evildoer has the real server's public key, so the evildoer re-encrypts the message with the real public key and sends it on. 恶人拥有真实服务器的公钥,因此恶人用真实公钥重新加密消息并发送。 Your whole system is thereby compromised. 因此,整个系统都受到了损害。 The public key cryptosystem is only secure if there is a secure key exchange mechanism . 只有存在安全密钥交换机制时,公钥密码系统才是安全的。 (And a reasonable question then is: if you have a secure key exchange mechanism, why not simply use it to exchange the message in the first place?) (然后一个合理的问题是:如果你有一个安全的密钥交换机制,为什么不直接用它来交换消息?)

4) since the server needs to be certain that the message comes from that specific client then the client will encrypt his name (signature) with his private key. 4)由于服务器需要确定消息来自该特定客户端,因此客户端将使用其私钥加密其名称(签名)。

The client should encrypt a hash of the entire message as the signature, not just a part of the message. 客户端应该将整个消息的散列加密为签名,而不仅仅是消息的一部分。 That way the server has evidence that the whole message was from the client. 这样服务器就有证据表明整条消息来自客户端。

6) the client will encrypt the message with the public key from the server. 6)客户端将使用来自服务器的公钥加密消息。 client will then send that message to the server. 然后,客户端将该消息发送到服务器。

This is extremely inefficient . 这是非常低效的 Better is for the server and client to agree upon a key to a symmetric cryptosystem. 更好的是服务器和客户端就对称密码系统的密钥达成一致。 The key can be transmitted between the server and the client using the public key cryptosystem. 密钥可以使用公钥密码系统在服务器和客户端之间传输。 The server and client now have a shared secret key that they can use for this communication session. 服务器和客户端现在具有可用于此通信会话的共享密钥。

9) lastly, the server will decrypt the client signature with the public key that was contained on the message to verify that the message is from that client. 9)最后,服务器将使用消息中包含的公钥解密客户端签名,以验证消息是否来自该客户端。

How on earth does that help anything? 这到底有什么用呢? I want to send you a message. 我想给你发一条消息。 You want to know who it comes from. 你想知道它来自谁。 So I send you a photocopy of my drivers license, so you can compare the signature on the license with the signature on the message. 因此,我向您发送了我的驾驶执照复印件,以便您可以将许可证上的签名与邮件上的签名进行比较。 How do you know I sent you my drivers license and not a photocopy of someone else's? 你怎么知道我给你我的驾驶执照,而不是别人的复印件? This doesn't solve the client authentication problem at all. 这根本不能解决客户端身份验证问题。 Again, you need to solve the key distribution problem. 同样, 您需要解决密钥分发问题。 The system depends on there being a secure key distribution infrastructure, which you have not specified. 系统依赖于您未指定的安全密钥分发基础结构。

Posting as an answer since it would be too long for a comment - it isn't specifically answering your question though. 发布作为答案,因为评论太长了 - 但它并没有具体回答你的问题。

As mentionned in the comment by driis, you should really rely on existing solutions which are regarded as being secure. 正如driis的评论中提到的,你应该真正依赖于被认为是安全的现有解决方案。 That said, your protocol does have security issues: 也就是说,您的协议确实存在安全问题:

  • Communication is usually two-way, you however only seem to address one-way communication (client to server). 通信通常是双向的,但您似乎只能解决单向通信(客户端到服务器)。 This doesn't make much sense, since you say that you're going to use TCP, which is a two-way protocol in itself. 这没有多大意义,因为你说你将使用TCP,这本身就是一种双向协议。

  • Steps 4 and 5 are buggy: since you send the public key of the client inside the message, anyone could create a pair and encrypt the client identification using this pair. 第4步和第5步是错误的:因为您在邮件中发送客户端的公钥,所以任何人都可以创建一对并使用该对加密客户端标识。 From your description the server has no forward knowledge of the client's keys, which makes this signature do nothing but ensure the integrity of the message - specifically is does not in any way make the client identification trustworthy. 根据您的描述,服务器没有客户端密钥的前瞻性知识,这使得此签名不会做任何事情,只能确保消息的完整性 - 特别是不会以任何方式使客户端标识值得信任。

For proper identification, you do have additional prerequisites; 为了正确识别,您还有其他先决条件; the server has to know the client's public key in advance or it has to be able to trust the client's claim to be himself by using a trusted 3rd party. 服务器必须提前知道客户的公钥,或者必须能够通过使用受信任的第三方来信任客户的声称自己。 This is what certificates and the certificate trust chains are about: if that client presents a certificate issued by the 3rd party X and the server trusts X, then he can assume that the client is who he pretends to be. 这就是证书和证书信任链的含义:如果该客户端提供由第三方X颁发的证书并且服务器信任X,那么他可以假设客户端是他假装的人。

SSL basically supports two modes: SSL基本上支持两种模式:

  • Either only the server identity is verified and any client can communicate with it; 只验证服务器身份,任何客户端都可以与之通信; the client's identity is not verified, only that (after the connection has been negotiated) it always is the same client which communicates to the server. 客户端的身份未经验证,只有(在协商连接之后)它始终是与服务器通信的同一客户端。 This is the typical usage for online shopping etc. - you (as the client) trust the server and create a trusted connection, but the server does not know who you are. 这是在线购物等的典型用法 - 您(作为客户端)信任服务器并创建可信连接,但服务器不知道您是谁。

  • Two-way authentification can be done as well by using client certificates. 使用客户端证书也可以进行双向身份验证。 The server has to know and trust either the client certificate directly or the issuer of the client certificate in order to negotiate the connection successfully. 服务器必须直接了解或信任客户端证书或客户端证书的颁发者才能成功协商连接。 In this scenario, the server does indeed know who the client is, but the prerequisite as mentioned above has to be met. 在这种情况下,服务器确实知道客户端是谁,但必须满足上面提到的先决条件。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM