编写高质量代码改善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 = ;
        ///缓存块
        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"), );
            //负责侦听的socket
            Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            listenSocket.Bind(iep);
            listenSocket.Listen();
            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, , serverBufferSize, SocketFlags.None, new AsyncCallback(this.ReceivedFromClient), null);
        }
        ///负责处理接受自客户端的数据
        void ReceivedFromClient(IAsyncResult result)
        {
            int read = serverCommunicateSocket.EndReceive(result);
            if (read > )
            {
                messageFromClient.Append(UTF32Encoding.Default.GetString(bytesReceivedFromClient, , read));
                //处理并显示数据
                ProcessAndShowInServer();
                serverCommunicateSocket.BeginReceive(bytesReceivedFromClient, , serverBufferSize, , new AsyncCallback(ReceivedFromClient), null);
            }
        }
        private void ProcessAndShowInServer()
        {
            string msg = messageFromClient.ToString();
            //如果接收到<EOF>则表示完成完成一次,否则继续将自己置于接收状态
            if (msg.IndexOf("<EOF>") > -)
            {
                //如果客户端发送key,则负责初始化key
                if (msg.IndexOf("<KEY>") > -)
                {
                    //用私钥解密发送过来的Key信息
                    key = RSAProcessor.RSADecrypt(pfxKey, msg.Substring(, msg.Length - ));
                    ListBoxServerShow(string.Format("接收到客户端密钥:{0}", key));
                }
                else
                {
                    //解密SSL通道中发送过来的密文并显式
                    ListBoxServerShow(string.Format("接收到客户端消息:{0}", RijndaelProcessor.DencryptString(msg.Substring(, msg.Length - ), 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(, msg.Length - ), key);
            byte[] msgBytes = UTF32Encoding.Default.GetBytes(msg);
            serverCommunicateSocket.BeginSend(msgBytes, , 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 = ;
int blockSize = ;
int lastblockSize = ;
int counter = ;
int iterations = ;
int index = ;
byte[] btPlaintextToken;
byte[] btEncryptedToken;
byte[] btEncryptedSecret;
RSACryptoServiceProvider rsaSender = new RSACryptoServiceProvider();
rsaSender.FromXmlString(rsaKeyString);
keySize = rsaSender.KeySize / ;
blockSize = keySize - ; if ((btSecret.Length % blockSize) != )
{
iterations = btSecret.Length / blockSize + ;
}
else
{
iterations = btSecret.Length / blockSize;
}
btPlaintextToken = new byte[blockSize];
btEncryptedSecret = new byte[iterations * keySize];
for (index = , counter = ; counter < iterations; counter++, index += blockSize)
{
if (counter == (iterations - ))
{
lastblockSize = btSecret.Length % blockSize;
btPlaintextToken = new byte[lastblockSize];
Array.Copy(btSecret, index, btPlaintextToken, , lastblockSize);
}
else
{
Array.Copy(btSecret, index, btPlaintextToken, , blockSize);
}
btEncryptedToken = rsaSender.Encrypt(btPlaintextToken, false);
Array.Copy(btEncryptedToken, , btEncryptedSecret, counter * keySize, keySize);
}
return btEncryptedSecret;
} public static byte[] DecryptBuffer(string rsaKeyString, byte[] btEncryptedSecret)
{
int keySize = ;
int blockSize = ;
int counter = ;
int iterations = ;
int index = ;
int byteCount = ;
byte[] btPlaintextToken;
byte[] btEncryptedToken;
byte[] btDecryptedSecret;
RSACryptoServiceProvider rsaReceiver = new RSACryptoServiceProvider();
rsaReceiver.FromXmlString(rsaKeyString);
keySize = rsaReceiver.KeySize / ;
blockSize = keySize - ;
if ((btEncryptedSecret.Length % keySize) != )
{
return null;
}
iterations = btEncryptedSecret.Length / keySize;
btEncryptedToken = new byte[keySize];
Queue<byte[]> tokenQueue = new Queue<byte[]>();
for (index = , counter = ; counter < iterations; index += blockSize, counter++)
{
Array.Copy(btEncryptedSecret, counter * keySize, btEncryptedToken, , keySize);
btPlaintextToken = rsaReceiver.Decrypt(btEncryptedToken, false);
tokenQueue.Enqueue(btPlaintextToken);
}
byteCount = ;
foreach (var PlaintextToken in tokenQueue)
{
byteCount += PlaintextToken.Length;
}
counter = ;
btDecryptedSecret = new byte[byteCount];
foreach (var PlaintextToken in tokenQueue)
{
if (counter == (iterations - ))
{
Array.Copy(PlaintextToken, , btDecryptedSecret, btDecryptedSecret.Length - PlaintextToken.Length, PlaintextToken.Length);
}
else
{
Array.Copy(PlaintextToken, , btDecryptedSecret, counter * blockSize, blockSize);
}
counter++;
}
return btDecryptedSecret;
} }
}
RijndaelProcessor工具类,用于封装对称加密算法:
public class RijndaelProcessor
{
static int bufferSize = * ;
static byte[] salt = { , , , , , , , , , , , , , , , };
static byte[] iv = { , , , , , , , , , , , , , , , }; static SymmetricAlgorithm CreateRijndael(string password, byte[] salt)
{
PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, salt, "SHA256", );
SymmetricAlgorithm sma = Rijndael.Create();
sma.KeySize = ;
sma.Key = pdb.GetBytes();
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, , 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();
}
}
}
}
这是一WinForm窗体程序,模拟的是服务器端部分,其中有两个按钮。按钮事件方法buttonStartServe_Click负责让服务器处理侦听状态。当然,为了模拟需要,在方法中还初始化了非对称加密密钥对。记住,公钥要公开给客户端。注意,也可以使用数字证书,但是为了演示方便,本例仅使用公钥-私钥对。
通信部分直接使用了FCL中的Socket类型,并采用异步的方式处理发送和接收任务。关于通信部分,本建议不再赘述。唯一要注意的是,在发送和接收过程中,要调用RijndaelProcessor.EncryptString方法加密,然后用RijndaelProcessor.DencryptString方法解密。RijndaelProcessor类型是用来封装对称加密、解密算法的工具类。
客户端部分代码:
#region client
///======================
///客户端代码
///====================== ///用于跟服务器通信的socket
Socket clientCommunicateSocket;
///用于暂存接收到的字符串
StringBuilder messageFromServer = new StringBuilder();
///定义接受缓存块的大小
static int clientBufferSize = ;
///缓存块
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"), );
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, , clientBufferSize, SocketFlags.None, new AsyncCallback(this.ReceivedFromServer), null);
ListBoxClientShow("客户端连接上服务器。。。");
//连接成功便发送密钥K给服务器
SendKey();
} void ReceivedFromServer(IAsyncResult result)
{
int read = clientCommunicateSocket.EndReceive(result);
if (read > )
{
messageFromServer.Append(UTF32Encoding.Default.GetString(bytesReceivedFromServer, , read));
//处理并显示客户端数据
ProcessAndShowInClient();
clientCommunicateSocket.BeginReceive(bytesReceivedFromServer, , clientBufferSize, , new AsyncCallback(ReceivedFromServer), null);
}
} private void ProcessAndShowInClient()
{
//如果接收到<EOF>则表示完成一次接收,否则继续将自己置于接收状态
if (messageFromServer.ToString().IndexOf("<EOF>") > -)
{
//解密消息体并呈现出来
ListBoxClientShow(string.Format("接收到服务器消息:{0}", RijndaelProcessor.DencryptString(messageFromServer.ToString().Substring(, messageFromServer.ToString().Length - ), 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, , 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, , 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个建议》陆敏技
编写高质量代码改善C#程序的157个建议——建议117:使用SSL确保通信中的数据安全的更多相关文章
- 编写高质量代码改善C#程序的157个建议[1-3]
		
原文:编写高质量代码改善C#程序的157个建议[1-3] 前言 本文主要来学习记录前三个建议. 建议1.正确操作字符串 建议2.使用默认转型方法 建议3.区别对待强制转换与as和is 其中有很多需要理 ...
 - 读书--编写高质量代码  改善C#程序的157个建议
		
最近读了陆敏技写的一本书<<编写高质量代码 改善C#程序的157个建议>>书写的很好.我还看了他的博客http://www.cnblogs.com/luminji . 前面部 ...
 - 编写高质量代码改善C#程序的157个建议——建议157:从写第一个界面开始,就进行自动化测试
		
建议157:从写第一个界面开始,就进行自动化测试 如果说单元测试是白盒测试,那么自动化测试就是黑盒测试.黑盒测试要求捕捉界面上的控件句柄,并对其进行编码,以达到模拟人工操作的目的.具体的自动化测试请学 ...
 - 编写高质量代码改善C#程序的157个建议——建议156:利用特性为应用程序提供多个版本
		
建议156:利用特性为应用程序提供多个版本 基于如下理由,需要为应用程序提供多个版本: 应用程序有体验版和完整功能版. 应用程序在迭代过程中需要屏蔽一些不成熟的功能. 假设我们的应用程序共有两类功能: ...
 - 编写高质量代码改善C#程序的157个建议——建议155:随生产代码一起提交单元测试代码
		
建议155:随生产代码一起提交单元测试代码 首先提出一个问题:我们害怕修改代码吗?是否曾经无数次面对乱糟糟的代码,下决心进行重构,然后在一个月后的某个周一,却收到来自测试版的报告:新的版本,没有之前的 ...
 - 编写高质量代码改善C#程序的157个建议——建议154:不要过度设计,在敏捷中体会重构的乐趣
		
建议154:不要过度设计,在敏捷中体会重构的乐趣 有时候,我们不得不随时更改软件的设计: 如果项目是针对某个大型机构的,不同级别的软件使用者,会提出不同的需求,或者随着关键岗位人员的更替,需求也会随个 ...
 - 编写高质量代码改善C#程序的157个建议——建议153:若抛出异常,则必须要注释
		
建议153:若抛出异常,则必须要注释 有一种必须加注释的场景,即使异常.如果API抛出异常,则必须给出注释.调用者必须通过注释才能知道如何处理那些专有的异常.通常,即便良好的命名也不可能告诉我们方法会 ...
 - 编写高质量代码改善C#程序的157个建议——建议152:最少,甚至是不要注释
		
建议152:最少,甚至是不要注释 以往,我们在代码中不写上几行注释,就会被认为是钟不负责任的态度.现在,这种观点正在改变.试想,如果我们所有的命名全部采用有意义的单词或词组,注释还有多少存在的价值. ...
 - 编写高质量代码改善C#程序的157个建议——建议151:使用事件访问器替换公开的事件成员变量
		
建议151:使用事件访问器替换公开的事件成员变量 事件访问器包含两部分内容:添加访问器和删除访问器.如果涉及公开的事件字段,应该始终使用事件访问器.代码如下所示: class SampleClass ...
 - 编写高质量代码改善C#程序的157个建议——建议150:使用匿名方法、Lambda表达式代替方法
		
建议150:使用匿名方法.Lambda表达式代替方法 方法体如果过小(如小于3行),专门为此定义一个方法就会显得过于繁琐.比如: static void SampeMethod() { List< ...
 
随机推荐
- Install MongoDB Community Edition on Ubuntu
			
Install MongoDB > Install MongoDB Community Edition > Install MongoDB Community Edition on Lin ...
 - Fastq 常用软件
			
文章转载于 Original 2017-06-08 Jolvii 生信百科 由于生物信息的大部分工作都是在没有 root 权限的集群上进行的,本期我主要介绍一下非 root 用户怎么安装常用的软件.工 ...
 - cocos2dx中快速完成一段可播放动画
			
版本:cocos2dx 2.2.6 IDE: VS2012 语言:C++98 CCSpriteFrameCache* cache = CCSpriteFrameCache::sharedSpriteF ...
 - C# 判断字体是否存在以及安装
			
1. 字体安装 在实际开发项目中,需要在客户端安装字体,一种是通过代码将字体文件复制到系统FONT目录即可,另一种通过安装文件实现,至于其他方式还未知晓. 1.1 软安装 public cla ...
 - Logging模块总结 2018/5/30
			
日志的级别 Level 用处 数字级别 DEBUG 详细的信息,在调试过程中用于诊断错误 10 INFO 用于确认事件正在运行 20 WARNING 意外发生时予以提醒,或者预测一些未来可能发生的一些 ...
 - html学习代码
			
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...
 - 【LA4043 训练指南】蚂蚁 【二分图最佳完美匹配,费用流】
			
题意 给出n个白点和n个黑点的坐标,要求用n条不相交的线段把他们连接起来,其中每条线段恰好连接一个白点和一个黑点,每个点恰好连接一条线段. 分析 结点分黑白,很容易想到二分图.其中每个白点对应一个X结 ...
 - Avro总结(RPC/序列化)
			
Avro(读音类似于[ævrə])是Hadoop的一个子项目,由Hadoop的创始人Doug Cutting(也是Lucene,Nutch等项目的创始人,膜拜)牵头开发,当前最新版本1.3.3.Avr ...
 - Mysql设置auto_increment_increment和auto_increment_offset
			
查看与设置: show variables like '%auto_inc%'; show session variables like '%auto_inc%'; -- //session会话变量 ...
 - SpringBoot 集成Mybatis时  使用通用插件Mapper 注意事项
			
1.如果在SpringBoot的启动入口类上面加入注解 @MapperScan(basePackages = "com.leecx.mapper") 使用的是 org ...