Introduction
Yes, I know, the Rijndael now is the best and generally used (AES) encryption algorithm. Other algorithms are less common and non-standard. Using Rijndael with a 256-bit encryption key makes me sure that my data won't be broken and read by an unauthorized person in the next thousand of years or, more probably, next 1000000... years. But, what if I don't need to hide my data from everybody forever? It is quite enough to maintain my encrypted data within the next 50-60 years or so. To tell the truth, it is almost improbable that one day somebody will be able to break a message encrypted with a key of 128 bits in length if the whole encryption schema won't have a flaw. So, it is the same for most of the applications whether you use an algorithm with 256-bit key or 128-bit key. Both are practically unbreakable. Why don't I like the strongest ciphers? There are two main reasons: the performance, and the usability. The third reason is a little subjective. I prefer easy and comprehensible (for non-mathematicians) algorithms.
This article describes a class which allows to encrypt and decrypt data with the CAST-128 algorithm. This algorithm belongs to the class of encryption algorithms known as Feistel ciphers. It has good resistance to differential and other cryptanalyses, and possesses a number of other desirable cryptographic properties (such as an absence of weak and semi-weak keys). The author of this cipher is Carlisle Adams (Canada). See RFC 2144 for more information on this algorithm.
On the above picture, you can observe the demo application for the CastEncryptor assembly. As you can see, CAST-128 is three times faster than a AES-256 cipher on the .NET 2.0 platform. Also, CAST-128 is two times faster than the reduced version of the Rijndael algorithm working with a 128-bit key (AES-128). It is also much faster than the unmanaged implementation of the TripleDES algorithm (by the TripleDESCryptoServiceProvider
class). As for usability, I think it is easier to manipulate keys of 128 bits in length because they are more human readable and can be represented by a value of the System.Guid
type. Although it may not be fully correct cryptographically, you can generate new encryption keys with the Guid.NewGuid()
method, and the subsequent transformation to a byte array with the Guid.ToByteArray()
method. I don't like using text passwords (even hashed with SHA or RipeMD) as encryption keys because such a string contains little unpredictable information.
Using CastEncryptor
Let's see how to use this tool to encrypt some data. Here is the method that accepts binary array, generates an encryption key as a new GUID value, schedules an internal key, then generates an ordinary initial vector and uses all this information for encrypting the binary array in the CBC mode:
public byte[] EncryptMyData(byte[] plainData, out byte[] encryptionKey)
{
encryptionKey = Guid.NewGuid().ToByteArray();
int[] scheduledKey = AcedCast5.ScheduleKey(encryptionKey);
long iv = AcedCast5.GetOrdinaryIV(scheduledKey);
byte[] result = (byte[])plainData.Clone();
AcedCast5.EncryptCBC(scheduledKey, result, 0, result.Length, iv);
AcedCast5.ClearKey(scheduledKey);
return result;
}
We create a copy of the plainData
array before passing it into the EncryptCBC
method. This is necessary because this method encrypts data in-place. After use, we should clear the internal key (scheduledKey
) for more security. The following is the method for decrypting binary data with the specified decryption key:
public byte[] DecryptMyData(byte[] encryptedData, byte[] decryptionKey)
{
int[] scheduledKey = AcedCast5.ScheduleKey(decryptionKey);
long iv = AcedCast5.GetOrdinaryIV(scheduledKey);
byte[] result = (byte[])encryptedData.Clone();
AcedCast5.DecryptCBC(scheduledKey, result, 0, result.Length, iv);
AcedCast5.ClearKey(scheduledKey);
return result;
}
This implementation of the CAST-128 algorithm has some shortcomings, nevertheless, or rather distinctive features. First of all, it does not support the ICryptoTransform
interface. So, it can not be used with the CryptoStream
class. The reason for such incompatibility was an attempt to provide maximum performance. I think, instead of supporting the ICryptoTransform
interface, it would be better to create separate stream classes (for encryption and decryption, or writing and reading) which work the same way as the CryptoStream
class and use internally the AcedCast5
class. Though, I have felt no need for such stream classes yet.
This encryption engine supports the Cipher Block Chaining (CBC) mode. Here, there is another shortcoming. This implementation has no support for padding messages which have not enough bytes to fill the last block. The block size in the CAST-128 algorithm is 8 bytes. The length of the encrypted/decrypted range of the source byte array must be a multiple of the block size. For example, if the length of the plainData
array in the above code will not be divisible by 8, the EncryptCBC
method throws an ArgumentException
. The padding problem is imposed on the developer who uses the EncryptCBC
and DecryptCBC
methods, because I don't know the perfect solution to this problem.
AcedCast5 Class
public sealed class AcedCast5
This class provides methods for the encryption/decryption of binary data with the CAST-128 encryption algorithm. This implementation of the CAST-128 algorithm conforms to RFC 2144. The only supported encryption modes are ECB and CBC.
There is a restriction for the CBC mode: the length of the input array must be a multiple of the encryption block size (8 bytes).
The key size for this algorithm is 128 bits. The source 16-byte encryption/ decryption key (the keyBytes
argument) must be transformed (scheduled) into the internal 128-byte key, with the ScheduleKey
method. All the other methods, except ScheduleKey
, work with such an internal key. At the end of work, you should pass that internal key into the ClearKey
method to wipe it out for more safety.
In a multithreaded application, you should synchronize calls to the methods of this class.
public static int[] ScheduleKey(byte[] keyBytes)
This method creates and returns an internal key which should be used with other methods of this class. That key depends on the source 16-byte key passed in the keyBytes
argument. You should clear the internal key at the end of the encryption/decryption session, passing the key to the ClearKey
method.
public static void ScheduleKey(byte[] keyBytes, int[] key)
This method is the same as the previous method but allows to reuse an existent 128-byte internal key to avoid memory reallocation. In the key
parameter, you should pass an array of 32 values of the System.Int32
type.
public static void EncryptECB(int[] key, byte[] bytes, int offset)
This method encrypts with the CAST-128 algorithm, the eight bytes of the bytes
array, starting from the element with the offset
index. The internal key (see the ScheduleKey
method) must be passed in the key
argument.
public static void DecryptECB(int[] key, byte[] bytes, int offset)
This method decrypts with the CAST-128 algorithm, the 8 bytes of the bytes
array, starting from the element with the offset
index. The internal key (see the ScheduleKey
method) must be passed in the key
argument.
public static long GetOrdinaryIV(int[] key)
This method generates an ordinary initial vector for an encryption or decryption process, with the specified key
. This method encrypts an empty (filled with zeros) block with the current key. The result can be used as an initial vector. This is more secure than using a zero-filled initial vector.
public static long EncryptCBC(int[] key,
byte[] bytes, int offset, int length, long iv)
This method encrypts a fragment of the bytes
array of length
bytes in length, starting from the element with the offset
index. The value of the length
argument must be a multiple of 8. This method implements the CBC mode. The internal encryption key is passed in the key
parameter. Here, the iv
argument passes the initial vector. The method returns a new initial vector which can be used for encryption of the next data block.
public static long DecryptCBC(int[] key, byte[] bytes,
int offset, int length, long iv)
This method decrypts a fragment of the bytes
array of length
bytes in length, starting from the element with the offset
index. The value of the length
argument must be a multiple of 8. This method implements the CBC mode. The internal decryption key is passed in the key
parameter. Here, the iv
argument passes the initial vector. The method returns a new initial vector which can be used for decryption of the next data block.
public static void ClearKey(int[] key)
This method clears the internal key passed with the key
parameter. It is assumed that the key is generated using the ScheduleKey
method.
Conclusion
I hope you will find this article useful. It is quite possible that one day I will create a wrapper for the AcedCast5
class inherited from the Stream
class, or you may do that yourself :-)