Click here to Skip to main content
15,868,024 members
Articles / Programming Languages / C#
Article

CCryptoTokenizer

,
Rate me:
Please Sign up or sign in to vote.
4.50/5 (6 votes)
13 Nov 200312 min read 64.5K   2.4K   29   6
CCryptoTokenizer

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

  1. Application Notes
  2. Architecture
  3. Scalability and performance
  4. Further extension in subsequent releases
  5. References
  6. 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

CTcpTokenizer

Source: TcpTokenizer.cs

Namespace: nsTcpTokenizer

TcpTokenizer.dll

Supports sending/receiving/tokenizing messages TO/FROM network stream.

CRSAWrap

Source: RSAWrap.cs

Namespace: nsRSAWrap

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.

C#
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.
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
}
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:

  1. tkMessenger2 (Messenger2’s thread) connects to tkMessenger1
  2. 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).

  3. tkMessenger2 dispatches a request for tkMessenger1’s public key.
  4. tkMessenger1 dispatches a request for tkMessenger2’s public key.
  5. tkMessenger2 waits until tkMessenger1’s key is retrieved.

    When that happens, event cCryptoTcpTokenizer.dgTargetPublicKeyRetrieved is raised and corresponding handler OnMsg2PKRetrieved is invoked.

  6. tkMessenger1 waits until tkMessenger2’s key is retrieved.
  7. tkMessenger2 sends messages to tkMessenger1.
  8. tkMessenger1 sends messages to tkMessenger2.
  9. Threads Messenger1 and Messenger2 are kept alive for as long as bContinue==true:
    C#
    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 connecting tkMessenger1.m_receiver and tkMessenger2.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.

  10. Main(..) toggles bContinue to false 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):

Image 1

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 and cCryptoTcpTokenizer.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 calls ConnectTarget( ). Event evConnectBack 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” cCryptoTcpTokenizer.qIncomingMessages after this event is fired. “Message Queue” QIncomingMessages is of type “System.Queue”. “Messages” in the queue are of type “System.Queue”. Individual “tokens” in these “messages” are of type “System.object”.

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:

  • Toggle bSendEncrypted to true.
  • Call Send( )
EvConnectBack

Fired when one instance connects back to a remote instance.

Table 2 cCryptoTcpTokenizer events

To subscribe to cCryptoTcpTokenizer events:

C#
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:

  1. Make sure connection is established between this instance of cCryptoTcpTokenizer (Server A) and the remote instance (Server B). Check bIsConnected.
  2. Server A calls AsynRequestTargetPublicKey( ). Internally, the method loads m_sender.bufferQueue with key request and send it to remote instance (Server B):
    C#
    string sREQ = "REQ_RSA_PUBKEY"; //That’s the message label.
    m_sender.bufferQueue.Enqueue(sREQ);
    m_sender.Send();
  3. Remote instance (Server B) receives this message. Internally, m_receiver of remote instance (Server B) raises an cTcpTokenizer event “evbufferLoaded” which triggers ProcessIncomingMsg( ). 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 to DispatchRsaPK( ).
  4. DispatchRsaPK( ) is responsible for sending its public key to the target very much the same way it receives the key request. Here’s how:
    C#
    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.
  5. The response is received by Server A. As with all other kinds of message, “REQ_RSA_PUBKEY_RESULT” messages are first retrieved by ProcessIncomingMsg( ). The message type is identified, and execution is then routed to SetRecipientRsaPK( ).
  6. SetRecipientRsaPK( ) then stores target’s (Server B) public key in protected field: m_rsaRecipientPublicKey. m_bIsRecipientPKRetrieved is toggled to true internally.
  7. Server A is now ready to send message to Server B in encrypted mode. Toggle cCryptoTcpTokenizer.bSendEncrypted to true before calling Send( ) 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, then SendMessageEncrypted( ) is invoked.

    If bSendEncrypted==false, then SendMessagePlaintext( ) is invoked.

  • Internally, both SendMessageEncrypted( ) and SendMessagePlaintext( ) packages the “message” in m_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:

Image 2

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

C#
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:

Image 3

Fig 3 Memory map: plaintext messages
  • Server B receives the message. m_receiver raises event “evbufferLoaded“, which triggers cCryptoTcpTokenizer.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 and ProcessPlaintext - ProcessPlaintext simply deposits the message in cCryptoTcpTokenizer.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

Image 4

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:

Image 5

Table 4 Time elapsed between first message (plaintext) sent and last message received (unit: sec)

Encrypted messages:

Image 6
Table 5 Time elapsed between first message (encrypted) sent and last message received (unit: sec)

Chart 1

Image 7

Chart 2

Image 8

Chart 3

Image 9

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.
Image 10
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:

  1. Common Cryptographic Exceptions
  2. cTcpTokenizer
  3. cRSAWrap
  4. 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:

  1. Common cryptographic exception: Bad Key, Bad Data, Bad Length
  2. 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:

C#
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

C#
//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.

Image 11


Fig 6. cTcpTokenizer class

Image 12


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

C#
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

C#
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();

... more code ...

keySize = rsaSender.KeySize/8;
blockSize = keySize -11;

2. RSACryptoServiceProvider.Encrypt

C#
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

C#
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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Hong Kong Hong Kong
Me too, I'm a developer.

Written By
Web Developer
Hong Kong Hong Kong
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionWhy don't you adhere to .NET naming standards? Pin
Anonymous20-Nov-03 4:48
Anonymous20-Nov-03 4:48 
For instance, why the 'C' in front of class names?
AnswerRe: Why don't you adhere to .NET naming standards? Pin
Norman Fung20-Nov-03 23:29
Norman Fung20-Nov-03 23:29 
GeneralPDF page Pin
Norman Fung18-Nov-03 15:45
Norman Fung18-Nov-03 15:45 
GeneralMan in the middle attack vulnerability Pin
Choppa18-Nov-03 2:55
Choppa18-Nov-03 2:55 
GeneralRe: Man in the middle attack vulnerability Pin
Norman Fung18-Nov-03 15:37
Norman Fung18-Nov-03 15:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.