Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / Visual Basic

The Amazingly Forgotten 2-Way .NET Rijndael CryptoStream

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
29 Aug 2009GPL37 min read 51.4K   452   35   5
How To Do Precise 2-Way Rijndael CryptoStream Communication
2W

Introduction

Every systems engineer needs tools to serve his daily needs at work. These tools do not necessarily come from vendors, they can be done by hand, specially when there is a need for custom ones.

One of the tools I needed lately is a TCP client/server application, with the server part running as a Windows service and securely accepting connections from the client part, while serving multiple purposes.

Well, the subject itself is not new. How many TCP client/server examples are there around on the web? Countless! It was really easy given that I already have the TcpListener and TcpClient in hand. Once I grabbed the idea, I was able to come out with some sort of an Echo server in less than an hour (experienced programmers could have done that in minutes :)), but that would never be used. Why? Not because it lacks functionality, but just because it lacks SECURITY. Yes, Telnet like programs are no option, specially when you will run them on a production server.

So, I went for the obviously obvious option: cryptography with the .NET CryptoStream. Or, did I? :)

Background

The .NET CryptoStream is easy to be used once you figure out how to do the ICryptoTransform operation that will be used with the CryptoStream, that is the cryptography Symmetric Key in either of its modes; Read or Write. But, there is a chronic problem with the CryptoStream, which I discovered with pain as a cryptography newbie: the CryptoStream will ultimately be unable to distinguish when a TCP message or block of data has ended (or that's at least what everybody is saying around the web and I'll get to that later ;)). Yes, as an object in an OOP-based framework, it inherits properties and methods of other "normal" streams, like .Peek, .Seek, .Length, and .EndOfStream. Dare to use any of these, and you will get a nasty and painful exception telling you that the CryptoStream does not support these methods/properties!

This is because the CryptoStream is performing its work at a higher level than that of the Socket. It builds on top of the NetworkStream that is created on TcpClient connection. You can Google this exact phrase "cryptostream does not support seek" and browse the few first results. Most are suggesting to do byte count before and after encryption/decryption, by recording the length of data being sent into the few first bytes before data itself.

That actually works, but why does the .Read operation fail to complete normally unless the sending party closes the connection on its side? When you translate the length-bytes and then try to read the now pre-determined byte-count CryptoStream.Read seems to be working like forever, before it eventually throws a timeout exception. One example used that technique of length-bytes and I wasted my time reading its code while it was "inventing" an object of its own for the TCP message just to find that it eventually closes the connection on the sending side so as to force it be read on the receiving side. Hilarious. Yes, closing the stream on the sending side automatically signals the receiving side to read whatever bytes are left in queue, thus fixes the CryptoStream.Read problem. This only creates one-way cryptographic communication; definitely not the way networking was designed to work. One of the people complaining about the same problem noticed that the data actually can be sent without closing the connection, only if the .Write function is committed twice. The reply to that observation - as pessimistic as it was - is what evoked me do what I've been refraining to do for long: count the bytes as they are sent-and-read in real time. Guess what?

Lucky Number 32 (not sleven:D)

Just as I was testing my proto-type code, I used a "password" of 777 bytes (oops, security breach :)), some flag bytes, and the rest was randomly generated bytes to try push my data onto the wire. I was .Write'ing these bytes each at a time, and as I was testing each time, I found the reading of the random bytes sent stopping at index number 718, which would make it byte number 719! When I got the same result each time, I counted the total bytes, it was 1504. I rushed into many conclusions, sorryfully, but it came out to be much simpler. As I'm using the Rijndael algorithm, and specifying the .BlockSize of the Rijndael provider that the ICrytoTransform uses as the maximum allowable 256 bits = 32 bytes, the CryptoStream was reading the data in .BlockSize, all the time.

VB.NET
Dim x As New RijndaelManaged 'initialize the AES CryptoProvider
        x.BlockSize = 256 'maximum length
        x.KeySize = 256 'maximum length
        x.Mode = CipherMode.CBC 'most secure cipher mode
        x.Padding = PaddingMode.ISO10126 'most secure padding with random bytes
        Dim key() As Byte = New Rfc2898DeriveBytes("keyPass", ASCII.GetBytes_
	("saltsaltsalt".ToCharArray)).GetBytes(32) 'generate 32 bytes for key
        Dim iv() As Byte = New Rfc2898DeriveBytes("ivCryptic", ASCII.GetBytes_
	("moreSaltmoreSalt".ToCharArray)).GetBytes(32) 'generate 32 bytes for iv

