[ Team LiB ] |
14.4 Extending the .NET FrameworkIn this section, we extend the .NET Framework by implementing the Extended Tiny Encryption Algorithm (XTEA). We have selected XTEA because it is simple to express, allowing us to concentrate on the details of adding a new algorithm rather than the specifics of the algorithm itself. Most symmetric encryption algorithms rely on large lookup tables that speed up encryption, but these are not suitable for listing in this book. We have provided only a C# implementation of the XTEA algorithm. Like almost all encryption algorithms, XTEA relies on using bitwise shift operations on unsigned data types; this is not possible in Visual Basic .NET without creating additional support functions to compensate for the limited numeric support Visual Basic .NET provides. 14.4.1 The Extended Tiny Encryption Algorithm ExplainedThe Tiny Encryption Algorithm (TEA) was developed by David Wheeler and Roger Needham of Cambridge University, England. They designed TEA to be simple, quick, and easily expressed in a range of programming languages. Wheeler and Needham released the XTEA in response to a weakness discovered in the original algorithm. Since that time, further weaknesses have emerged with TEA, leading the designers to make more modifications (often referred to as XTEA). To complicate matters, poor documentation has made differing implementations incompatible. This confusion has restricted the adoption of XTEA, and it is not frequently used. There has been little analysis of the XTEA algorithm, and the security of the design is unknown.
14.4.2 Defining the Abstract ClassFirst, create an abstract class representing the XTEA algorithm. The abstract class extends the System.Security.Cryptography.SymmetricAlgorithm class and is the foundation for our managed code implementation: using System.Security.Cryptography; public abstract class XTEA : SymmetricAlgorithm { } You have created the abstract class to conform to the .NET class model, allowing you to add other XTEA implementations in the future. You can omit the abstract class, but we feel that the extra flexibility of this model is well worth the effort. 14.4.3 Defining the Implementation ClassThe SymmetricAlgorithm class has two roles: the first is to describe an algorithm and the second is to provide a mechanism for creating instances of ICryptoTransform to encrypt or decrypt data. Table 14-8 lists the SymmetricAlgorithm-protected fields, each of which, with the exception of LegalKeySizesValue and LegalBlockSizesValue, can also be get and set using a corresponding property; each property holds data that describes an aspect of the encryption algorithm configuration.
We define the XTEAManaged class as our implementation of the XTEA algorithm. The XTEA cipher operates on 64-bit blocks of data and uses a 128-bit secret key. Use the class constructor to set these values, implementing the LegalKeySizeValue and LegalBlockSizeValue fields: using System.Security.Cryptography; public class XTEAManaged : XTEA { public XTEAManaged( ) { // define the valid block and key sizes LegalKeySizesValue = new KeySizes[] {new KeySizes(128, 128, 0)}; LegalBlockSizesValue = new KeySizes[] {new KeySizes(64, 64, 0)}; } The GenerateKey and GenerateIV methods are marked as abstract in the SymmetricAlgorithm class; therefore, they must be included in the implementation class; these methods must create a random secret key or initialization vector that is subject to the design of the algorithm. Create a 128-bit secret key and a 64-bit IV using the random number generator; see Chapter 17 for more information about creating and managing keys. public override void GenerateIV( ) { // create the PRNG RandomNumberGenerator x_random = RandomNumberGenerator.Create( ); // create the data array that will hold the IV IVValue = new byte[8]; x_random.GetBytes(IVValue); } public override void GenerateKey( ) { // create the PRNG RandomNumberGenerator x_random = RandomNumberGenerator.Create( ); // create the data array that will hold the IV KeyValue = new byte[16]; x_random.GetBytes(KeyValue); } public override ICryptoTransform CreateDecryptor(byte[] p_key, byte[] p_iv) { return new XTEAManagedDecryptor(p_key); } public override ICryptoTransform CreateEncryptor(byte[] p_key, byte[] p_iv) { return new XTEAManagedEncryptor(p_key); } } The remaining methods create implementations of the ICryptoTransform interface, called XTEAManagedEncryptor and XTEAManagedDecryptor. Notice that you do not pass the p_iv parameter into the constructor for the ICryptoTranform implementations; this is because our XTEA implementation supports only the ECB cipher mode; therefore, you do not need to use an IV as a seed value for cipher function chaining. 14.4.4 Defining an Abstract TransformationWe have defined an abstract class that you used as the basis for the ICryptoTransform implementation classes. The main purpose of this class is to provide a common location for utility methods and ICryptoTransform interface members that are the same in both the encryption and decryption classes: using System; using System.Security.Cryptography; public abstract class XTEAManagedAbstractTransform : ICryptoTransform { protected uint[] o_key; // the key, expressed as a series of 32bit words public XTEAManagedAbstractTransform(byte[] p_key) { // convert the key from an array of bytes into // and array of unsigned 32-bit words, as required // by the XTEA specification o_key = ConvertBytesToUints(p_key, 0, p_key.Length); } The constructor simply converts the secret key from a byte array, as expressed by the .NET Framework, to an array of unsigned 32-bit integers as required by the XTEA algorithm; store the key as an instance array. The XTEA algorithm has been designed with unsigned 32-bit integers in mind, and you can use the ConvertBytesToUints and ConvertUintstoBytes methods in the encryption and decryption transformations to convert to and from the byte representation that the .NET Framework uses: protected uint[] ConvertBytesToUints(byte[] p_data, int p_offset, int p_count) { // allocate an array - each uint requires 4 bytes uint[] x_result = new uint[p_count/4]; // run through the data and create the unsigned ints from // the array of bytes for (int i = p_offset, j = 0; i < p_offset + p_count; i+=4, j++) { x_result[j] = BitConverter.ToUInt32(p_data, i); } // return the array of 32-bit unsigned ints return x_result; } protected byte[] ConvertUintstoBytes(uint[] p_data) { // convert the unit[] into a byte[] byte[] x_result = new byte[p_data.Length * 4]; // run through the data and create the bytes from // the array of unsigned ints for (int i = 0, j = 0; i < p_data.Length; i++, j+=4) { byte[] x_interim = BitConverter.GetBytes(p_data[i]); Array.Copy(x_interim, 0, x_result, j, x_interim.Length); } // return the array of 8-bit bytes return x_result; } The ICryptoTransform interface requires the following properties, which describe the algorithm used by the transformation. Some encryption algorithms (for example, Rijndael/AES) support a range of block and key sizes, and it can be useful to examine the settings for a specific transformation, especially if the SymmetricAlgorithm instance used to create it is not available. The XTEA algorithm accepts and produces 64-bit blocks of data, and so the InputBlockSize and OutputBlockSize properties both return "8", representing the number of bytes—note that unlike the SymmetricAlgorithm class, the ICryptoTransform interface expresses blocks in bytes rather than bits. The CryptoStream class uses the CanTransformMultipleBlocks property when breaking up data for processing. If this property returns true, then the implementation of the TransformBlock method will process data arrays that are multiples of InputBlockSize. The implementations of XTEA processes only 64-bit blocks, and for simplicity, return false from this method, indicating that CryptoStream should only request processing for data in 64-bit blocks and not multiples thereof. Set the CanReuseTransform property always to return false so that our XTEA transformation can be used only to process one set of data—to either encrypt a single plaintext or decrypt a single ciphertext: public bool CanReuseTransform { get { return false; } } public bool CanTransformMultipleBlocks { get { return false; } } public int InputBlockSize { get { return 8; } } public int OutputBlockSize { get { return 8; } } The remaining members of this class are included simply to comply with the interface requirements; the Dispose method is useful if your algorithm uses unmanaged code (for accessing specialized acceleration hardware or key readers), but the XTEA implementation is written using only managed code; therefore you have no need to use statements to clean up resources. The definitions for TransformBlock and TransformFinalBlock are abstract, because they represent the point of specialization for the encryption and decryption transformations: public virtual void Dispose( ) { // we use no unmanaged resources } public abstract int TransformBlock(byte[] p_buffer, int p_offset, int p_count, byte[] p_output, int p_output_offset); public abstract byte[] TransformFinalBlock(byte[] p_buffer, int p_offset, int p_count); } 14.4.5 Defining the Encryption TransformationThe XTEAManagedEncryptor class is instantiated by the XTEAManaged.CreateEncryptor method (see Section 14.4.2) and is used to transform blocks of plaintext into ciphertext: using System; using System.Security.Cryptography; public class XTEAManagedEncryptor : XTEAManagedAbstractTransform { public XTEAManagedEncryptor(byte[] p_key) : base(p_key) { // do nothing } The class constructor does nothing other than set the key in the base class for future use. The private XTEAEncrypt method is the encrypting version of the XTEA cipher function; this method converts a series of bytes to unsigned 32-bit integers, encrypts them, and then converts the results back into a byte array. We have listed the XTEAEncrypt method for completeness, but we will treat the working of the XTEA cipher as a black box and will not explain the way that the cipher works: private byte[] XTEAEncrypt(byte[] p_data, int p_offset, int p_count) { // defintions that are used to create the cipher text uint x_sum = 0, x_delta = 0x9e3779b9, x_count = 32; // convert the plaintext data into 32 bit words uint[] x_words = ConvertBytesToUints(p_data, p_offset, p_count); // main part of the XTEA Cipher while(x_count-->0) { x_words[0]+= (x_words[1]<<4 ^ x_words[1]>>5) + x_words[1] ^ x_sum + o_key[x_sum&3]; x_sum += x_delta; x_words[1]+= (x_words[0]<<4 ^ x_words[0]>>5) + x_words[0] ^ x_sum + o_key[x_sum>>11 & 3]; } // convert the cipher block into bytes are return the result return ConvertUintstoBytes(x_words); } The CryptoStream class uses the TransformBlock method to encrypt blocks of data that are the same size as the return value of the InputBlockSize property—for XTEA this means that you will process eight bytes of data at a time. If the CanTransformMultipleBlocks property returns true, then CryptoStream may request that the transformation encrypt bytes in multiples of InputBlockSize, but will never request that the TransformBlock method process a partial data block. The implementation only transforms single data blocks; therefore, you work on the basis that CryptoStream will only request a transformation for eight bytes, which is the input for the XTEAEncrypt cipher function: public override int TransformBlock(byte[] p_buffer, int p_offset, int p_count, byte[] p_output, int p_output_offset) { // get the cipher text for the block byte[] x_ciphertext = XTEAEncrypt(p_buffer, p_offset, p_count); // copy the ciphertext into the output buffer Array.Copy(x_ciphertext, 0, p_output, p_output_offset, x_ciphertext.Length); // return the number of bytes written out return x_ciphertext.Length; } The CryptoStream class calls the TransformFinalBlock after the TransformBlock method has processed all of the complete data blocks. This method allows you to pad out any partial data and is called even if there is no partial data to process (in which case the value of p_count is 0). The XTEA algorithm requires padding (because the cipher function works only on complete 64-bit blocks), so use "PKCS#7" padding.
public override byte[] TransformFinalBlock(byte[] p_buffer, int p_offset, int p_count) { // create an array which is the size of an input block byte[] x_result = new byte[InputBlockSize]; // copy in the data from the buffer to the array Array.Copy(p_buffer, p_offset, x_result, 0, p_count); // work out the number of bytes we need to add to the // array to ensure that we have a complete block int x_shortfall = InputBlockSize - ((p_count - p_offset)); // pad out the data by adding bytes for (int i = p_count - p_offset; i < x_result.Length; i++) { x_result[i] = (byte)x_shortfall; } // encrypt and return the data block return XTEAEncrypt(x_result, 0, x_result.Length); } } The effect of padding out partial data blocks is that the ciphertext you produce can be longer than the plaintext, most notably, add a complete block of padding (8 bytes) when the plaintext contains only full data blocks. 14.4.6 Defining the Decryption TransformationThe XTEAManagedDecryptor class is instantiated by the XTEAManaged.CreateDecryptor method (see Section 14.4.3) and is used to transform blocks of ciphertext into plaintext, a task complicated by the way that we pad partial blocks of data during encryption. This padding strategy means that you will have to decrypt complete data blocks only, but you have to process the final block to remove the padding. The problem is that the CryptoStream class will call the TransformFinalBlock method only after the TransformBlock method has processed all of the data blocks. The solution to this problem is to hold onto each block of plaintext until the next time that the TransformBlock or TransformFinalBlock methods are called. You know that you have retained the last data block if the CryptoStream has invoked the TransformFinalBlock method and you can safely remove the padding: using System; public class XTEAManagedDecryptor : XTEAManagedAbstractTransform { private byte[] o_data_block; public XTEAManagedDecryptor(byte[] p_key) : base(p_key) { // initalize the data block o_data_block = new byte[0]; } private byte[] XTEADecrypt(byte[] p_data, int p_offset, int p_count) { // defintions that are used to restore the plaintext text uint x_count = 32, x_sum = 0xC6EF3720, x_delta = 0x9E3779B9; // convert the data into 32 bit words uint[] x_words = ConvertBytesToUints(p_data, p_offset, p_count); // main part of the XTEA Cipher while(x_count-- > 0) { x_words[1]-= (x_words[0]<<4 ^ x_words[0]>>5) + x_words[0] ^ x_sum + o_key[x_sum>>11 & 3]; x_sum -= x_delta; x_words[0]-= (x_words[1]<<4 ^ x_words[1]>>5) + x_words[1] ^ x_sum + o_key[x_sum&3]; } // convert the unit[] into a byte[] return ConvertUintstoBytes(x_words); } The class constructor sets the key in the base class and initializes the byte array that you use to hold back plaintext blocks. The private XTEADecrypt method is the decryption version of the XTEA cipher function; this method converts a series of bytes to unsigned 32-bit integers, decrypts them and then converts the results back into a byte array. The TransformBlock implementation decrypts the ciphertext block and assigns the result to the o_data_block field. Write any data held from previous invocations to the output buffer: public override int TransformBlock(byte[] p_buffer, int p_offset, int p_count, byte[] p_output, int p_output_offset) { // define the number of data bytes we copied int x_result = 0; if (o_data_block.Length > 0) { // there is some data from a previous call - write it out Array.Copy(o_data_block, 0, p_output, p_output_offset, o_data_block.Length); x_result = o_data_block.Length; } // decrypt the ciphertext block we are bring passed and store it o_data_block = XTEADecrypt(p_buffer, p_offset, p_count); // return the number of bytes that we have written // to the output buffer return x_result; } You know that the "held" data is the final plaintext block when the TransformFinalBlock method is invoked. Remove the PKCS #7 padding and copy the rest of the data into a new byte array, which returns as the method result, completing our implementation of the XTEA cipher: public override byte[] TransformFinalBlock(byte[] p_buffer, int p_offset, int p_count) { // get the value of the last byte, which tells us // how many padding bytes there are int x_padding_count = (int)o_data_block[o_data_block.Length -1]; // create the shortened data block byte[] x_final_block = new byte[o_data_block.Length - x_padding_count]; // copy in the data from the block Array.Copy(o_data_block, 0, x_final_block, 0, x_final_block.Length); // return the final plaintext block return x_final_block; } } |
[ Team LiB ] |