Friday, April 18, 2014

Implementing SymmetricAlgorithm

In a previous post I answered my own question about how difficult it was to formally implement HashAlgorithm. It turns out you just override a handful of members and you have a class that behaves like the standard classes (MD5, SHA1, etc).

Then I wondered how difficult it was to implement SymmetricAlgorithm and create a class that behaves just like the standard classes (Aes, DES, etc). It turns out to be more tricky and 'fiddly' because there are many interrelated properties and virtual members that are difficult to understand clearly even after reading the MSDN documentation. You also need to implement three classes: the provider and the pair of encrypt/decrypt transform classes.

I set myself the challenge of writing a simple but completely functional set of symmetric algorithm classes that behave just like the standard ones. The internal algorithm is the childish technique of encrypting and decrypting 8-byte blocks by XORing them with a sequence of pseudo-random blocks in either ECB or CBC modes (this algorithm is otherwise known as Snake Oil or Craptography)..

The resulting project, code and test data can be downloaded from the repository. The code is well documented, but there are some points worth clarifying.

CanTransformMultipleBlocks

If you set this property to true, then the transform methods may be passed long buffers which are a multiple of the block size. It is your responsibility to process the blocks sensibly. You may set this property true if you know you can process many blocks more efficiently. For the demonstration code I set this property false and only transform single blocks.

Decrypting the final block

During decryption we need to know when the final block is being transformed so the padding can be stripped from it. Unfortunately, the TransformBlock method does not distinguish the blocks and there is no way of telling which block is the final one. However, TransformFinalBlock is always the last method to run and it's always passed input length zero because there are no trailing bytes. A workaround is to delay writing decrypted blocks until the next one is read, so writing "lags" one call behind the reading. So when we hit the TransformFinalBlock call we know that the "lagged" block is the one to be padding stripped. This "lag" workaround is a nuisance as it clutters the decryption code a bit.

ADDENDUM #1

A few days after finishing the sample code I thought I'd replace the childish pseudo-random encryption with something also simple but more realistic. I searched for TEA (Tiny Encryption Algorithm) and discovered that someone had practically duplicated my code using the XTEA algorithm, except their version lacked C# style, lacked some Dispose calls, and they didn't support CBC mode. See: eTutorials 14.4 Extending the .NET Framework.

ADDENDUM #2

Rather than use one of the TEA variants as the encryption algorithm I decided to use a pseudo-random sequence of 64-bit numbers. Such sequences produce nice random looking ciphertext for my demonstration, but it is well documented that they are worthless for serious cryptography. I didn't actually have a 64-bit random number generator handy, so I took this incredibly simple and effective unsigned 32-bit one-liner from Numerical Recipes in C, Chapter 7:

uint s = seed value;
s = s * 1664525u + 1013904223u;


And I turned it into this:

ulong s = seed value;
s = s * 2770643476691ul + 4354685564936844689ul;


The new 64-bit multiplier is a prime that is near the square of the 32-bit multiplier. The new 64-bit addend is a prime near (Sqrt[5]-2) * 2^64 as suggested by Knuth. I have no theoretical proof that these numbers are good, but running 5 billion iterations through the RandPlot application shows no lattice structure or recycling. Based on this heuristic proof only, I feel that this is a great choice when speed, reliability and simplicity are desirable for random number generation. The low-orders bits of the sequence are of course highly correlated, which could be partly cured by a Bayes-Durham shuffle, but I didn't bother with it for this sample.

I found the prime numbers thanks to Mathematica's RandomPrime function.

No comments:

Post a Comment