zoukankan      html  css  js  c++  java
  • 【转】编写高质量代码改善C#程序的157个建议——建议117:使用SSL确保通信中的数据安全

    建议117:使用SSL确保通信中的数据安全

    SSL(Secure Socket Layer)最初是由NetScape公司设计的,用于Web安全的网络协议。目前它已经广泛应用到各类网络传输通信中了。SSL利用数字证书技术(非对称加密),保证了通信过程中的唯一性、不可篡改性、不可抵赖性。SSL通道原理图:

    非对称加密中:

    • 秘钥分为两部分:公钥PK和私钥SK。
    • 公钥用于加密数据用,私钥用于解密。
    • 公钥可公开而且应该公开,私钥只属于创建者。

    经过公钥加密的数据只有证书创建者才能解密。这是构成SSL通道所有理论的依据。

    在传统的网络传输过程中,我们将通信双方定义为:服务器端和客户端。假定服务器端是数字证书的创建者,它保存好自己的私钥,同时公布了自己的公钥给所有的客户端。满足了这个条件,我们来构建SSL通道。

    首先,客户端随机生成一个字符串作为密钥K,然后用公钥PK对这个密钥加密,并将加密后密钥发送给服务器端。如果客户端曾经在服务器端注册过自己的信息,则还可以在这个密钥上加上自己的身份信息,从而向服务器端汇报自己的唯一性,但在本例中略去这一步。

    服务器端用私钥解密消息,获取了客户端的K,并确认了客户端的身份(不可抵赖性),SSL通道建立。

    服务器端和客户端现在可以进行安全通信。过程是:发送方使用密钥K对要传输的消息进行对称加密,接受方则使用K进行解密。这就是传输过程中的不可篡改性。

    我们来模拟SSL的通信,服务器部分的代码:

            #region server
    
            //用于保存非对称加密(数字证书)的公钥
            string publicKey = string.Empty;
            //用于保存非对称加密(数字证书)的私钥
            string pfxKey = string.Empty;
    
            ///======================
            ///服务器端代码
            ///======================
    
            ///用于跟客户端通信的socket
            Socket serverCommunicateSocket;
            ///定义接受缓存块的大小
            static int serverBufferSize = 1024;
            ///缓存块
            byte[] bytesReceivedFromClient = new byte[serverBufferSize];
            ///密钥K
            string key = string.Empty;
            StringBuilder messageFromClient = new StringBuilder();
    
            ///开启服务器
            private void buttonStartServer_Click(object sender, EventArgs e)
            {
                //先生成数字证书(模拟,即非对称密钥对)
                RSAKeyInit();
                //负责侦听
                StartListen();
            }
    
            private void RSAKeyInit()
            {
                RSAProcessor.CreateRSAKey(ref publicKey, ref pfxKey);
            }
    
            private void StartListen()
            {
                IPEndPoint iep = new IPEndPoint(IPAddress.Parse("192.168.1.100"), 8009);
                //负责侦听的socket
                Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                listenSocket.Bind(iep);
                listenSocket.Listen(50);
                listenSocket.BeginAccept(new AsyncCallback(this.Accepted), listenSocket);
                ListBoxServerShow("开始侦听。。。");
                buttonStartServer.Enabled = false;
            }
    
            ///负责客户端的连接,并开始将自己置于接收状态
            void Accepted(IAsyncResult result)
            {
                Socket listenSocket = result.AsyncState as Socket;
                //初始化和客户端进行通信的socket
                serverCommunicateSocket = listenSocket.EndAccept(result);
                ListBoxServerShow("有客户端连接到。。。");
                serverCommunicateSocket.BeginReceive(bytesReceivedFromClient, 0, serverBufferSize, SocketFlags.None, new AsyncCallback(this.ReceivedFromClient), null);
            }
    
            ///负责处理接受自客户端的数据
            void ReceivedFromClient(IAsyncResult result)
            {
                int read = serverCommunicateSocket.EndReceive(result);
                if (read > 0)
                {
                    messageFromClient.Append(UTF32Encoding.Default.GetString(bytesReceivedFromClient, 0, read));
                    //处理并显示数据
                    ProcessAndShowInServer();
                    serverCommunicateSocket.BeginReceive(bytesReceivedFromClient, 0, serverBufferSize, 0, new AsyncCallback(ReceivedFromClient), null);
                }
            }
    
            private void ProcessAndShowInServer()
            {
                string msg = messageFromClient.ToString();
                //如果接收到<EOF>则表示完成完成一次,否则继续将自己置于接收状态
                if (msg.IndexOf("<EOF>") > -1)
                {
                    //如果客户端发送key,则负责初始化key
                    if (msg.IndexOf("<KEY>") > -1)
                    {
                        //用私钥解密发送过来的Key信息
                        key = RSAProcessor.RSADecrypt(pfxKey, msg.Substring(0, msg.Length - 10));
                        ListBoxServerShow(string.Format("接收到客户端密钥:{0}", key));
                    }
                    else
                    {
                        //解密SSL通道中发送过来的密文并显式
                        ListBoxServerShow(string.Format("接收到客户端消息:{0}", RijndaelProcessor.DencryptString(msg.Substring(0, msg.Length - 5), key)));
                    }
                    messageFromClient.Clear();
                }
            }
    
            ///负责向客户端发送数据
            private void buttonStartSendToClient_Click(object sender, EventArgs e)
            {
                //加密消息体
                string msg = string.Format("{0}{1}", RijndaelProcessor.EncryptString(DateTime.Now.ToString(), key), "<EOF>");
                RijndaelProcessor.DencryptString(msg.Substring(0, msg.Length - 5), key);
                byte[] msgBytes = UTF32Encoding.Default.GetBytes(msg);
                serverCommunicateSocket.BeginSend(msgBytes, 0, msgBytes.Length, SocketFlags.None, null, null);
                ListBoxServerShow(string.Format("发送:{0}", msg));
            }
    
            private void ListBoxServerShow(string msg)
            {
                listBoxServer.BeginInvoke(new Action(() =>
                {
                    listBoxServer.Items.Add(msg);
                }));
            }
            #endregion server

    RSAProcessor工具类,用于封装非对称加密算法:

        public class RSAProcessor
        {
            public static void CreateRSAKey(ref string publicKey, ref string pfxKey)
            {
                RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
                pfxKey = provider.ToXmlString(true);
                publicKey = provider.ToXmlString(false);
            }
    
            public static string RSAEncrypt(string xmlPublicKey, string m_strEncryptString)
            {
                byte[] btEncryptedSecret = Encoding.UTF8.GetBytes(m_strEncryptString);
                btEncryptedSecret = CRSAWrap.EncryptBuffer(xmlPublicKey, btEncryptedSecret);
                return Convert.ToBase64String(btEncryptedSecret);
            }
    
            public static string RSADecrypt(string xmlPrivateKey, string m_strDecryptString)
            {
                byte[] btDecryptedSecred = Convert.FromBase64String(m_strDecryptString);
                btDecryptedSecred = CRSAWrap.DecryptBuffer(xmlPrivateKey, btDecryptedSecred);
                return Encoding.UTF8.GetString(btDecryptedSecred);
            }
    
            class CRSAWrap
            {
                public static byte[] EncryptBuffer(string rsaKeyString, byte[] btSecret)
                {
                    int keySize = 0;
                    int blockSize = 0;
                    int lastblockSize = 0;
                    int counter = 0;
                    int iterations = 0;
                    int index = 0;
                    byte[] btPlaintextToken;
                    byte[] btEncryptedToken;
                    byte[] btEncryptedSecret;
                    RSACryptoServiceProvider rsaSender = new RSACryptoServiceProvider();
                    rsaSender.FromXmlString(rsaKeyString);
                    keySize = rsaSender.KeySize / 8;
                    blockSize = keySize - 11;
    
                    if ((btSecret.Length % blockSize) != 0)
                    {
                        iterations = btSecret.Length / blockSize + 1;
                    }
                    else
                    {
                        iterations = btSecret.Length / blockSize;
                    }
                    btPlaintextToken = new byte[blockSize];
                    btEncryptedSecret = new byte[iterations * keySize];
                    for (index = 0, counter = 0; counter < iterations; counter++, index += blockSize)
                    {
                        if (counter == (iterations - 1))
                        {
                            lastblockSize = btSecret.Length % blockSize;
                            btPlaintextToken = new byte[lastblockSize];
                            Array.Copy(btSecret, index, btPlaintextToken, 0, lastblockSize);
                        }
                        else
                        {
                            Array.Copy(btSecret, index, btPlaintextToken, 0, blockSize);
                        }
                        btEncryptedToken = rsaSender.Encrypt(btPlaintextToken, false);
                        Array.Copy(btEncryptedToken, 0, btEncryptedSecret, counter * keySize, keySize);
                    }
                    return btEncryptedSecret;
                }
    
                public static byte[] DecryptBuffer(string rsaKeyString, byte[] btEncryptedSecret)
                {
                    int keySize = 0;
                    int blockSize = 0;
                    int counter = 0;
                    int iterations = 0;
                    int index = 0;
                    int byteCount = 0;
                    byte[] btPlaintextToken;
                    byte[] btEncryptedToken;
                    byte[] btDecryptedSecret;
                    RSACryptoServiceProvider rsaReceiver = new RSACryptoServiceProvider();
                    rsaReceiver.FromXmlString(rsaKeyString);
                    keySize = rsaReceiver.KeySize / 8;
                    blockSize = keySize - 11;
                    if ((btEncryptedSecret.Length % keySize) != 0)
                    {
                        return null;
                    }
                    iterations = btEncryptedSecret.Length / keySize;
                    btEncryptedToken = new byte[keySize];
                    Queue<byte[]> tokenQueue = new Queue<byte[]>();
                    for (index = 0, counter = 0; counter < iterations; index += blockSize, counter++)
                    {
                        Array.Copy(btEncryptedSecret, counter * keySize, btEncryptedToken, 0, keySize);
                        btPlaintextToken = rsaReceiver.Decrypt(btEncryptedToken, false);
                        tokenQueue.Enqueue(btPlaintextToken);
                    }
                    byteCount = 0;
                    foreach (var PlaintextToken in tokenQueue)
                    {
                        byteCount += PlaintextToken.Length;
                    }
                    counter = 0;
                    btDecryptedSecret = new byte[byteCount];
                    foreach (var PlaintextToken in tokenQueue)
                    {
                        if (counter == (iterations - 1))
                        {
                            Array.Copy(PlaintextToken, 0, btDecryptedSecret, btDecryptedSecret.Length - PlaintextToken.Length, PlaintextToken.Length);
                        }
                        else
                        {
                            Array.Copy(PlaintextToken, 0, btDecryptedSecret, counter * blockSize, blockSize);
                        }
                        counter++;
                    }
                    return btDecryptedSecret;
                }
    
            }
        }
    View Code

    RijndaelProcessor工具类,用于封装对称加密算法:

        public class RijndaelProcessor
        {
            static int bufferSize = 128 * 1024;
            static byte[] salt = { 134, 216, 7, 36, 88, 164, 91, 227, 174, 76, 191, 197, 192, 154, 200, 248 };
            static byte[] iv = { 134, 216, 7, 36, 88, 164, 91, 227, 174, 76, 191, 197, 192, 154, 200, 248 };
    
            static SymmetricAlgorithm CreateRijndael(string password, byte[] salt)
            {
                PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, salt, "SHA256", 1000);
                SymmetricAlgorithm sma = Rijndael.Create();
                sma.KeySize = 256;
                sma.Key = pdb.GetBytes(32);
                sma.Padding = PaddingMode.PKCS7;
                return sma;
            }
    
            public static string EncryptString(string input, string password)
            {
                using (MemoryStream memoryStream = new MemoryStream())
                using (SymmetricAlgorithm algorithm = CreateRijndael(password, salt))
                {
                    algorithm.IV = iv;
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, algorithm.CreateEncryptor(), CryptoStreamMode.Write))
                    {
                        byte[] bytes = UTF32Encoding.Default.GetBytes(input);
                        cryptoStream.Write(bytes, 0, bytes.Length);
                        cryptoStream.Flush();
                    }
                    return Convert.ToBase64String(memoryStream.ToArray());
                }
            }
    
            public static string DencryptString(string input, string password)
            {
                using (MemoryStream inputMemoryStream = new MemoryStream(Convert.FromBase64String(input)))
                using (SymmetricAlgorithm algorithm = CreateRijndael(password, salt))
                {
                    algorithm.IV = iv;
                    using (CryptoStream cryptoStream = new CryptoStream(inputMemoryStream, algorithm.CreateDecryptor(), CryptoStreamMode.Read))
                    {
                        StreamReader sr = new StreamReader(cryptoStream);
                        return sr.ReadToEnd();
                    }
                }
            }
        }
    View Code

    这是一WinForm窗体程序,模拟的是服务器端部分,其中有两个按钮。按钮事件方法buttonStartServe_Click负责让服务器处理侦听状态。当然,为了模拟需要,在方法中还初始化了非对称加密密钥对。记住,公钥要公开给客户端。注意,也可以使用数字证书,但是为了演示方便,本例仅使用公钥-私钥对。

    通信部分直接使用了FCL中的Socket类型,并采用异步的方式处理发送和接收任务。关于通信部分,本建议不再赘述。唯一要注意的是,在发送和接收过程中,要调用RijndaelProcessor.EncryptString方法加密,然后用RijndaelProcessor.DencryptString方法解密。RijndaelProcessor类型是用来封装对称加密、解密算法的工具类。

     客户端部分代码:

            #region client
            ///======================
            ///客户端代码
            ///======================
    
            ///用于跟服务器通信的socket
            Socket clientCommunicateSocket;
            ///用于暂存接收到的字符串
            StringBuilder messageFromServer = new StringBuilder();
            ///定义接受缓存块的大小
            static int clientBufferSize = 1024;
            ///缓存块
            byte[] bytesReceivedFromServer = new byte[clientBufferSize];
            //随机生成的key,在这里硬编码为key123
            string keyCreateRandom = "key123";
    
            private void buttonConnectAndReceiveMsg_Click(object sender, EventArgs e)
            {
                IPEndPoint iep = new IPEndPoint(IPAddress.Parse("192.168.1.100"), 8009);
                Socket connectSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                connectSocket.BeginConnect(iep, new AsyncCallback(this.Connected), connectSocket);
                buttonConnectAndReceiveMsg.Enabled = false;
            }
    
            void Connected(IAsyncResult result)
            {
                clientCommunicateSocket = result.AsyncState as Socket;
                clientCommunicateSocket.EndConnect(result);
                clientCommunicateSocket.BeginReceive(bytesReceivedFromServer, 0, clientBufferSize, SocketFlags.None, new AsyncCallback(this.ReceivedFromServer), null);
                ListBoxClientShow("客户端连接上服务器。。。");
                //连接成功便发送密钥K给服务器
                SendKey();
            }
    
            void ReceivedFromServer(IAsyncResult result)
            {
                int read = clientCommunicateSocket.EndReceive(result);
                if (read > 0)
                {
                    messageFromServer.Append(UTF32Encoding.Default.GetString(bytesReceivedFromServer, 0, read));
                    //处理并显示客户端数据
                    ProcessAndShowInClient();
                    clientCommunicateSocket.BeginReceive(bytesReceivedFromServer, 0, clientBufferSize, 0, new AsyncCallback(ReceivedFromServer), null);
                }
            }
    
            private void ProcessAndShowInClient()
            {
                //如果接收到<EOF>则表示完成一次接收,否则继续将自己置于接收状态
                if (messageFromServer.ToString().IndexOf("<EOF>") > -1)
                {
                    //解密消息体并呈现出来
                    ListBoxClientShow(string.Format("接收到服务器消息:{0}", RijndaelProcessor.DencryptString(messageFromServer.ToString().Substring(0, messageFromServer.ToString().Length - 5), keyCreateRandom)));
                    messageFromServer.Clear();
                }
            }
    
            private void buttonStartSendToServer_Click(object sender, EventArgs e)
            {
                //加密消息体
                string msg = string.Format("{0}{1}", RijndaelProcessor.EncryptString(DateTime.Now.ToString(), keyCreateRandom), "<EOF>");
                byte[] msgBytes = UTF32Encoding.Default.GetBytes(msg);
                clientCommunicateSocket.BeginSend(msgBytes, 0, msgBytes.Length, SocketFlags.None, null, null);
                ListBoxClientShow(string.Format("发送:{0}", msg));
            }
    
            private void SendKey()
            {
                string msg = RSAProcessor.RSAEncrypt(publicKey, keyCreateRandom) + "<KEY><EOF>";
                byte[] msgBytes = UTF32Encoding.Default.GetBytes(msg);
                clientCommunicateSocket.BeginSend(msgBytes, 0, msgBytes.Length, SocketFlags.None, null, null);
                ListBoxClientShow(string.Format("发送:{0}", keyCreateRandom));
            }
    
            private void ListBoxClientShow(string msg)
            {
                listBoxClient.BeginInvoke(new Action(() =>
                {
                    listBoxClient.Items.Add(msg);
                }));
            }
            #endregion client

     客户端部分也包含两个按钮,在服务器部分按下“侦听”按钮后,客户端可以按下“链接”按钮。这个过程,程序主要完成两件事情。首先,程序会根据服务器IP地址连接上服务器;其次,一旦连接上服务器,客户端会立即将自己用于加密的密钥发送给服务器。

    完成这个步骤后,SSL通道已经建立起来的,这个时候就可以随意发送加密数据而不担心被盗走了。我们可以看到,客户端的代码与服务器端一样,在发送之前,消息要加密,而在接收到消息体之后,首先会解密。

    转自:《编写高质量代码改善C#程序的157个建议》陆敏技

  • 相关阅读:
    学习:Radio Button和Check Box
    学习:访问Edit Control的七种方法
    实现:EDIT控件字符个数与长度的计算
    学习:GDI基础
    学习:MFC的CWinApp和CFrameWnd
    学习:远程代码注入
    实现:获取指定进程PID
    学习:远程线程实现DLL注入和shellcode注入以及OD调试原理
    学习:内存映射文件
    实现 Trie (前缀树)
  • 原文地址:https://www.cnblogs.com/farmer-y/p/8005985.html
Copyright © 2011-2022 走看看