The trick here was that you must *at least* append an extra "dummy block" in .BlockSize for the ICryptoTransform to sense that there is another block. If I went for 128 bits BlockSize, the extra data required to push .Read operation would have been 16 bytes, instead.

VB.NET
Dim x As New RijndaelManaged 'initialize the AES CryptoProvider
        x.BlockSize = 128 '16 bytes
        x.KeySize = 128 '16 bytes, as it must match the BlockSize
        x.Mode = CipherMode.CBC 'most secure cipher mode
        x.Padding = PaddingMode.ISO10126 'most secure padding with random bytes
        Dim key() As Byte = New Rfc2898DeriveBytes("keyPass", ASCII.GetBytes
	("saltsaltsalt".ToCharArray)).GetBytes(16) 'generate 16 bytes for key
        Dim iv() As Byte = New Rfc2898DeriveBytes("ivCryptic", ASCII.GetBytes
	("moreSaltmoreSalt".ToCharArray)).GetBytes(16) 'generate 16 bytes for iv

Unfortunately, there is not much reference for the CryptoStream, and all I had was web resources, most of which have NO EXACT EXPLANATION to the way CryptoStream.Read function is behaving.

So, finally I was able to do 2-Way CryptoStream communication, knowing exactly when my sent data will actually be read on the receiving side.. Finally :)

Article Code

The code I made for this article is split into 2 applications: dataSender.exe and dataReceiver.exe. Using those demo apps is very easy, you start both apps, define in both apps' textboxes how many bytes of data will be written/read, then finally start sending the bytes. To guarantee the delivery of a non-32-multiple chunk of data, just add extra 64 bytes to the count, and you are done:) No-nag! To help you use the same code over the network, I've included a random byte generator app, that will produce lines of text into a file to the same directory it runs in, each line will be in the form (Chr(i) + Chr(i) + ...) for the count of 32 concatenated Chars. You take those and paste them in the appropriate commented line within the code, and do necessary changes.

The code is also heavily commented. Just beware that comments always come after code, not the regular opposite. I extremely hate comments that show before code; it is anti-logic. Your mind parses a code line then demands explanation, not the opposite!! You will also find the code eye-friendly: no blank lines that would force you to re-parse whole blocks just to re-remember what you were reading!!!! The only pitfall: this technique requires highlighting. So, don't use Notepad to read it. :D

You can download the latest and updated article code at http://admincraft.net.

Points of Interest

To guarantee delivery of your bytes over .NET CryptoStream with Rijndael algorithm, append extra dummy block of bytes in .BlockSize at the end. If the length of your data will produce float if divided by .BlockSize, then count the length of the last BlockSize-fragment of your data and append extra bytes until the count = (BlockSize x 2)

VB.NET
crypto.Write(ASCII.GetBytes("done!".ToCharArray), 0, 5) 'send the word "done!", 
							'only 5 bytes
Dim y As New Random 'random bytes generator
Array.Resize(rwBuffer, 59) 'resize buffer to the remaining length of 
			'(.BlockSize x2)(i.e. 64 - 5 = 59 bytes remaining)
y.NextBytes(rwBuffer) 'fill buffer with random bytes
crypto.Write(rwBuffer, 0, rwBuffer.Length) 	'write the extra 
					'remaining bytes to the wire

I also want to add that some people insist that the 2 functions .Flush and .FlushFinalBlock have a "healing" effect on the .Read function on the receiving side. This is absolutely non-sense, and if you use my demo apps you will surely know that CryptoStream.Write does not and will never need any flushing at all. All buffer data handed over to the .Write function gets written IMMEDIATELY. Plus, if - and only if - .FlushFinalBlock was going to have any effect on the .Read function on the receiving side - which I extremely doubt, it is already useless. Why? Simply because it can only be called ONCE - ONLY ONE TIME - during CryptoStream lifetime, after which you MUST close the stream. Otherwise? You will get an ugly exception that will force you to close it. So, still one-way communication. :)

Don't forget to vote if you like it. :)

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


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

Comments and Discussions

 
QuestionThanks Pin
IdahoEngr15-Apr-14 17:10
professionalIdahoEngr15-Apr-14 17:10 
GeneralPadding [modified] Pin
dorexxx19-Apr-10 19:08
dorexxx19-Apr-10 19:08 
GeneralRe: Padding [modified] Pin
_Khallaf22-Apr-10 23:08
_Khallaf22-Apr-10 23:08 
GeneralThanks! Exactly what I was looking for. Pin
di~v~inci1-Oct-09 5:44
di~v~inci1-Oct-09 5:44 
GeneralRe: Thanks! Exactly what I was looking for. Pin
_Khallaf2-Oct-09 8:22
_Khallaf2-Oct-09 8:22 

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.