Flow Group SASFlow Group
L'erp des Processus Relationnels
Systeme d'Information – Audit Industriel
> Home > Expertise & Knowledge > Technical Articles

Articles


Encrypting with the Compact Framework
on a PocketPC using a "BlowFish" Stream

lire la version francophone Version française

Implementation
Limits
Some useful links...
Revision history...

While developing applications for PocketPC we were faced with the need to encrypt the data to be persisted onto the disk. In fact, because of its "mobile" nature, the PocketPC might be easily stolen.

We searched the web and found that Markus Hahn ported in c# the Blowfish algorithm, from Bruce Schneier. In order to use it easily in our applications, we implemented auround it a Stream derived class.

Implementation

Because we do not know what kind of stream we will require, our Blowfish stream will simply "adapt" a stream object parameter.

namespace FlowGroup.Crypto { ... public class BlowfishStream : Stream { ... private System.IO.Stream mStream; /// <summary> /// Construct a BlowfishStream that adapt the /// "stream" parameter in order to encrypt/decrypt /// the data /// </summary> /// <param name="stream">inner stream</param> /// <param name="key">key for the encryption</param> /// <param name="mode">Read or Write</param> public BlowfishStream(Stream stream, byte[] key, BlowfishStreamMode mode) { ... mStream = stream; ... } ... } }

The same stream object might be used for streams that are encypted or not, we decided to sign the stream. If the stream is not encrypted, all the calls are delegated to the parameter stream object.

public class BlowfishStream : Stream { static private readonly byte[] signature = { 5, 11, 14, 22, 6, 17, 15, 192}; private System.IO.Stream mStream; ... private bool mIsEncrypted = false; private byte[] mBuffer; private int mCount = 0; private byte mOffset = 0; ... public BlowfishStream(Stream stream, byte[] key, BlowfishStreamMode mode) { ... mStream = stream; mBuffer = new byte[256]; mMode = mode; switch(mode) { case BlowfishStreamMode.Read: mCount = (byte)mStream.Read(mBuffer, 0, 8); mOffset = 0; if(mCount == 8) { mIsEncrypted = true; while(mCount > 0) { mCount--; if(mBuffer[mCount] != signature[mCount]) { mCount = 8; mIsEncrypted = false; break; } // if } } break; case BlowfishStreamMode.Write: mIsEncrypted = true; mStream.Write(signature, 0, 8); break; } } ... public override long Seek(long offset, System.IO.SeekOrigin origin) { if(mIsEncrypted) throw new System.NotSupportedException(); return mStream.Seek(offset, origin); } public override void SetLength(long value) { if(mIsEncrypted) throw new NotSupportedException(); mStream.SetLength(value); } public override bool CanRead { get { return mIsEncrypted ? (mMode == BlowfishStreamMode.Read) : mStream.CanRead; } } public override bool CanSeek { get { return mIsEncrypted ? false : mStream.CanSeek; } } public override bool CanWrite { get { return mIsEncrypted ? (mMode == BlowfishStreamMode.Write) : mStream.CanWrite; } } public override long Length { get { if(mIsEncrypted) throw new System.NotSupportedException(); return mStream.Length; } } public override long Position { get { if(mIsEncrypted) throw new System.NotSupportedException(); return mStream.Position; } set { if(mIsEncrypted) throw new System.NotSupportedException(); mStream.Position = value; } }

The algorithm used 8 bytes blocks, so we have to use a buffer to read/write to and from the stream. So let's implement the read, write and flush functions...

public override void Flush() { if(!mIsEncrypted) { } else if(mMode == BlowfishStreamMode.Write) { if(mCount > 0) { int bound = ((mCount % 8) == 0) ? mCount : (((mCount / 8) + 1) * 8); mBlowfish.Encrypt(mBuffer, mBuffer, 0, 0, bound); mStream.WriteByte((byte)(mCount - 1)); mStream.Write(mBuffer, 0, bound); mCount = 0; mOffset = 0; } } mStream.Flush(); } public override int Read(byte[] buffer, int offset, int count) { if(mIsEncrypted) { int totalRead = 0; int read = 0; int bound; // while data are required, fill the internal buffer while(count > 0) { if(mCount == 0) { int nRead = 0; read = mStream.ReadByte(); if(read == -1) break; // the stream is empty mCount = read + 1; mOffset = 0; bound = ((mCount % 8) == 0) ? mCount : (((mCount / 8) + 1) * 8); read = mStream.Read(mBuffer, mOffset, bound); nRead += read; if(bound != read) { int nMaxCount = 256; while(nRead < bound) { read = mStream.Read(mBuffer, mOffset+nRead, bound-nRead); nRead += read; nMaxCount--; if(nMaxCount <= 0) throw new System.IO.IOException(); } } mBlowfish.Decrypt(mBuffer, mBuffer, mOffset, mOffset, bound); } buffer[offset++] = mBuffer[mOffset++]; count--; mCount--; totalRead++; } return totalRead; } else { int cbRead = 0; if(mCount > 0) { while((mOffset < 8) && (count > 0)) { buffer[offset++] = mBuffer[mOffset++]; count--; cbRead++; } if(mOffset == 8) { // the "wrong" signature has been transfered back // to the reader, so empty the buffer mCount = 0; mOffset = 0; } } cbRead += mStream.Read(buffer, offset, count); return cbRead; } } public override void Write(byte[] buffer, int offset, int count) { if(mIsEncrypted) { string str = System.Text.ASCIIEncoding.ASCII.GetString(buffer, 0, count); // while data are required, fill the internal buffer while(count > 0) { if(mCount == 256) { mStream.WriteByte(255); mBlowfish.Encrypt(mBuffer, mBuffer, 0, 0, 256); mStream.Write(mBuffer, 0, 256); mOffset = 0; mCount = 0; } mBuffer[mOffset++] = buffer[offset++]; count--; mCount++; } } else mStream.Write(buffer, offset, count); }

Limits

In order to simplify the code, the stream can be opened for reading or writing, but not both, and cannot be seeked.

Some useful links...

Revision history...

daterévision
19/12/2005 00:00The link to Markus Hahn website has been updated.
26/10/2004 00:00The link to Markus Hahn website has been updated.

all the informations here are provided as is, without any warranty of any kind.
© 2024 Flow Group SAS - Flow Group est une marque déposée de GL Conseil SA.