CCryptoTokenizer






4.50/5 (6投票s)
2003年11月14日
12分钟阅读

65362

2406
CCryptoTokenizer
摘要
cCryptoTcpTokenizer
建立在 .NET 框架之上,扩展了 System.Security.Cryptography
提供的功能,并提供了一个机制,便于两个 IP 端点之间的安全、异步通信。cCryptoTcpTokenizer
支持
- 消息加密/解密:消息使用 Rijndael 算法进行加密。载荷加密中使用的密钥和初始化向量反过来使用接收者的公钥进行加密。加密的密钥和初始化向量与加密的消息一起发送给接收者。
cCryptoTcpTokenizer
在内部管理密钥交换,整个过程对客户端是透明的。 - 消息完整性与签名。
- 传入网络流的解析/分词。
- 支持异步消息的事件
- EvIncomingMessage 检测到传入消息。
- EvTargetPublicKeyRetrieved 检索到接收者的公钥。
- EvConnectBack 已建立与目标的连接。
目录
- 应用笔记
- 架构
- 可扩展性和性能
- 后续版本中的进一步扩展
- 参考文献
- 附录
应用笔记
cCryptoTcpTokenizer 包信息
- 可执行文件:clCryptoTcpTokenizer.dll
- 解决方案:clCryptoTcpTokenizer (clCryptoTcpTokenizer.sln)
- 解决方案目录:cCryptoTcpTokenizer_package\clCryptoTcpTokenizer\
- 源代码:CryptoTcpTokenizer.cs
- 演示源代码:CryptoTcpTokenizerDemo.cs
- 演示可执行文件:CryptoTcpTokenizerDemo.exe (C# 控制台应用程序)
- 版本:1.0.1355.23652 (BETA)
引用的程序集
类 |
DLL |
支持 |
源代码:TcpTokenizer.cs 命名空间: |
TcpTokenizer.dll |
支持发送/接收/分词到/从网络流的消息。 |
源代码:RSAWrap.cs 命名空间: |
clRSAWrap.dll |
多块 RSA 加密/解密子程序。 |
表 1 引用的程序集
其他包含的包
a. cTcpTokenizer
- 可执行文件:TcpTokenizer.dll
- 解决方案:TcpTokenizer (TcpTokenizer.sln)
- 解决方案目录:cCryptoTcpTokenizer_package\ TcpTokenizer\
- 源代码:TcpTokenizer.cs
- 演示源代码:TcpTokenizerDemo.cs
- 演示可执行文件:TcpTokenizerDemo.exe
- 性能测试:cCryptoTcpTokenizerPerformanceTest.cs (项目:cCryptoTcpTokenizerPerformanceTest)
b. clRSAWrap
- 解决方案:clRSAWrap (RSACryptoServiceProvider Demo.sln)
- 解决方案目录:cCryptoTcpTokenizer_package\ RSACryptoServiceProvider Demo\
- 信息:此解决方案是一系列项目,演示了 .NET System.Cryptography 命名空间的基础知识:哈希、数字签名、RSA/非对称加密。其中包括一个围绕 RSACrptoServiceProvider 的简单包装器:类 cRSAWrap。该类提供两个静态方法来简化多块 RSA 加密/解密。
演示 (1.2) (源代码: CryptoTcpTokenizerDemo.cs)
下面的代码片段演示了如何使用类 cCryptoTcpTokenizer
连接两个 IP 端点。如果您更喜欢 UML 顺序图而非代码,请跳至**演练**部分并先阅读该部分。
using System;
using System.Text;
using System.Threading;
using System.Collections;
using nsCryptoTcpTokenizer;
namespace CryptoTcpTokenizerDemo
{
public class cCryptoTcpTokenizerDemo
{ //cCryptoTcpTokenizerDemo
public bool bContinue;
public cCryptoTcpTokenizer tkMessenger1;
public cCryptoTcpTokenizer tkMessenger2;
public ManualResetEvent evMsg1SendComplete;
//tkMessenger1 is done sending message to tkMessenger2.
public ManualResetEvent evMsg2SendComplete;
//tkMessenger2 is done sending message to tkMessenger1.
public ManualResetEvent evMsg1PKRetrieved;
//tkMessenger1 has retrieved public key (RSA) from tkMessenger2.
public ManualResetEvent evMsg2PKRetrieved;
//tkMessenger2 has retrieved public key (RSA) from tkMessenger1.
public ManualResetEvent evConnectBackTo2;
//tkMessenger1 has connected back to tkMessenger2,
//in response to
//tkMessenger2's connection request.
public cCryptoTcpTokenizerDemo()
{
bContinue=true;
evMsg2SendComplete = new ManualResetEvent(false);
evMsg1PKRetrieved = new ManualResetEvent(false);
evMsg2PKRetrieved = new ManualResetEvent(false);
evConnectBackTo2 = new ManualResetEvent(false);
evMsg1SendComplete = new ManualResetEvent(false);
}
//****************** Event Handlers ******************
void OnConnectBackTo2()
{
evConnectBackTo2.Set();
}
void OnIncomingMsg1()
{
object oMsg = tkMessenger1.qIncomingMessages.Dequeue();
string sMsg = oMsg.ToString();
Console.WriteLine("tkMessenger1: {0}", sMsg);
}
void OnIncomingMsg2()
{
object oMsg = tkMessenger2.qIncomingMessages.Dequeue();
string sMsg = oMsg.ToString();
Console.WriteLine("tkMessenger2: {0}", sMsg);
}
void OnMsg1PKRetrieved()
{
Console.WriteLine("tkMessenger1 just retrieved public" +
" key from target (tkMessenger2).");
evMsg1PKRetrieved.Set();
}
void OnMsg2PKRetrieved()
{
Console.WriteLine("tkMessenger2 just retrieved " +
"public key from target (tkMessenger1).");
evMsg2PKRetrieved.Set();
}
public void Messenger1()
{
int index=0;
try
{
//STEP M1.1: Configure cCryptoTcpTokenizer:
tkMessenger1 = new cCryptoTcpTokenizer();
tkMessenger1.listenPort = 11000;
tkMessenger1.targetIP="localhost";
tkMessenger1.targetPort=12000;
//STEP M1.2: Subscribe to cCryptoTcpTokenizer's events:
//(a) Fired everytime tkMessenger1.qIncomingMessages
//(or protected message queue:
// "m_qPlaintextMsgCollection") is loaded after
//tkMessenger1 parses input
// NetworkStream internally.
cCryptoTcpTokenizer.dgIncomingMessage dgIncoming1 =
new cCryptoTcpTokenizer.dgIncomingMessage(OnIncomingMsg1);
tkMessenger1.evIncomingMessage += dgIncoming1;
//(b) Fired when target's public key is retrieved.
cCryptoTcpTokenizer.dgTargetPublicKeyRetrieved dgMsg1PKRetrieved =
new cCryptoTcpTokenizer.dgTargetPublicKeyRetrieved(
OnMsg1PKRetrieved);
tkMessenger1.evTargetPublicKeyRetrieved += dgMsg1PKRetrieved;
//(c) tkMessenger1 connects back to tkMessenger2 internally.
// Here's the sequence of events that takes place when
// tkMessenger2 initiates connection with tkMessenger1.
// (1) tkMessenger2 initiates connection request to tkMessenger1.
// (2) tkMessenger1 accepted connection request from tkMessenger2
// (3) tkMessenger1 connects back to tkMessenger2.
// (4) tkMessenger1.evConnectBack event fired.
cCryptoTcpTokenizer.dgConnectBack dgConnectBackTo2 =
new cCryptoTcpTokenizer.dgConnectBack(OnConnectBackTo2);
tkMessenger1.evConnectBack += dgConnectBackTo2;
//STEP M1.3: Start listening for incoming connection:
tkMessenger1.StartListening();
//STEP M1.4: Retrieve tkMessenger2's public key so data
//can be sent to tkMessenger2 encrypted.
// Wait until connection FROM tkMessenger1
//TO tkMessenger2 is established.
evConnectBackTo2.WaitOne();
tkMessenger1.AsynRequestTargetPublicKey();
//Issue request for tkMessenger2's public key.
//STEP M1.5: Block current thread and wait until
//tkMessenger2 has sent over the data.
evMsg2SendComplete.WaitOne();
//STEP M1.6: Sending data back to Messenger2
//Wait until you've got your target's public key:
//Only then you can start sending
//encrypted msg to your target.
evMsg1PKRetrieved.WaitOne();
Console.WriteLine("tkMessenger1: sending 10" +
" messages to tkMessenger2.");
tkMessenger2.bSendEncrypted=true; //Send encrypted.
for(index=0; index<10; index++)
{
tkMessenger1.SendMessage("Message index[" +
index.ToString() + "] (encrypted)" );
}
//STEP M1.7: Fire event to notify tkMessenger2
//that messages has been sent.
evMsg1SendComplete.Set();
//STEP M1.8:
//CAUTION: Make sure data has been transferred:
// FROM: incoming NetworkStream
// TO: tkMessenger1.m_receiver.m_bufferQueueCollection
// Then,
// FROM: tkMessenger1.m_receiver.m_bufferQueueCollection
// TO: tkMessenger1.qIncomingMessages
//(Event " evIncomingMessage" fired!)
//
//This process takes time, especially when messages are encrypted.
//But whenever a message arrives, event
//"tkMessenger1.evIncomingMessage" is fired and handler
//"OnIncomingMsg1()" is invoked to display the message.
while(bContinue==true)
{
}
//STEP M1.9: Stop listener.
tkMessenger1.StopListening();
Console.WriteLine("Messenger1 thread returning.");
}
catch(Exception err)
{
Console.WriteLine(err.Message);
}
return;
}
public void Messenger2()
{
int index=0;
try
{
//STEP M2.1: Configure tokenizer
tkMessenger2 = new cCryptoTcpTokenizer();
tkMessenger2.listenPort = 12000;
tkMessenger2.targetIP="localhost";
tkMessenger2.targetPort=11000;
//STEP M2.2: Subscribe to events
cCryptoTcpTokenizer.dgIncomingMessage dgIncoming2 =
new cCryptoTcpTokenizer.dgIncomingMessage(OnIncomingMsg2);
tkMessenger2.evIncomingMessage += dgIncoming2;
cCryptoTcpTokenizer.dgTargetPublicKeyRetrieved
dgMsg2PKRetrieved =
new cCryptoTcpTokenizer.dgTargetPublicKeyRetrieved(
OnMsg2PKRetrieved);
tkMessenger2.evTargetPublicKeyRetrieved += dgMsg2PKRetrieved;
//STEP M2.3: Start listening for incoming connection
//and connect to tkMessenger1:
tkMessenger2.StartListening();
tkMessenger2.ConnectTarget();
//STEP M2.4: Request tkMessenger1 for it public key:
tkMessenger2.AsynRequestTargetPublicKey();
//Key is retrieved asynchronously.
//Wait until tkMessenger1's public key is retrieved before
//you start sending encrypted message to
//tkMessenger1.
evMsg2PKRetrieved.WaitOne();
//STEP M2.5: Send data to tkMessenger1. First 5 in plaintext.
//Then 5 more encrypted.
tkMessenger2.bSendEncrypted=false; //Send plaintext!
Console.WriteLine("tkMessenger2: sending " +
"10 messages to tkMessenger1.");
for(index=0; index<5; index++)
{
tkMessenger2.SendMessage("Message index[" +
index.ToString() + "] (plaintext)" );
}
index=0;
tkMessenger2.bSendEncrypted=true; //Send encrypted!
for(index=0; index<5; index++)
{
tkMessenger2.SendMessage("Message index[" +
index.ToString() + "] (encrypted)" );
}
//STEP M2.6: Set event to alert tkMessenger1 that
//all messages has been dispatched.
evMsg2SendComplete.Set();
//STEP M2.7: Wait until tkMessenger1 has sent all data.
evMsg1SendComplete.WaitOne();
//STEP M2.8: Display data received from tkMessenger1.
while(bContinue==true)
{
//While tkMessenger2 display messages whenever
//"cCryptoTcpTokenizer.evIncomingMessage" is fired.
}
//STEP M2.9
tkMessenger2.StopListening();
Console.WriteLine("Messenger2 thread returning.");
}
catch(Exception err)
{
Console.WriteLine(err.Message);
}
return;
}
} //cCryptoTcpTokenizerDemo
class cMain
{ //cMain
[STAThread]
static void Main(string[] args)
{
cCryptoTcpTokenizerDemo demo = new cCryptoTcpTokenizerDemo();
Thread tTom = new Thread( new ThreadStart(demo.Messenger1));
tTom.Name="Tom";
Thread tJerry = new Thread( new ThreadStart(demo.Messenger2));
tJerry.Name="Jerry";
tTom.Start();
Thread.Sleep(100);
tJerry.Start();
Thread.Sleep(180000); //Wait 180 seconds.
demo.bContinue=false;
return;
}
} //cMain
}
列表 1
演练 (1.3)
第 1.2 节中的示例包含三个块
cCryptoTcpTokenizer
支持的事件的事件处理程序。- 两个线程函数:Messenger1() 和 Messenger2()
Main(..)
– 该演示是一个 C# 控制台应用程序。因此,从这里开始跟踪演示。
public class cCryptoTcpTokenizerDemo
{ //cCryptoTcpTokenizerDemo
//BLOCK 1: Declarations
public bool bContinue;
public cCryptoTcpTokenizer tkMessenger1;
public cCryptoTcpTokenizer tkMessenger2;
public ManualResetEvent evMsg1SendComplete;
//tkMessenger1 is done sending message to tkMessenger2.
public ManualResetEvent evMsg2SendComplete;
//tkMessenger2 is done sending message to tkMessenger1.
public ManualResetEvent evMsg1PKRetrieved;
//tkMessenger1 has retrieved public key (RSA) from tkMessenger2.
public ManualResetEvent evMsg2PKRetrieved;
//tkMessenger2 has retrieved public key (RSA) from tkMessenger1.
public ManualResetEvent evConnectBackTo2;
//tkMessenger1 has connected back to tkMessenger2, in response to
//tkMessenger2's connection request.
public cCryptoTcpTokenizerDemo()
{
... code ...
}
//BLOCK 2: Handlers for cCryptoTcpTokenizer events.
//****************** Handlers ******************
void OnConnectBackTo2()
{
... code ...
}
void OnIncomingMsg1()
{
... code ...
}
void OnIncomingMsg2()
{
... code ...
}
void OnMsg1PKRetrieved()
{
... code ...
}
void OnMsg2PKRetrieved()
{
... code ...
}
//BLOCK 3: Thread Functions Messenger1 and Messenger2
public void Messenger1()
{
... code ...
}
public void Messenger2()
{
... code ...
}
} //cCryptoTcpTokenizerDemo
//BLOCK 4: Main
class cMain
{ //cMain
[STAThread]
static void Main(string[] args)
{
//STEP 1: Launches Messenger1 thread.
//STEP 2: Launches Messenger2 thread.
}
} //cMain
}
列表 2
Main(...)
首先,启动两个线程。线程函数 Messenger1()
和 Messenger2()
实例化 cCryptoTcpTokenizer
的两个实例(tkMessenger1
和 tkMessenger2
),配置它们指向对方,并将消息处理程序与 cCryptoTcpTokenizer
公开的事件关联起来。
接下来发生的是一系列线性事件
- tkMessenger2 (Messenger2 的线程) 连接到 tkMessenger1
- tkMessenger1 通过连接回 tkMessenger2 来响应
发生这种情况时,会引发
cCryptoTcpTokenizer.evConnectBack
事件。tkMessenger1 现在可以发出请求获取 tkMessenger2 的公钥(在建立连接之前,您无法向目标发出密钥请求)。 - tkMessenger2 发出请求获取 tkMessenger1 公钥的请求。
- tkMessenger1 发出请求获取 tkMessenger2 公钥的请求。
- tkMessenger2 等待直到检索到 tkMessenger1 的密钥。
发生这种情况时,会引发事件
cCryptoTcpTokenizer.dgTargetPublicKeyRetrieved
并调用相应的处理程序OnMsg2PKRetrieved
。 - tkMessenger1 等待直到检索到 tkMessenger2 的密钥。
- tkMessenger2 将消息发送到 tkMessenger1。
- tkMessenger1 将消息发送到 tkMessenger2。
- 只要
bContinue==true
,线程 Messenger1 和 Messenger2 就会保持活动状态。while(bContinue==true) { //both tkMessenger1 and tkMessenger2 will continue //parsing the network streams while bContinue==true. }
在此期间,tkMessenger1 和 tkMessenger2 继续解析连接两个 cCryptoTcpTokenizer 实例的网络流。每个
cCryptoTcpTokenizer
实例封装两个 TcpTokenizer 实例。cCryptoTcpTokenizer.m_sender
负责将数据发送到目标。cCryptoTcpTokenizer.m_receiver
负责从目标接收数据。
请记住
- tkMessenger1 的
m_receiver
连接到 tkMessenger2 的 m_sender。 - tkMessenger1 的
m_sender
连接到 tkMessenger2 的 m_receiver。
直到调用
StopListening()
,tkMessenger1 将继续从连接tkMessenger1.m_receiver
和tkMessenger2.m_sender
的网络流中检索可用数据,并将其加载到传入消息队列 -tkMessenger1.qIncomingMessages
(类型:“System.Queue
”)。每次有新消息到达时,都会引发cCrytoTcpTokenizer.evIncomingMessage
事件。调用该事件的处理程序(OnIncomingMsg1
)。从 tkMessenger1 的消息队列(tkMessenger1.qIncomingMessages
)中检索传入消息,然后显示到屏幕上。 Main(..)
在 180 秒后将bContinue
切换为false
。然后调用StopListening()
。tkMessenger1/tkMessenger2 断开与网络流的连接,并停止解析/读取网络流。
图 1. 两个 cCryptoTcpTokenizer
实例之间的通信(CryptoTcpTokenizerDemo.cs)
架构
- 配置和连接
- 事件订阅
- 密钥交换
- 发送和接收消息
配置和连接
首先,cCryptoTcpTokenizer
的一个实例必须配置其监听的端口。
- 设置
cCryptoTcpTokenizer.listenport
。 - 调用
cCryptoTcpTokenizer.StartListening()
以开始监听传入的连接请求、密钥请求和消息。
连接到目标
- 设置
cCryptoTcpTokenizer.targetPort
和cCryptoTcpTokenizer.targetIP
- 调用
cCryptoTcpTokenizer.ConnectTarget()
当一个实例发起连接到远程实例时,远程实例会通过以下方式响应:
- 接受连接
- 内部调用
ConnectBackToClient()
,后者又调用ConnectTarget()
。方法返回后会引发evConnectBack
事件。
事件订阅
有三个事件可供客户端订阅
事件 |
当 |
evIncomingMessage |
传入消息处理后触发。要检索传入的消息,在该事件触发后,从“消息队列” |
evTargetPublicKeyRetrieved |
检索到目标的公钥后触发。在引发此事件之前,请勿尝试发送加密的消息。检索到目标的公钥后,可以按以下方式发送加密的消息:
|
EvConnectBack |
一个实例连接回远程实例时触发。 |
表 2 cCryptoTcpTokenizer 事件
订阅 cCryptoTcpTokenizer
事件
cCryptoTcpTokenizer.dgIncomingMessage dgIncoming1 =
new cCryptoTcpTokenizer.dgIncomingMessage(OnIncomingMsg1);
tkMessenger1.evIncomingMessage += dgIncoming1;
密钥交换
密钥请求的发送方式与常规消息的发送方式非常相似。以下是密钥交换过程的描述:
请求目标的公钥
- 确保此
cCryptoTcpTokenizer
实例(服务器 A)和远程实例(服务器 B)之间的连接已建立。检查bIsConnected
。 - 服务器 A 调用
AsynRequestTargetPublicKey()
。在内部,该方法会将密钥请求加载到m_sender.bufferQueue
并发送到远程实例(服务器 B)。string sREQ = "REQ_RSA_PUBKEY"; //That’s the message label. m_sender.bufferQueue.Enqueue(sREQ); m_sender.Send();
- 远程实例(服务器 B)接收到此消息。在内部,远程实例(服务器 B)的 m_receiver 会引发一个
cTcpTokenizer
事件“evbufferLoaded
”,该事件触发ProcessIncomingMsg()
。该方法检查 m_receiver.bufferQueueCollection,该集合现在包含密钥请求“REQ_RSA_PUBKEY
”。该消息被识别为密钥请求,因此被路由到DispatchRsaPK()
。 DispatchRsaPK()
负责以接收密钥请求的相同方式将其公钥发送到目标。方法如下:m_sender.bufferQueue.Enqueue("REQ_RSA_PUBKEY_RESULT"); //Message label. m_sender.bufferQueue.Enqueue(m_rsaSelfPublicKey); //The public key in XML string. m_sender.bufferQueue.Enqueue(btHash); //Hash of public key. m_sender.Send(); //Sending the message.
- 响应由服务器 A 接收。与所有其他类型的消息一样,“
REQ_RSA_PUBKEY_RESULT
”消息首先由ProcessIncomingMsg()
检索。消息类型被识别,然后执行被路由到SetRecipientRsaPK()
。 SetRecipientRsaPK()
然后将目标(服务器 B)的公钥存储在受保护的字段中:m_rsaRecipientPublicKey。m_bIsRecipientPKRetrieved
在内部被切换为 true。- 服务器 A 现在已准备好以加密模式将消息发送到服务器 B。在调用
Send()
方法之前,将cCryptoTcpTokenizer.bSendEncrypted
切换为 true。对于服务器 B 发送加密消息,服务器 B 必须向服务器 A 发出密钥请求。
发送和接收消息
以下部分描述了使用 cCryptoTcpTokenizer
发送和接收消息的内部过程。
- 服务器 A 调用
Send()
,尝试将消息发送到服务器 B。 - 如果
bSendEncrypted==true
,则调用SendMessageEncrypted()
。如果
bSendEncrypted==false
,则调用SendMessagePlaintext()
。 - 在内部,
SendMessageEncrypted()
和SendMessagePlaintext()
都会将“消息”打包到m_sender.bufferQueue
中。然后,m_sender
(类型:cTcpTokenizer)负责将消息发送到目标。
单个“消息”(类型:“System.Queue
”)由多个“标记”组成(类型:“System.Object
”)。第一个标记是“消息标签”。后续标记取决于服务器 A 发送的是加密消息还是明文消息。
单个加密“消息”的内存映射如下所示:
图 2 内存映射:加密消息
SendMessageEncrypted()
首先使用对称算法 RijndaelManaged 加密明文消息——对称算法比非对称算法快千倍。密钥和初始化向量使用接收者的公钥进行加密。加密消息的哈希值使用接收者的公钥进行加密——因此,只能由接收者的公钥解密。
以下是在 SendMessageEncrypted()
中排队和分派标记的方式:
m_sender.bufferQueue.Enqueue("ENCRYPTED_PAYLOAD");
m_sender.bufferQueue.Enqueue(btEncryptedRMKey);
m_sender.bufferQueue.Enqueue(btEncryptedRMIV);
m_sender.bufferQueue.Enqueue(btEncryptedHash);
m_sender.bufferQueue.Enqueue(btEncryptedMessage);
m_sender.Send(); //m_sender is of type: cTcpTokenizer
单个明文消息的内存映射如下所示:
图 3 内存映射:明文消息
- 服务器 B 接收到消息。
m_receiver
引发事件“evbufferLoaded”,该事件触发cCryptoTcpTokenizer.ProcessIncomingMsg()
。 cCryptoTcpTokenizer.ProcessIncomingMsg()
首先通过检查消息标签对消息进行分类。消息可以是以下类型:
消息类型 |
描述 |
Handler |
REQ_RSA_PUBKEY |
请求公钥 |
DispatchRsaPK() |
REQ_RSA_PUBKEY_RESULT |
对公钥请求的响应 |
SetRecipientRsaPK() |
ENCRYPTED_PAYLOAD |
消息(加密) |
ProcessEncryptedPayload() |
PLAINTEXT_PAYLOAD |
消息(明文) |
ProcessPlaintext() |
表 3 消息类型
ProcessIncomingMsg
根据“消息标签”将执行路由到适当的处理程序。
ProcessEncryptedPayload
和ProcessPlaintext -
ProcessPlaintext
只是将消息放入cCryptoTcpTokenizer.qIncomingMessages
供以后检索。然后该方法将引发 evIncomingMessage 事件以通知任何订阅者。
ProcessEncryptedPayload
首先使用其自己的私钥、RSA 算法和 cRSAWrap
解密以下标记:
- 密钥(RijndaelManaged)
- 初始化向量(RijndaelManaged)
- 加密消息的哈希值(SHA1)
通过比较接收到的哈希值和在接收到的加密消息上计算的哈希值来确认消息完整性。然后可以使用 RijndaelManaged 算法以及先前解密的密钥/初始化向量解密载荷。
图 4. cCryptoTcpTokenizer
类
可扩展性和性能
线程安全 (3.1)
cCryptoTcpTokenizer.qIncomingMessages
是线程安全的。
性能测试 (3.2)
- 测试方法:发送多条消息,并测量发送第一条消息的时间与接收最后一条消息之间的时间差。两个 IP 端点位于同一台物理机上。
- 设置:Toshiba Satellite 2400 1694MHz 256MB
- 源代码:cCryptoTcpTokenizerPerformanceTest.cs
- 项目目录:\cCryptoTcpTokenizer_package \clCryptoTcpTokenizer\ cCryptoTcpTokenizerPerformanceTest\
明文消息
表 4 第一个消息(明文)发送与最后一个消息接收之间经过的时间(单位:秒)
加密消息
表 5 第一个消息(加密)发送与最后一个消息接收之间经过的时间(单位:秒)
图表 1
图表 2
图表 3
第一次发送消息与最后一次接收消息之间经过的时间(单位:秒;共发送 10 条消息。)
消息大小 |
明文 |
加密 |
性能 比例 |
10 |
3.81 |
91 |
23.8 |
50 |
3.92 |
103 |
26.2 |
500 |
3.95 |
95 |
24.0 |
1 kB |
3.98 |
98 |
24.6 |
10 kB |
2.5 |
97 |
38.8 |
100 kB |
32 |
123 |
3.84 |
1 MB |
353 |
465 |
1.32 |
表 6 性能比较:明文与加密消息。
表 7
观察
- 对于较小的消息(大小 < 1 kB),加密消息的处理时间大约是明文消息的 24 倍。对于较大的消息,加密消息和明文消息之间的处理时间差异要小得多。(表 6)
- 加密和传输 10 张 JPEG(每张 100kB)到指定目标大约需要 100 秒。如果图像未加密,则同一过程大约需要 32 秒。(表 4、表 5、表 7)
- 对于较小的消息(< 100 kB),消息大小对处理时间影响很小。对于较大的消息,处理时间和消息大小呈线性关系。(图表 1、图表 2、图表 3)。
后续版本中的进一步扩展
CRSAWrap
:
cRSAWrap.EncryptBuffer()
和 cRSAWrap.DecryptBuffer():
应该为每个连接/会话实例化 RSACryptoServiceProvider
。
CTcpTokenizer (安全警告)
cCryptoTcpTokenizer
的“消息”封装在 cTcpTokenizer
的“消息”中。但是 cTcpTokenizer
的消息容易受到 DOS 攻击,因为没有机制来防止不正确/损坏的块大小(参见附录 2 图 5 内存映射:cCryptoTcpTokenizer
消息)。例如,“数据块大小”在块 1 和块 2 中指定了“数据块”的大小(块 3)。攻击者可以将块 2 中的“数据块大小”标记覆盖为大于或小于块 3 中相应缓冲区实际大小的数字。这将在后续解析中导致问题并关闭监听线程。一种方法是加密发送 cTcpTokenizer
消息的哈希值。
cCryptoTcpTokenizer
:
- 当前实现不支持组播/广播。
- 没有机制来确认消息的接收。
- 应为明文消息提供消息签名。对于加密消息,消息签名应作为可选参数提供。
- Send 方法的附加重载
Send(string sMessage)
Send(byte[] btMessage)
质量问题
- 需要进行更多测试才能全面评估
cCryptoTcpTokenizer
的性能特征。 - 安全审查待定。
附录
- 常见加密异常
- cTcpTokenizer
- cRSAWrap
- 免责声明
附录 1:常见加密异常
MSDN 提供了关于 System.Security.Cryptography
命名空间的相当不错的文档。文档可以在主题:“Cryptographic Tasks”、“Cryptography Overview”下找到。有几个问题没有得到充分阐述:
- 常见加密异常:无效密钥、无效数据、无效长度
- RSA 加密/解密中的多块加密(请参阅附录 3 cRSAWrap)
在本节中,我们将简要讨论常见的加密异常。
- 解决方案名称:RSACryptoServiceProvider Demo
- 解决方案目录:\cCryptoTcpTokenizer_package\RSACryptoServiceProvider Demo\
- 项目名称:SimplestRSADemo
- 源代码:SimplestRSADemo.cs
演示
using System;
using System.Text;
using System.Security.Cryptography;
namespace tryDecrypt
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
int keySize=0;
int blockSize=0;
byte[] buffer;
byte[] encryptedbuffer;
byte[] decryptedbuffer;
string decryptedstring;
// STEP 1: Create key pair
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
RSAParameters completeParams =
rsa.ExportParameters(true); //Complete key pairs.
RSAParameters publicParams =
rsa.ExportParameters(false); //Only public key.
// STEP 2:
// (a) Encrypt our text with public key only.
// (b) CAUTION: Block size must be 11 bytes less
// KeySize (unit: bytes). Otherwise, you'll get a
// cryptographic exception: Bad Length.
// (c) block size is 117 in this case.
// Try this: adjusts length of plainText to 118.
// You'll get a cryptographic exception: “Bad Length”
string plainText = "00000000011111111111111122222222222222222" +
"3333333333333333333000000000000000000111111111111111" +
"222222222222222223333333";
Console.WriteLine("plainText to be encrypted: {0}", plainText);
Console.WriteLine("plainText.Length: {0}",
Convert.ToString(plainText.Length));
RSACryptoServiceProvider rsaSender = new RSACryptoServiceProvider();
rsaSender.ImportParameters(publicParams); //Import public key only.
keySize = rsaSender.KeySize/8; //KeySize in number of bits,
// so we need to divide that by 8 to convert it
//to bytes.
blockSize = keySize -11; //keysize less 11 bytes = block size.
//Why? Don't ask.
Console.WriteLine("keySize = {0}; blockSize = {1}",
Convert.ToString(keySize),
Convert.ToString(blockSize));
buffer = Encoding.ASCII.GetBytes(plainText);
encryptedbuffer = rsaSender.Encrypt(buffer, false);
Console.WriteLine("Encrypted stuff: {0}",
Encoding.ASCII.GetString(encryptedbuffer) );
rsaSender.Clear();
rsaSender= null;
// STEP 3: Decrypt encrypted buffer.
RSACryptoServiceProvider rsaReceiver =
new RSACryptoServiceProvider();
//(a) completeParams contain the private key with which
// one decrypt the encrypted payload.
//(b) Skip this line and use key generated by constructor
// initialization and see what happens:
// "...Cryptographic exception: Bad Data..."
//(c) If you use publicParams instead of completeParams,
//you will get:
// "...Cryptographic exception: Bad Key..."
rsaReceiver.ImportParameters(completeParams);
decryptedbuffer = rsaReceiver.Decrypt(encryptedbuffer, false);
decryptedstring = Encoding.ASCII.GetString(decryptedbuffer);
Console.WriteLine("Decrypted stuff: {0}", decryptedstring);
Console.WriteLine("Lenght of decrypted stuff: {0}",
Convert.ToString(decryptedstring.Length));
Console.ReadLine();
}
}
}
附录 2:cTcpTokenizer
设计目标
- 促进从
NetworkStream
检索到的数据的解析/分词。 - 简化将由多个标记组成的消息发送到目标 IP 地址的过程。
- 线程安全操作。
- 解决方案名称:TcpTokenizer
- 解决方案目录:\cCryptoTcpTokenizer_package\TcpTokenizer\
- 项目名称:TcpTokenizer
- 源代码:TcpTokenizer.cs
- 演示项目名称:TcpTokenizerDemo
- 演示源代码:TcpTokenizerDemo.cs
演示
//TcpTokenizer demo
using System;
using System.Text;
using System.Threading;
using System.Collections;
using nsTcpTokenizer;
namespace TcpTokenizerDemo
{
public class ClassDemo
{ //ClassDemo
public cTcpTokenizer sender;
public cTcpTokenizer receiver;
public bool bContinue;
public ClassDemo()
{
bContinue=true;
}
//****************** SENDING SIDE ******************
public void StartSending()
{ //StartSending
Console.WriteLine("StartSending invoked.");
try
{
//This is the array of buffer you wish to
//send to the other side:
//CAUTION: Only two data types are supported
//at the moment: byte[], string.
string [] payload1 = new string []
{"AAA", "BBBB", "CCCCC", "DDDDDD", "EEEEEEE"};
string [] payload2 = new string []
{"T2kSN", "yfh@", ".:sX2", "AA*3@$", "@@B"};
string sPayload3 = "AABBCCDD";
byte[] btPayload3 = Encoding.ASCII.GetBytes(sPayload3);
sender = new cTcpTokenizer();
//Indicates this instance of cTcpTokenizer is configured
//to send messages (ie. “sender”
//cannot receive message).
sender.bMode = true;
//**************** Sending FIRST batch of data from
//FIRST Tcp connection ****************
//Set target connection setting and establish connection:
sender.targetIP = "localhost";
sender.targetPort = 11000;
sender.ConnectTarget();
//Load data:
for(int i=0; i<5; i++)
{
sender.bufferQueue.Enqueue(payload2[i]);
}
//Load more data:
for(int i=0; i<3; i++)
{
sender.bufferQueue.Enqueue(payload1[i]);
}
//Send it:
sender.Send();
//Disconnect target
//NOTE: You don't need to disconnect between Send().
//**************** Sending SECOND batch of data from
//FIRST Tcp connection ****************
//Load more data:
for(int i=0; i<3; i++)
{
sender.bufferQueue.Enqueue(payload1[i]);
}
sender.Send();
sender.DisconnectTarget();
//**************** Sending THIRD batch of data from
//SECOND Tcp connection ****************
//Connect again - IF disconnected previously:
sender.targetIP = "localhost";
sender.targetPort = 11000;
sender.ConnectTarget();
//Load more data:
for(int i=0; i<5; i++)
{
sender.bufferQueue.Enqueue(payload2[i]);
}
sender.Send();
sender.bufferQueue.Enqueue(btPayload3);
sender.Send();
sender.DisconnectTarget();
}
catch(Exception err)
{
Console.WriteLine("Sender side: error detected.");
Console.WriteLine(err.ToString());
}
finally
{
Console.WriteLine("StartSending return.");
}
return;
} //StartSending
//****************** EVENT HANDLERS ******************
//Event handlers:
public void OnIncomingConnectionAccepted()
{
Console.WriteLine("Incoming connection accepted.");
}
public void OnBufferLoaded()
{
byte[] btToken;
string sToken;
int index=0;
//Display incoming message.
Console.WriteLine("Incoming message:");
Queue qMsg = (Queue) receiver.bufferQueueCollection.Dequeue();
index=0;
foreach(object oToken in qMsg)
{
if(oToken.GetType().ToString().Equals("System.String"))
{
Console.WriteLine("Token[{0}]: {1}", index.ToString(),
oToken.ToString());
}
else if(oToken.GetType().ToString().Equals("System.Byte[]"))
{
btToken = (byte[]) oToken;
sToken = Encoding.ASCII.GetString(btToken);
Console.WriteLine("Token[{0}]: {1}", index.ToString(),
sToken);
}
index++;
}
Console.WriteLine();
return;
}
//****************** RECEIVING SIDE ******************
public void StartListening()
{ //StartListening
Console.WriteLine("StartListening invoked.");
try
{
//STEP L1: Configure receiver
receiver = new cTcpTokenizer();
//Indicates this instance of cTcpTokenizer is configured
//to receive data only.
//(ie. receiver cannot send data)
receiver.bMode = false;
receiver.listenPort=11000; //Port number
//Frequency at which cTcpTokenizer checks for
//incoming Tcp connection.
receiver.polltime = 500;
//STEP L2: Subscribe to events.
//(a) "evIncomingConnectionAccepted" event is fired
//everytime a Tcp connection is accepted.
// Please refer to cTcpTokenizer.Listen()
cTcpTokenizer.dgIncomingConnectionAccepted d1 = new
cTcpTokenizer.dgIncomingConnectionAccepted(
OnIncomingConnectionAccepted);
receiver.evIncomingConnectionAccepted += d1;
//(b) "receiver.evbufferLoaded" event is fired everytime
//"receiver.bufferQueueCollection"
// and "receiver.bufferQueue" are loaded.
// (ie. Event fired whenever incoming message arrives.)
cTcpTokenizer.dgbufferLoaded d3 =
new cTcpTokenizer.dgbufferLoaded(OnBufferLoaded);
receiver.evbufferLoaded += d3;
//STEP L3: starts listening for incoming
//connection and incoming data
receiver.StartListening();
//CAUTION: Thread is blocked here to allow time for
//receiver.Listen()
// and receiver.ParseIncomingBuffer() to parse the incoming data
// on the network stream and load data
// in receiver.bufferQueueCollection.
// The duration of time to continue listening on the first Tcp
// connection is "arbitrary". No event is available
// from cTcpTokenizer
// class to notify the client application
// that first Tcp connection
// has finished loading the incoming network stream.
// There's no way
// for the receiving end to know whether the sending
// side has done
// sending UNLESS the sending side (sender)
// notify the receiving side
// (receiver) that sender has finished sending.
// "3000 ms" here is arbitrary and any unparsed
// data that remains on
// network stream will be lost upon execution of STEP L4 below.
Thread.Sleep(3000);
//STEP L4: Listen in for second Tcp connection.
// This will close network stream associated
//with the first Tcp connection.
receiver.DisconnectCurrentClient();
//STEP L5: Continue parsing incoming network stream/data.
while(bContinue==true)
{
}
//STEP L6: Thread that hosts ParseIncomingBuffer()
//and Listen() is aborted
// as soon as StopListening() is invoked.
// Therefore, receiver will stop parsing the
// network stream after
// the next statement is executed. Any unparsed
// data on network
// stream will be lost.
receiver.StopListening();
}
catch(Exception err)
{
Console.WriteLine("Receiver side: error detected.");
}
finally
{
Console.WriteLine("StartListening return.");
}
return;
} //StartListening
} //ClassDemo
public class ClassMain
{ //ClassMain
[STAThread]
static void Main(string[] args)
{ //Main()
ClassDemo demo = new ClassDemo();
Thread receiverThread = new Thread(
new ThreadStart(demo.StartListening));
Thread senderThread =
new Thread( new ThreadStart(demo.StartSending));
receiverThread.Start();
Thread.Sleep(100);
senderThread.Start();
Thread.Sleep(150000);
demo.bContinue=false;
} //Main()
} //ClassMain
}
图 5. 内存映射:cTcpTokenizer
消息。
图 6. cTcpTokenizer
类
附录 3:cRSAWrap
- 设计目标:简化多块加密/解密(RSA)——它基本上是 RSACryptoServiceProvider 的包装器。
- 解决方案名称:RSACryptoServiceProvider Demo
- 解决方案目录:\cCryptoTcpTokenizer_package\RSACryptoServiceProvider Demo\
- 项目名称:SimplestRSADemo
- 源代码:SimplestRSADemo.cs
- 所需命名空间:System; System.Text; System.Security.Cryptography; nsRSAWrap;
演示
string secret = "At a beach resort best known for turquoise surf "
+ "and drunken U.S. college students, trade ministers huddled" +
"in conference rooms of five-star hotels in preparation " +
"for the meeting, which begins Wednesday. Away from the " +
"hotel zone, thousands of anti-globalization activists from " +
"around the world set up camp, renting hammocks and swatting " +
"mosquitoes, and vowed to derail the meetings with protests " +
"and marches, as they did in Seattle in 1999. Agriculture will " +
"likely be at the top of the Cancun agenda. Removing barriers " +
"to trade in agriculture is controversial, with developing " +
"nations demanding that rich countries like Japan, the United" +
"States and European nations end subsidies and tariffs " +
"designed to keep unprofitable farms afloat.";
byte [] btSecret;
byte [] btEncryptedSecret;
byte [] btDecryptedSecret;
ASCIIEncoding AE = new ASCIIEncoding();
//As future extension, RSACryptoServiceProvider should
//be kept alive for as long as
//the connection lasts.
RSACryptoServiceProvider rsaKeyGen = new RSACryptoServiceProvider();
string rsaCompleteKeyString =
rsaKeyGen.ToXmlString(true); //private + public key
string rsaPublicKeyString =
rsaKeyGen.ToXmlString(false); //public key only.
btSecret = Encoding.ASCII.GetBytes(secret);
//Encrypt: EncryptBuffer takes only two parameters.
// (1) Public Key (RSA/Xml format string)
// (2) Buffer to be encrypted.
btEncryptedSecret = cRSAWrap.EncryptBuffer(
rsaPublicKeyString, btSecret);
//Decrypt: DecryptBuffer takes only two parameters.
// (1) Private Key (RSA/Xml format string)
// (2) Buffer to be decrypted.
btDecryptedSecret = cRSAWrap.DecryptBuffer(
rsaCompleteKeyString, btEncryptedSecret);
关于多块加密/解密的说明
1. 密钥大小 vs 块大小
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
... more code ...
keySize = rsaSender.KeySize/8;
blockSize = keySize -11;
2. RSACryptoServiceProvider.Encrypt
btEncryptedToken = rsaSender.Encrypt(btPlaintextToken, false);
btEncryptedToken size: keySize;
btPlaintextToken size: blockSize;
注意:如果 btPlaintextToken
的缓冲区大小设置不正确,将会引发加密异常“Bad Data”。
3. RSACryptoServiceProvider.Decrypt
btPlaintextToken = rsaReceiver.Decrypt(btEncryptedToken, false);
btEncryptedToken size: keySize;
btPlaintextToken size: blockSize;
注意:如果 btEncryptedToken
的缓冲区大小设置不正确,将会引发加密异常“Bad Data”。
附录 4:免责声明
本软件由作者/贡献者“按原样”提供,并且作者/贡献者不提供任何明示或暗示的保证,包括但不限于适销性和特定用途适用性的暗示保证。在任何情况下,作者/贡献者均不对因使用本软件而产生的任何直接、间接、附带、特殊、惩戒性或后果性损害(包括但不限于采购替代品或服务;使用、数据或利润损失;或业务中断)承担任何责任,无论是由何种原因引起,也无论其责任理论如何,无论是在合同、严格责任还是侵权(包括疏忽或其他)中,即使已被告知发生此类损害的可能性。