The .NET Framework takes the same basic approach to defining symmetric algorithms as the one you saw in Chapter 13 for hashing algorithms; abstract classes extend the System.Security.Cryptography.SymmetricAlgorithm class for each of the supported algorithms. Individual implementations of the algorithms extend the abstract class, supporting the possibility of more than one implementation of an algorithm, as represented by Figure 14-11. Figure 14-11. The .NET Framework class hierarchy for symmetric encryption algorithms14.3.1 The SymmetricAlgorithm ClassThe SymmetricAlgorithm class allows you to configure an algorithm (select the block size, padding mode, etc.) and create instances of the classes that encrypt and decrypt data; this class, and the derived implementation classes, are not used to process data directly; see Section 14.3.4 for more details. Table 14-2 summarizes the public members of the SymmetricAlgorithm class.
14.3.2 Instantiating the AlgorithmYou can instantiate the implementation classes for symmetric algorithms in the same way as the hashing algorithms we examined in Chapter 13. The preferred way to create an instance is by using the Create method of the SymmetricAlgorithm class: # C# SymmetricAlgorithm x_alg = SymmetricAlgorithm.Create("RC2"); # Visual Basic .NET Dim x_alg As SymmetricAlgorithm = SymmetricAlgorithm.Create("RC2") The Create method instantiates an implementation class based on the value of the argument; Table 14-3 shows the list of supported argument strings and the implementation classes that they create. If no argument is supplied, the Create method will instantiate the RijndaelManaged class and return null (C#) or Nothing (Visual Basic .NET) if the argument is not one of those listed in Table 14-3. The system administrator configures the mapping between the string values and the instantiated classes. Algorithms can be instantiated directly, an approach that is useful if you wish to ensure that a specific implementation class is used or if you are using algorithms for which no mappings are available: # C# SymmetricAlgorithm x_alg = new RC2CryptoServiceProvider( ); # Visual Basic .NET Dim x_alg As SymmetricAlgorithm = New RC2CryptoServiceProvider( )
14.3.3 Configuring the AlgorithmThe public properties of the SymmetricAlgorithm class provide a mechanism that allows you to configure an algorithm before data is encrypted or decrypted. The following sections describe the configuration options and the impact that each option has. 14.3.3.1 Block and key sizesThe KeySize and BlockSize properties represent the size (in bits) of the secret key and the size of block processed by the cipher function. Inspect and change the current settings as follows: # C# SymmetricAlgorithm x_alg = SymmetricAlgorithm.Create("Rijndael"); // print out the current values Console.WriteLine("Block Size: {0}", x_alg.BlockSize); Console.WriteLine("Key Size: {0}", x_alg.KeySize); // change the values x_alg.BlockSize = 192; x_alg.KeySize = 128; # Visual Basic .NET Dim x_alg As SymmetricAlgorithm = SymmetricAlgorithm.Create("Rijndael") ' print out the current values Console.WriteLine("Block Size: {0}", x_alg.BlockSize) Console.WriteLine("Key Size: {0}", x_alg.KeySize) ' change the values x_alg.BlockSize = 192 x_alg.KeySize = 128 Each algorithm supports a different set of block and key sizes; refer to Table 14-1 for a summary of the sizes supported by the algorithms included in the .NET Framework. Inspect the valid sizes programmatically using the LegalKeySizes and LegalBlockSizes properties, which rely on the KeySizes structure, summarized in Table 14-4.
Both properties return an array of the KeySizes structure, where each element represents a range of keys. To determine the valid key lengths, begin with the smallest valid size and append multiples of the skip size until you reach the largest valid size; for example, if MinSize is 16 bits, SkipSize is 8 bits, and MaxSize is 32 bits, then the valid key lengths would be 16, 24, and 32 bits. The following code demonstrates how to determine all of the valid key lengths for a SymmetricAlgorithm instance: # C# // create the encryption algorithm instance SymmetricAlgorithm x_alg = SymmetricAlgorithm.Create("Rijndael"); // get the valid sizes for the secret key KeySizes[] x_size_ranges = x_alg.LegalKeySizes; // iterate through each KeyRange in the array foreach (KeySizes x_range in x_size_ranges) { // only iterate through if there is a range to process if (x_range.SkipSize > 0) { // calculate each valid size for (int i = x_range.MinSize; i <= x_range.MaxSize; i+= x_range.SkipSize) { Console.WriteLine("Valid Secret Key Size: {0}", i); } } else { // there is no range - only a single value Console.WriteLine("Valid Secret Key Size: {0}", x_range.MinSize); } } # Visual Basic .NET ' create the encryption algorithm instance Dim x_alg As SymmetricAlgorithm = SymmetricAlgorithm.Create("Rijndael") ' get the valid sizes for the secret key Dim x_size_ranges( ) As KeySizes = x_alg.LegalKeySizes ' iterate through each KeyRange in the array Dim x_range As KeySizes For Each x_range In x_size_ranges ' only iterate through if there is a range to process If x_range.SkipSize > 0 Then ' calculate each valid size Dim i As Integer For i = x_range.MinSize To x_range.MaxSize Step x_range.SkipSize Console.WriteLine("Valid Secret Key Size: {0}", i) Next Else ' there is no range - only a single value Console.WriteLine("Valid Secret Key Size: {0}", x_range.MinSize) End If Next 14.3.3.2 Cipher and padding modesThe .NET Framework supports the two padding modes that we described in Section 14.2.3 earlier in the chapter. A member of the System.Security.Cryptography.PaddingMode enumeration, as summarized in Table 14-5, represents each padding mode. The Padding property of the SymmetricAlgorithm class allows the padding mode to be determined and changed.
The .NET Framework supports block cipher modes that include those we describe in the "Cipher Modes" section of this chapter, and some additional variations. The members of the System.Security.Cryptography.CipherMode enumeration, listed in Table 14-6, represent each cipher mode. The Mode property of the SymmetricAlgorithm class configures the cipher mode; note that not all encryption algorithms support all of the cipher modes.
The following code example demonstrates how to inspect the padding and cipher modes: # C# // create the encryption algorithm instance SymmetricAlgorithm x_alg = SymmetricAlgorithm.Create("Rijndael"); // view the current settings Console.WriteLine("Padding Mode: {0}", x_alg.Padding); Console.WriteLine("Cipher Mode: {0}", x_alg.Mode); // change the padding and cipher modes x_alg.Padding = PaddingMode.Zeros; x_alg.Mode = CipherMode.ECB; # Visual Basic .NET ' create the encryption algorithm instance Dim x_alg As SymmetricAlgorithm = SymmetricAlgorithm.Create("Rijndael") ' view the current settings Console.WriteLine("Padding Mode: {0}", x_alg.Padding) Console.WriteLine("Cipher Mode: {0}", x_alg.Mode) ' change the padding and cipher modes x_alg.Padding = PaddingMode.Zeros x_alg.Mode = CipherMode.ECB 14.3.3.3 Keys and initialization vectors (IVs)The .NET Framework expresses secret keys and initialization vectors (IVs) as arrays of bytes. The Key and IV properties of the SymmetricAlgorithm class allow you to get and set the values. If you set values using the Key and IV properties, the SymmetricAlgorithm class will use them for encryption or decryption. If you wish to generate random values for the key and IV, simply get the values from the related properties, as illustrated in the following code fragment: # C# // create the encryption algorithm instance SymmetricAlgorithm x_alg = SymmetricAlgorithm.Create("DES"); // we are "getting" the value of the secret key, which // will lead the SymmetricAlgorithm class to create a // new random key byte[] x_secret_key = x_alg.Key; // we are "setting" the value of the secret key, which // will now be used for any subsequent encryption or // decryption operations x_alg.Key = new byte[] {0xD0, 0x8C, 0xD3, 0xEB, 0x10, 0x60, 0x41, 0x59}; # Visual Basic .NET ' create the encryption algorithm instance Dim x_alg As SymmetricAlgorithm = SymmetricAlgorithm.Create("DES") ' we are "getting" the value of the secret key, which ' will lead the SymmetricAlgorithm class to create a ' new random key Dim x_secret_key( ) As Byte = x_alg.Key ' we are "setting" the value of the secret key, which ' will now be used for any subsequent encryption or ' decryption operations x_alg.Key = New Byte( ) {&HD0, &H8C, &HD3, &HEB, &H10, &H60, &H41, &H59} You do not have to get the values of the key and IV explicitly to create random values; any encryption or decryption operation will lead to the SymetricAlgorithm implementation class creating new Key and IV values if they have not been set. If you are using randomly generated values, you must be sure to take note of the key and IV values used to encrypt the plaintext or you won't be able to decrypt the ciphertext. 14.3.4 Encrypting and Decrypting DataThe SymmetricAlgorithm class delegates the process of encrypting and decrypting data to the ICryptoTransform interface, which exposes the details of handling data in blocks. An instance of ICryptoTransform transforms plaintext to ciphertext or transforms ciphertext to plaintext; each ICryptoTransform is "one-way" and can be used only for the purpose for which it was created. The following statements demonstrate how to create transformations, using the CreateEncryptor and CreateDecryptor methods: # C# // create the encryption algorithm SymmetricAlgorithm x_alg = SymmetricAlgorithm.Create("Rijndael"); // create an ICryptoTransform that can be used to encrypt data ICryptoTransform x_encryptor = x_alg.CreateEncryptor( ); // create an ICryptoTransform that can be used to decrypt data ICryptoTransform x_decryptor = x_alg.CreateDecryptor( ); # Visual Basic .NET ' create the encryption algorithm Dim x_alg As SymmetricAlgorithm = SymmetricAlgorithm.Create("Rijndael") ' create an ICryptoTransform that can be used to encrypt data Dim x_encryptor As ICryptoTransform = x_alg.CreateEncryptor( ) ' create an ICryptoTransform that can be used to decrypt data Dim x_decryptor As ICryptoTransform = x_alg.CreateDecryptor( ) The transformations that these statements create will transform data using the key and IV from the SymmetricAlgorithm instance; there are overloaded forms of the CreateEncryptor and CreateDecryptor classes that accept byte arrays to specify particular key and IV values. Table 14-7 summarizes the members of the ICryptoTransform interface.
Instances of the ICryptoTransform interface are not useful on their own; the .NET Framework provides the CryptoStream companion class, which is the basis for using instances of ICryptoTransform. The CryptoStream class acts as a wrapper around a stream and automatically transforms blocks of data using an ICryptoTransform. The CryptoStream class transforms data read from a stream (for example, decrypting ciphertext stored in a file) or written to a stream (for example, encrypting programmatically generated data and storing the result in a file). Creating instances of CryptoStream requires a real stream, an ICryptoTransform, and a value from the CryptoStreamMode enumeration, which defines whether to transform the data as it is read from the stream (CryptoStreamMode.Read) or as it is written to the stream (CryptoStreamMode.Write). The CryptoStream class extends System.IO.Stream and exposes all of the functionality of the stream it is wrapped around; requests made to the CryptoStream Read or Write methods are serviced by the underlying stream, and then transformed using an ICryptoTransform. Figure 14-12 shows how the CryptoStream class transforms data read from a file stream. Figure 14-12. Using the CryptoStream class to transform dataThe following statements, Example 14-1, demonstrate how to use the CryptoStream class to encrypt data as it is written to a MemoryStream, which provides a stream that is backed by an in-memory byte array: Example 14-1. using the CryptoStream class to encrypt data in a stream# C# using System; using System.Security.Cryptography; using System.IO; using System.Text; class MemoryEncryptionExample { static void Main( ) { // define the message that we will encrypt string x_message = "Programming .NET Security"; // get the bytes representing the message byte[] x_plaintext = Encoding.Default.GetBytes(x_message); // create the memory stream MemoryStream x_memory_stream = new MemoryStream( ); // create the encryption algorithm SymmetricAlgorithm x_alg = SymmetricAlgorithm.Create("RC2"); // create an ICryptoTransform that can be used to encrypt data ICryptoTransform x_encryptor = x_alg.CreateEncryptor( ); // create the CryptoStream that ties together the FileStream and // the ICryptoTransform CryptoStream x_cryptostream = new CryptoStream(x_memory_stream, x_encryptor, CryptoStreamMode.Write); // write the plaintext out to the cryptostream x_cryptostream.Write(x_plaintext, 0, x_plaintext.Length); // close the CryptoStream x_cryptostream.Close( ); // get the ciphertext from the MemoryStream byte[] x_ciphertext = x_memory_stream.ToArray( ); // print out the cipher text bytes foreach (byte b in x_ciphertext) { Console.Write("{0:X2} ", b); } } } # Visual Basic .NET Imports System Imports System.Security.Cryptography Imports System.IO Imports System.Text Class MemoryEncryptionExample Shared Sub Main( ) ' define the message that we will encrypt Dim x_message As String = "Programming .NET Security" ' get the bytes representing the message Dim x_plaintext( ) As Byte = Encoding.Default.GetBytes(x_message) ' create the memory stream Dim x_memory_stream As MemoryStream = New MemoryStream( ) ' create the encryption algorithm Dim x_alg As SymmetricAlgorithm = SymmetricAlgorithm.Create("RC2") ' create an ICryptoTransform that can be used to encrypt data Dim x_encryptor As ICryptoTransform = x_alg.CreateEncryptor( ) ' create the CryptoStream that ties together the FileStream and ' the ICryptoTransform Dim x_cryptostream As CryptoStream = New CryptoStream(x_memory_stream, _ x_encryptor, _ CryptoStreamMode.Write) ' write the plaintext out to the cryptostream x_cryptostream.Write(x_plaintext, 0, x_plaintext.Length) ' close the CryptoStream x_cryptostream.Close( ) ' get the ciphertext from the MemoryStream Dim x_ciphertext( ) As Byte = x_memory_stream.ToArray( ) ' print out the cipher text bytes Dim b As Byte For Each b In x_ciphertext Console.Write("{0:X2} ", b) Next End Sub End Class This example provides the general model for encrypting and decrypting data using symmetric algorithms; substitute any stream in the example to write encrypted data using the CryptoStream class. Note that the .NET symmetric encryption support makes no special provision for handling data held in memory; the MemoryStream class provides the means to process in-memory data using streams. |