Introduction
The .NET Framework defines several good symmetric encryption algorithms: DES, Rijndael, and so on. For 'block work' (encrypting a file, the contents of a text box and the like), these algorithms are excellent. However when manipulating streams of unknown length, such as a TCP socket, they have a major drawback: they require data in multibyte blocks. If you require 8 bytes and you have only received 6 so far, you can't provide any information to the data consumer at all.
For such a stream based environment, an algorithm which takes each byte by itself and produces an encoded/decoded byte is useful. Because there is less linkage between bytes, such a method is cryptographically weaker (so I don't recommend running your online bank this way!), but for amateur use all that is really required is that a simple look at the transferred data reveals nothing.
Like all symmetric algorithms, the security is completely lost if the key is not kept secure. You should always transmit keys for important secure channels in a secure way, either by both parties knowing the key through offline sources or by a public key encrypted exchange process. In the new version of my Sockets library which uses this encryption method (link pending), the key exchange uses RSA, which is easy to do in .NET.
Use
The algorithm is coded as a pair of classes implementing the ICryptoTransform
interface. They are in the RedCorona.Cryptography
namespace. A simple example of how to use them would be:
void EncryptionTest(string text, byte[] key){
byte[] plain = Encoding.UTF8.GetBytes(text);
BaseCrypto enc = new SimpleEncryptor(key), dec = new SimpleDecryptor(key);
byte[] cipher = enc.TransformFinalBlock(plain, 0, plain.Length);
byte[] plain2 = dec.TransformFinalBlock(cipher, 0, cipher.Length);
}
Note that there is no inbuilt checking; if you attempt to decrypt bad data, or decrypt with the wrong key, you will generate nonsense results. I recommend that you insert some form of checksum or identifying bytes into your input so you can detect when decryption is going wrong.
The Algorithm
Encryption of an individual byte takes advantage of the useful fact that 257 is a prime number, and thus ([1..256]×key) mod 257 is a one-to-one (and therefore reversible) mapping for any key in [1..256]. Multiplication in mod space is a good way to generate well dispersed cipher-data. The simplest version of the algorithm would be:
cipher-byte: (((plain-byte+1)×(key-byte+1)) mod 257)-1
I will call this operation mul257, hence this simplistic algorithm would be:
cipher-byte: mul257(plain-byte, key-byte)
However this is vulnerable to the plain text containing zeroes at known locations, which will be quite common in computer data. (Zeroes – which become 1s for the multiplication – result in the cipher-text being equal to the key, so if the location of plain-text zeroes is known, parts of the key can be determined trivially.) Thus the version as implemented is:
cipher-byte: mul257(plain-byte+key-byte, key-byte)
Now the cipher-text is equal to the key when byte(plain-byte+key-byte) is zero, which cannot be known without knowing the key. Decryption is simply:
plain-byte: mul257(cipher-byte, key-byte')-key-byte
... where x' is the 257-complement of x, i.e. xx' mod 257 = 1. (These numbers are stored in a lookup table.)
Obviously a one-byte key is trivial to break by brute force and is not sufficient. Thus my algorithm uses a 'rolling cipher'; rather like the German Enigma machine, the key byte changes each time a new byte is encoded. The 'encryption key' which you pass to the class is a byte vector of arbitrary length. Each time a new byte is encrypted, after the encryption has been done the key byte is modified to become:
new-key-byte: mul257(mul257(key-byte, keyvec[keypos++]), cipher-byte+plain-byte)
Using the cipher-byte produces a better dispersion of keys than just using the plain byte, but the plain byte must be included for this multiplication to have any security value as the cipher byte is (obviously) available to a cracker. The key vector wraps once the end is reached. An initial key byte is generated as the sum (mod 256) of the key vector, thus changing any byte of the key, even if it's not used at all in the encryption, produces a completely different byte stream.
Because the encryption depends on earlier characters, this algorithm is not suitable for use when some of the data may be lost or out of sequence. That is, the value of encrypt(plaintext)
will be different depending on what has been passed to encrypt
in the past.
However, because it processes each byte in turn it is suitable where the data may be unpredictably broken up, as long as it is in sequence. That is:
encrypt(a,b),encrypt(c) == encrypt(a),encrypt(b,c)
Code
Since the code is so small (3 KB), here it is in easily C&P'able form. Please remember to credit me and link back to this article if you use it!
using System;
using System.Security.Cryptography;
namespace RedCorona.Cryptography {
public abstract class BaseCrypto : ICryptoTransform {
public int InputBlockSize { get { return 1; } }
public int OutputBlockSize { get { return 1; } }
public bool CanTransformMultipleBlocks { get { return true; } }
public bool CanReuseTransform { get { return true; } }
protected byte[] key;
protected byte currentKey;
protected int done = 0, keyinx = 0;
protected BaseCrypto(byte[] key){
if(key.Length == 0) throw new ArgumentException("Must provide a key");
this.key = key;
currentKey = 0;
for(int i = 0; i < key.Length; i++) currentKey += key[i];
}
protected abstract byte DoByte(byte b);
public int TransformBlock(byte[] from, int frominx, int len, byte[] to, int toinx){
for(int i = 0; i < len; i++){
to[toinx + i] = DoByte(from[frominx + i]);
BumpKey();
}
return len;
}
public byte[] TransformFinalBlock(byte[] from, int frominx, int len){
byte[] to = new byte[len];
TransformBlock(from, frominx, len, to, 0);
return to;
}
protected void BumpKey(){
keyinx = (keyinx + 1) % key.Length;
currentKey = Multiply257(key[keyinx], currentKey);
}
protected static byte Multiply257(byte a, byte b){
return (byte)((((a + 1) * (b + 1)) % 257) - 1);
}
protected static byte[] complements = {
0,128,85,192,102,42,146,224,199,179,186,149,177,201,119,240,
120,99,229,89,48,221,189,74,71,88,237,100,194,59,198,248,
147,188,234,49,131,114,144,44,162,152,5,110,39,94,174,165,
20,35,125,172,96,118,242,178,247,225,60,29,58,227,101,252,
86,73,233,222,148,245,180,24,168,65,23,185,246,200,243,150,
164,209,95,204,126,2,64,183,25,19,208,175,151,215,45,82,
52,138,134,17,27,62,4,214,163,176,244,187,223,249,43,217,
115,123,37,112,133,158,53,14,16,157,139,113,219,50,84,254,
1,171,205,36,142,116,98,239,241,202,97,122,143,218,132,140,
38,212,6,32,68,11,79,92,41,251,193,228,238,121,117,203,
173,210,40,104,80,47,236,230,72,191,253,129,51,160,46,91,
105,12,55,9,70,232,190,87,231,75,10,107,33,22,182,169,
3,154,28,197,226,195,30,8,77,13,137,159,83,130,220,235,
90,81,161,216,145,250,103,93,211,111,141,124,206,21,67,108,
7,57,196,61,155,18,167,184,181,66,34,207,166,26,156,135,
15,136,54,78,106,69,76,56,31,109,213,153,63,170,127,255
};
protected static byte Complement257(byte b){ return complements[b]; }
public void Dispose(){}
}
public class SimpleEncryptor : BaseCrypto {
public SimpleEncryptor(byte[] key) : base(key) {}
protected override byte DoByte(byte b){
byte b2 = Multiply257((byte)(b+currentKey), currentKey);
currentKey = Multiply257((byte)(b+b2), currentKey);
return b2;
}
}
public class SimpleDecryptor : BaseCrypto {
public SimpleDecryptor(byte[] key) : base(key) {}
protected override byte DoByte(byte b){
byte b2 = (byte)(Multiply257(b, Complement257(currentKey)) - currentKey);
currentKey = Multiply257((byte)(b+b2), currentKey);
return b2;
}
}
}