Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Strong and Fast Data Encryption with the CAST-128 Algorithm

4.63/5 (13 votes)
27 May 2006CPOL7 min read 1   3.9K  
An effective implementation of the CAST-128 algorithm (ECB and CBC modes).

Sample Image - CastEncryptor_Image.png

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:

C#
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:

C#
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

C#
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.

C#
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.

C#
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.

C#
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.

C#
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.

C#
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.

C#
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.

C#
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.

C#
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 :-)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)