CCryptoTokenizer






4.50/5 (6 votes)
Nov 14, 2003
12 min read

64993

2406
CCryptoTokenizer
- Download TCP tokenizer source - 86 Kb
- Download Crypto TCP tokenizer source - 21 Kb
- Download RSA Crypto service provider demo - 25 Kb
Abstract
cCryptoTcpTokenizer
builds upon .NET framework, extends what’s offered by System.Security.Cryptography
and provides a mechanism that facilitates secured, asynchronous communication between two IP end points. cCryptoTcpTokenizer
supports:
- Message encryption/decryption: Messages are encrypted using Rijndael algorithm. Secret key and initialization vector used in payload encryption are, in turn, encrypted using recipient’s public key. Encrypted secret key and initialization vector are sent to recipient along with the encrypted message.
cCryptoTcpTokenizer
manages key exchanges internally and the whole process is transparent to client. - Message integrity and signing.
- Parsing/tokenizing of incoming network stream.
- Events to support asynchronous messaging:
- EvIncomingMessage Incoming message detected.
- EvTargetPublicKeyRetrieved Recipient’s public key retrieved.
- EvConnectBack Connect back to target established.
Contents
- Application Notes
- Architecture
- Scalability and performance
- Further extension in subsequent releases
- References
- Appendix
Application Notes
cCryptoTcpTokenizer package info
- executable: clCryptoTcpTokenizer.dll
- solution: clCryptoTcpTokenizer (clCryptoTcpTokenizer.sln)
- Solution Directory: cCryptoTcpTokenizer_package\clCryptoTcpTokenizer\
- source: CryptoTcpTokenizer.cs
- demo source: CryptoTcpTokenizerDemo.cs
- demo executable: CryptoTcpTokenizerDemo.exe (C# console application)
- VERSION: 1.0.1355.23652 (BETA)
Referenced assemblies
Class |
DLL |
Supports |
Source: TcpTokenizer.cs Namespace: |
TcpTokenizer.dll |
Supports sending/receiving/tokenizing messages TO/FROM network stream. |
Source: RSAWrap.cs Namespace: |
clRSAWrap.dll |
Multi-block RSA encryption/decryption subroutines. |
Table 1 Referenced Assemblies
Other packages included:
a. cTcpTokenizer
- executable: TcpTokenizer.dll
- solution: TcpTokenizer (TcpTokenizer.sln)
- Solution Directory: cCryptoTcpTokenizer_package\ TcpTokenizer\
- source: TcpTokenizer.cs
- demo source: TcpTokenizerDemo.cs
- demo executable: TcpTokenizerDemo.exe
- performance test: cCryptoTcpTokenizerPerformanceTest.cs (Project: cCryptoTcpTokenizerPerformanceTest)
b. clRSAWrap
- solution: clRSAWrap (RSACryptoServiceProvider Demo.sln)
- Solution Directory: cCryptoTcpTokenizer_package\ RSACryptoServiceProvider Demo\
- Info: This solution is a collection of projects demonstrating basics of .NET System.Cryptography namespace: Hash, digital signature, RSA/asymmetric encryption. Included is a simple wrapper around RSACrptoServiceProvider: class cRSAWrap. The class provides two static methods to simplify multi-block RSA encryption/decryption.
Demonstration (1.2) (Source: CryptoTcpTokenizerDemo.cs)
The following code fragment below demonstrates the how to connect two IP endpoints using class cCryptoTcpTokenizer
. If you’re more comfortable with UML sequence diagrams then code, skip ahead to the Walkthrough section and read that section first.
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
}
Listing 1
Walkthrough (1.3)
The sample in Section 1.2 consists of three blocks:
- Events handlers for events supported by
cCryptoTcpTokenizer
. - Two thread functions: Messenger1( ) and Messenger2( )
Main(..)
– The demo is a C# console application. So, start tracing the demo from here.
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
}
Listing 2
Main(...)
First, two threads are launched. Thread functions Messenger1( )
and Messenger2( )
instantiate two instances of cCryptoTcpTokenizer
(tkMessenger1
and tkMessenger2
), configures them to point at each other and associates message handlers with events exposed by cCryptoTcpTokenizer
.
What happens after this is a linear sequence of events:
- tkMessenger2 (Messenger2’s thread) connects to tkMessenger1
- tkMessenger1 responds by connecting back to tkMessenger2
When this happens,
cCryptoTcpTokenizer.evConnectBack
event is raised. tkMessenger1 is now ready to issue request for tkMessenger2’s public key (You can’t issue a key request to target until a connection has been established). - tkMessenger2 dispatches a request for tkMessenger1’s public key.
- tkMessenger1 dispatches a request for tkMessenger2’s public key.
- tkMessenger2 waits until tkMessenger1’s key is retrieved.
When that happens, event
cCryptoTcpTokenizer.dgTargetPublicKeyRetrieved
is raised and corresponding handlerOnMsg2PKRetrieved
is invoked. - tkMessenger1 waits until tkMessenger2’s key is retrieved.
- tkMessenger2 sends messages to tkMessenger1.
- tkMessenger1 sends messages to tkMessenger2.
- Threads Messenger1 and Messenger2 are kept alive for as long as
bContinue==true
:while(bContinue==true) { //both tkMessenger1 and tkMessenger2 will continue //parsing the network streams while bContinue==true. }
During this time, tkMessenger1 and tkMessenger2 continue parsing from “incoming” network stream connecting the two instances of cCryptoTcpTokenizer. Each instance of
cCryptoTcpTokenizer
encapsulates two instances of TcpTokenizer:cCryptoTcpTokenizer.m_sender
Responsible for sending data to target.cCryptoTcpTokenizer.m_receiver
Responsible for receiving data from target.
Keep in mind that:
m_receiver
of tkMessenger1 is connected to m_sender of tkMessenger2.m_sender
of tkMessenger1 is connected to m_receiver of tkMessenger2.
Until
StopListening( )
is invoked, tkMessenger1 will continue to retrieve data, if available, from network stream connectingtkMessenger1.m_receiver
andtkMessenger2.m_sender
, and to load its incoming message queue -tkMessenger1.qIncomingMessages
(type: “System.Queue
”) with new messages. Every time a new message arrives,cCrytoTcpTokenizer.evIncomingMessage
event is raised. Handler (OnIncomingMsg1
) for the event is invoked. The incoming message is retrieved from tkMessenger1’s message queue (tkMessenger1.qIncomingMessages
) and is then displayed to screen. Main(..)
togglesbContinue
tofalse
after 180 seconds elapsed.StopListening( )
is then invoked. tkMessenger1/tkMessenger2 disconnects from the network stream and stops parsing/reading from the network stream.
Fig 1. Communication between two instances of cCryptoTcpTokenizer
(CryptoTcpTokenizerDemo.cs):
Architecture
- Configuration and Connections
- Event Subscriptions
- Key Exchanges
- Sending and Receiving Messages
Configuration and Connections
First an instance of cCryptoTcpTokenizer
must configure the port on which it listens on:
- Set
cCryptoTcpTokenizer.listenport
. - Call
cCryptoTcpTokenizer.StartListening( )
to starts listening in for incoming connection requests, key requests and messages.
Connects to target:
- Set
cCryptoTcpTokenizer.targetPort
andcCryptoTcpTokenizer.targetIP
- Call
cCryptoTcpTokenizer.ConnectTarget( )
When one instance initiates connection to a remote instance, the remote instance responds by:
- Accepting the connection
- Internally calls
ConnectBackToClient( )
, which in turn callsConnectTarget( ).
EventevConnectBack
is raised upon return of the method.
Event Subscriptions
Three events are available for client subscription:
Event |
When |
evIncomingMessage |
Fired when incoming message has been processed. To retrieve the incoming message, pops off “message” from the “message queue” |
evTargetPublicKeyRetrieved |
Fired when target’s public key has been retrieved. Don’t try sending message encrypted before this event is raised. Once target’s public key is retrievd, message can be sent encrypted as follows:
|
EvConnectBack |
Fired when one instance connects back to a remote instance. |
Table 2 cCryptoTcpTokenizer events
To subscribe to cCryptoTcpTokenizer
events:
cCryptoTcpTokenizer.dgIncomingMessage dgIncoming1 =
new cCryptoTcpTokenizer.dgIncomingMessage(OnIncomingMsg1);
tkMessenger1.evIncomingMessage += dgIncoming1;
Key Exchanges
Key requests are dispatched to client very much the same way regular messages are sent. Here’s a description of the key exchange process:
Requesting for target’s public key:
- Make sure connection is established between this instance of
cCryptoTcpTokenizer
(Server A) and the remote instance (Server B). CheckbIsConnected
. - Server A calls
AsynRequestTargetPublicKey
( ).
Internally, the method loadsm_sender.bufferQueue
with key request and send it to remote instance (Server B):string sREQ = "REQ_RSA_PUBKEY"; //That’s the message label. m_sender.bufferQueue.Enqueue(sREQ); m_sender.Send();
- Remote instance (Server B) receives this message. Internally, m_receiver of remote instance (Server B) raises an
cTcpTokenizer
event “evbufferLoaded
” which triggersProcessIncomingMsg( ).
The method examines m_receiver.bufferQueueCollection, which now contains the key request “REQ_RSA_PUBKEY
”. The message is identified as a key request, and is therefore routed toDispatchRsaPK( ).
DispatchRsaPK( )
is responsible for sending its public key to the target very much the same way it receives the key request. Here’s how: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.
- The response is received by Server A. As with all other kinds of message, “
REQ_RSA_PUBKEY_RESULT
” messages are first retrieved byProcessIncomingMsg( ).
The message type is identified, and execution is then routed toSetRecipientRsaPK( ).
SetRecipientRsaPK( )
then stores target’s (Server B) public key in protected field: m_rsaRecipientPublicKey.m_bIsRecipientPKRetrieved
is toggled to true internally.- Server A is now ready to send message to Server B in encrypted mode. Toggle
cCryptoTcpTokenizer.bSendEncrypted
to true before callingSend( )
method. For server B to send encrypted message, server B must issue key requests to server A.
Sending and Receiving Messages
The following section describes the internal process of sending and receiving messages with cCryptoTcpTokenizer
.
- Server A calls
Send( ),
attempts to send message to Server B. - If
bSendEncrypted==true
, thenSendMessageEncrypted( )
is invoked.If
bSendEncrypted==false
, thenSendMessagePlaintext( )
is invoked. - Internally, both
SendMessageEncrypted( )
andSendMessagePlaintext( )
packages the “message” inm_sender.bufferQueue
.m_sender
(type: cTcpTokenizer) is then responsible for sending the message to target.
A single “message” (type: “System.Queue
”) is composed of multiple “tokens” (type: “System.Object
”). The first token is “message label”. Subsequent tokens depends on which whether Server A is sending encrypted message or plaintext.
Memory map of a single encrypted “message” resembles the following:
Fig 2 Memory map: encrypted messages
SendMessageEncrypted( )
first encrypts the plaintext message using symmetric algorithm RijndaelManaged - symmetric key algorithms are a thousand times faster than asymmetric algorithms. The secret key and initialization vector are encrypted using recipient’s public key. Hash of the encrypted message is encrypted with recipient’s public key – and therefore, can only be decrypted by recipient’s public key.
Here’s how tokens are queued and dispatched inside 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
Memory map of a single plaintext message resembles the following:
Fig 3 Memory map: plaintext messages
- Server B receives the message.
m_receiver
raises event “evbufferLoaded“, which triggerscCryptoTcpTokenizer.ProcessIncomingMsg( ).
cCryptoTcpTokenizer.ProcessIncomingMsg( )
first categorizes the message by examining message label. A message can be of the following message types:
Message Type |
Description |
Handler |
REQ_RSA_PUBKEY |
Request for public key |
DispatchRsaPK() |
REQ_RSA_PUBKEY_RESULT |
Response to a request for public key |
SetRecipientRsaPK() |
ENCRYPTED_PAYLOAD |
Message (Encrypted) |
ProcessEncryptedPayload() |
PLAINTEXT_PAYLOAD |
Message (plaintext) |
ProcessPlaintext() |
Table 3 Message Types
ProcessIncomingMsg
routes execution to appropriate handlers depending on “Message Label”.
ProcessEncryptedPayload
andProcessPlaintext -
ProcessPlaintext
simply deposits the message incCryptoTcpTokenizer.qIncomingMessages
for later retrieval. The method will then fire event evIncomingMessage to alert/notify any subscriber.
ProcessEncryptedPayload
first decrypts the following tokens using its own private key, RSA algorithm and cRSAWrap
:
- secret key (RijndaelManaged)
- initialization vectors (RijndaelManaged)
- hash on encrypted message (SHA1)
Message integrity is confirmed by comparing the hash received and the hash computed on the received encrypted message. The payload can then be decrypted using RijndaelManaged algorithm and the secret key/initialization vectors decrypted earlier.
Fig 4. The cCryptoTcpTokenizer
class
Scalability and performance
Thread safety (3.1)
cCryptoTcpTokenizer.qIncomingMessages
is Thread-Safe.
Performance test (3.2)
- Test method: Send a number of messages and measure the difference between the time when the first message is sent and the time the last message is received. Both IP end points reside on the same physical machine.
- Setup: Toshiba Satellite 2400 1694MHz 256MB
- Source code: cCryptoTcpTokenizerPerformanceTest.cs
- Project directory: \cCryptoTcpTokenizer_package \clCryptoTcpTokenizer\ cCryptoTcpTokenizerPerformanceTest\
Plaintext messages:
Table 4 Time elapsed between first message (plaintext) sent and last message received (unit: sec)
Encrypted messages:
Table 5 Time elapsed between first message (encrypted) sent and last message received (unit: sec)
Chart 1
Chart 2
Chart 3
Time elapsed between first message sent and last message received (unit: sec; Total 10 messages sent.)
Message Size |
Plaintext |
Encrypted |
Performance Ratio |
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 |
Table 6 Performance Comparison: Plaintext Vs Encrypted Messages.
Table 7
Observations:
- For smaller messages (size < 1 kB), processing time for encrypted messages is roughly 24 times slower than that for plaintext messages. The difference in processing time between encrypted messages and plaintext is much less pronounced for larger messages. (Table 6)
- It will take approximately 100 sec to encrypt and wire 10 JPEG’s (100kB each) to a specified target. The same process takes approximately 32 sec to complete if the images are not encrypted. (Table 4, Table 5, Table 7)
- For smaller messages (< 100 kB), message size has little impact on processing time. For larger messages, processing time and message size exhibits a linear correlation. (Chart 1, Chart 2, Chart 3).
Further extension in subsequent releases
CRSAWrap
:
cRSAWrap.EncryptBuffer( )
and cRSAWrap.DecryptBuffer( ):
RSACryptoServiceProvider
should be instantiated on a per connection/session basis.
CTcpTokenizer
(SECURITY WARNING):
cCryptoTcpTokenizer
“messages” are encapsulated in cTcpTokenizer
“messages”. But cTcpTokenizer
messages are susceptible to DOS attacks because no mechanism is in place to guard against incorrect/corrupted block sizes specified BLOCK 1 and BLOCK 2 (Appendix 2 Fig 5 Memory map: cTcpTokenizer
messages). For example, “data buffer size” in BLOCK 2 specifies size of “data buffers” in BLOCK 3. Attacker can overwrites “data buffer size” token in BLOCK 2 to a number larger/smaller than the actual size of the corresponding buffer in BLOCK 3. This would cause problem in subsequent parsing and shut down the listening thread. One way to do this is to encrypt hash of cTcpTokenizer
message before sending it.
cCryptoTcpTokenizer
:
- Current implementation does not support multicasting/broadcasting.
- No mechanism in place to acknowledge receipt of message.
- Message signing should be made available to plaintext messaging. For encrypted messages, message signing should be available as an optional parameter.
- Additional overload for
Send
method:Send(string sMessage)
Send(byte[] btMessage)
Quality issues
- More tests are required to fully evaluate performance characteristics of
cCryptoTcpTokenizer
. - Security review pending.
Appendix:
- Common Cryptographic Exceptions
- cTcpTokenizer
- cRSAWrap
- Disclaimer
Appendix 1: Common Cryptographic Exceptions
MSDN provides pretty good documentation on System.Security.Cryptography
namespace. The documentation can be located under subject: “Cryptographic Tasks”, “Cryptography Overview”. There’re a few issues that are not adequately elaborated:
- Common cryptographic exception: Bad Key, Bad Data, Bad Length
- Multi-block encryption in RSA encryption/decryption (Please refer to Appendix 3 cRSAWrap)
In this section, we’ll provide a brief discussion of common cryptographic exceptions.
- Solution Name: RSACryptoServiceProvider Demo
- Solution Directory: \cCryptoTcpTokenizer_package\RSACryptoServiceProvider Demo\
- Project Name: SimplestRSADemo
- Source code: SimplestRSADemo.cs
Demonstration:
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();
}
}
}
Appendix 2: cTcpTokenizer
Design objectives:
- Facilitate parsing/tokenizing of data retrieved from
NetworkStream
. - Simplify the process of sending messages composed of multiple tokens to target IP address.
- Thread-Safe operation.
- Solution Name: TcpTokenizer
- Solution Directory: \cCryptoTcpTokenizer_package\TcpTokenizer\
- Project Name: TcpTokenizer
- Source code: TcpTokenizer.cs
- Demo Project Name: TcpTokenizerDemo
- Demo Source code: TcpTokenizerDemo.cs
Demonstration
//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
}
Fig 5. Memory map: cTcpTokenizer
messages.
Fig 6. cTcpTokenizer
class
Appendix 3: cRSAWrap
- Design objectives: Simplify multi-block encryption/decryption (RSA) – It’s basically a wrapper around RSACryptoServiceProvider.
- Solution Name: RSACryptoServiceProvider Demo
- Solution Directory: \cCryptoTcpTokenizer_package\RSACryptoServiceProvider Demo\
- Project Name: SimplestRSADemo
- Source code: SimplestRSADemo.cs
- Required namespace: System; System.Text; System.Security.Cryptography; nsRSAWrap;
Demonstration
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);
A Note On Multi-Block encryption/decryption:
1. Key size Vs Block Size
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;
CAUTION: Cryptographic exception ”Bad Data” will be thrown if btPlaintextToken’s buffer size is set incorrectly.
3. RSACryptoServiceProvider.Decrypt
btPlaintextToken = rsaReceiver.Decrypt(btEncryptedToken, false);
btEncryptedToken size: keySize;
btPlaintextToken size: blockSize;
CAUTION: Cryptographic exception ”Bad Data” will be thrown if btEncryptedToken’s buffer size is set incorrectly.
Appendix 4: Disclaimer
THIS SOFTWARE IS PROVIDED BY AUTHORS/CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS/CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.