.NET Security and Cryptography
Microsoft initially addressed the issue of data privacy in 1996 with the introduction of the Win32 Cryptography API (CryptoAPI) in Windows NT. Although the CryptoAPI provided thorough support for cryptographic programming, it was very difficult to use. You had to know a great deal about cryptography to make sense out of the many parameters and the large number of APIs. Also, it was not object oriented, since it was a straight C language interface, and you typically had to call many functions to perform even the simplest of operations. Fortunately, the .NET Framework greatly simplifies our work by providing a very elegant set of classes in the System.Security.Cryptography namespace. The Main Cryptography Classes
We look only at asymmetric programming in this chapter; however, since this is our first opportunity to see the .NET Security Framework, we now briefly list the most generally important classes provided in the System.Security.Cryptography namespace. The following list contains the classes that are of principal importance, since they are the base classes for each of the main aspects of cryptographic programming. We do not consider any of the classes related to Code Access Security or Role-Based Security here. Security- related classes, which fall into the System.Security namespace, are covered in Chapters 7 and 8.
Since these classes reside in the System.Security.Cryptography namespace, you should remember to add the using statement for this namespace in your programs. The SymmetricAlgorithm Class
The .NET Framework classes that implement symmetric algorithms are derived from the abstract base class SymmetricAlgorithm . The SymmetricAlgorithm abstract class has several protected fields, which are not directly accessible to methods of nonderived classes. However, these protected fields can be accessed via public virtual properties that are implemented in concrete derived classes. For example, the protected int field BlockSizeValue can be accessed via the BlockSize public virtual property in a manner that is appropriate, depending on the actual derived class being used. In this way, an attempt to set the block size to a value that is illegal for a particular symmetric algorithm will throw a CryptographicException , depending on the actual algorithm (i.e., the particular derived class) being used. In each case the protected field and the corresponding public virtual property have the same datatype, and the names are identical except that the protected fields have the word Value appended to them. The public virtual properties defined in the SymmetricAlgorithm class are shown in Table 3-1. SymmetricAlgorithm has only one public constructor that takes no parameters. This constructor initializes the new instance with a randomly generated secret key. Of course, SymmetricAlgorithm also supports the standard methods Equals, Finalize, GetHashCode, ToString, GetType , and MemberwiseClone , which are defined in the base class Object . In addition to these, SymmetricAlgorithm supports the public methods shown in Table 3-2. SymmetricAlgorithm-Derived Classes
You will never work directly with an actual SymmetricAlgorithm object, since it is an abstract class, which cannot be instantiated . Instead, you will work with derived concrete classes that implement each of the public properties as well as the abstract and virtual methods of SymmetricAlgorithm in an algorithm-specific and polymorphic manner. Figure 3-8 shows the symmetric algorithm class hierarchy. Figure 3-8. The symmetric algorithm hierarchy.
As you can see from Figure 3-8, the following classes, which are also abstract, are derived from SymmetricAlgorithm . We will look at how to program with the concrete classes derived from these abstract classes shortly.
Table 3-1. Public Virtual Properties in the SymmetricAlgorithm Class
Table 3-2. Public Methods in the SymmetricAlgorithm Class
Each of these algorithm-specific abstract classes is further derived into concrete classes that implement each of the supported symmetric algorithms. The .NET Framework provides one such concrete class for each of these algorithms; however, CSPs may choose to implement their own implementation classes to enable proprietary enhancements, or to take advantage of custom encryption hardware, and so on. The public properties and certain of the public methods described previously for the SymmetricAlgorithm class are implemented in each case. For security reasons, each of these concrete classes is sealed, which means that they cannot be inherited from for further customization. The four concrete classes provided out of the box are
The SymmetricAlgorithms Example
In this section, we look at the SymmetricAlgorithms example program provided for this chapter. This example demonstrates how to encrypt and decrypt with each of the four SymmetricAlgorithms -derived concrete class. Figure 3-9 shows the SymmetricAlgorithms example being used to encrypt and decrypt a plaintext message. Figure 3-9. The SymmetricAlgorithms example program.
Now let's look at the code in the SymmetricAlgorithms example. This program is somewhat contrived in that it encrypts and then decrypts the data all within a single Form. More realistic scenarios would probably call for transmission or storage of ciphertext ; however, for convenience, the purpose of this example is to show the whole story all in one simple, compact form-based example program. To simulate the realistic scenario, five fields have been added to the Form -derived SymmetricAlgorithms class, which are used to communicate all the required information from the encryption method to the decryption method. These are fields in this example rather than data to be communicated between participants . In real life, the key, initialization vector, cipher mode, and padding mode would have to be previously agreed upon between the communicating parties. The cipher bytes would of course be sent via some communications channel. These fields are the byte arrays named Key, IV, Mode, Padding , and cipherbytes : //variables communicated from encrypt to decrypt byte[] Key; byte[] IV; CipherMode Mode; PaddingMode Padding; byte[] cipherbytes; When the form initially loads, the following method is called to handle the event. The Key and IV fields are initialized with new random key and IV byte arrays. The cipher mode and padding convention are established according to the initial state of the associated radio buttons . private void Form1_Load( object sender, System.EventArgs e) { //setup initial key, iv, mode, and padding GenIV(); GenKey(); EstablishMode(); EstablishPadding(); } There are several methods in the program (including GenIV and GenKey) that need to create a symmetric algorithm object. This functionality is encapsulated in the CreateSymmetricAlgorithm method. This method simply creates and returns a SymmetricAlgorithm -derived object according to the currently selected radio button choices of RC2, Rijndael, DES, and Triple DES. In each case the corresponding static Create method is called to create the desired object. SymmetricAlgorithm CreateSymmetricAlgorithm() { //create new instance of symmetric algorithm if (radioButtonRC2.Checked == true) return RC2.Create (); if (radioButtonRijndael.Checked == true) return Rijndael.Create (); if (radioButtonDES.Checked == true) return DES.Create (); if (radioButtonTripleDES.Checked == true) return TripleDES.Create (); return null; } The most important methods in the program are those that actually encrypt and decrypt data using the currently selected algorithm with the current values for key and initialization vector fields. These methods are actually handlers for button-click events, with the names buttonEncrypt_Click and buttonDecrypt_Click . Note that the CreateSymmetricAlgorithm method is called again in both the encryption and decryption methods. The MemoryStream and CryptoStream classes are also used here, which will be briefly described shortly. Here is the code for the encryption method followed by the decryption method. private void buttonEncrypt_Click ( object sender, System.EventArgs e) { //do UI stuff ... //establish symmetric algorithm SymmetricAlgorithm sa = CreateSymmetricAlgorithm(); //use current key and iv sa.Key = Key; sa.IV = IV; //use current mode and padding sa.Mode = Mode; sa.Padding = Padding; //establish crypto stream MemoryStream ms = new MemoryStream (); CryptoStream cs = new CryptoStream ( ms, sa.CreateEncryptor() , CryptoStreamMode.Write ); //write plaintext bytes to crypto stream byte[] plainbytes = Encoding.UTF8.GetBytes(textPlaintext.Text); cs. Write (plainbytes, 0, plainbytes.Length); cs.Close(); cipherbytes = ms.ToArray(); ms.Close(); //display ciphertext as text string ... //display ciphertext byte array in hex format ... //do UI stuff ... } private void buttonDecrypt_Click ( object sender, System.EventArgs e) { //establish symmetric algorithm SymmetricAlgorithm sa = CreateSymmetricAlgorithm(); //use current key and iv sa.Key = Key; sa.IV = IV; //use current mode and padding sa.Mode = Mode; sa.Padding = Padding; //establish crypto stream MemoryStream ms = new MemoryStream (cipherbytes); CryptoStream cs = new CryptoStream ( ms, sa.CreateDecryptor() , CryptoStreamMode.Read ); //read plaintext bytes from crypto stream byte[] plainbytes = new Byte[cipherbytes.Length]; cs. Read (plainbytes, 0, cipherbytes.Length); cs.Close(); ms.Close(); //display recovered plaintext ... //do UI stuff ... } Cryptographic Streams
The Common Language Runtime (CLR) supports a stream-oriented design for cryptographic operations. The class that encapsulates a cryptographic stream is named, not surprisingly, CryptoStream . Any cryptographic operations that provide a CryptoStream object can be connected with other CryptoStream objects. By chaining cryptographic streams together in this way, the output from the one object is passed directly into the input of the next , and no separate storage needs to be provided for the intermediate results. The CryptoStream is a handy class that allows you to read and write data via a cipher stream object just as you would perform simple data input/output on a file or socket stream object. The CryptoStream class can be instantiated for encryption (writing mode) or decryption (reading mode). Since our SymmetricAlgorithms example program uses an in-memory byte array to represent plaintext and ciphertext, the MemoryStream class is used to enable I/O operations to be performed on these in-memory buffers. Here is the constructor for CryptoStream : public CryptoStream ( Stream stream, ICryptoTransform transform, CryptoStreamMode mode ); By passing in a freshly created Stream object, an ICryptoTransform encryptor object associated with the desired symmetric encryption algorithm, and the CryptoStreamMode.Write value to indicate I/O direction, we effectively create a CryptoStream object that can encrypt all data that is written to it. MemoryStream ms = new MemoryStream (); CryptoStream cs = new CryptoStream ( ms, sa.CreateEncryptor(), CryptoStreamMode.Write); Once you have this output CryptoStream object established, you can write bytes to it, which are automatically encrypted by the ICryptoTransform object and written to the associated Stream object (which is a MemoryStream object in this example, but it could be a file or socket). cs. Write (plainbytes, 0, plainbytes.Length); cs.Close(); By passing in a MemoryStream object based on a specified ciphertext byte array, an ICryptoTransform decryptor object, and the CryptoStreamMode.Read mode, we effectively create a CryptoStream object that can decrypt all data read from it. MemoryStream ms = new MemoryStream (cipherbytes); CryptoStream cs = new CryptoStream ( ms, sa.CreateDecryptor(), CryptoStreamMode.Read); Once you have this input CryptoStream object established, you can read bytes from the Stream object, which are automatically decrypted by the ICryptoTransform object and placed into a byte array. cs.Read(plainbytes, 0, cipherbytes.Length); cs.Close(); When you run this example program, you may notice that certain combinations of cipher modes and padding conventions do not work with all symmetric algorithm choices. For example, if you choose the OFB cipher mode along with any of the four algorithm choices, you will see a CryptographicException being thrown, with the message Output feedback mode ( OFB ) is not supported by this implementation . This exception is shown in Figure 3-10. Figure 3-10. OFB is not supported by the current implementation.
Another example of this type of problem occurs when you try to use Rijndael with either Zeros or None for the padding value. This results in another CryptographicException with the message Input buffer contains insufficient data . This is shown in Figure 3-11. It is instructive to see these errors in an example program, but, obviously, these errors should be avoided in actual working programs. Figure 3-11. Rijndael is incompatible with padding set to Zeros or None.
Avoiding Weak Keys
There is a public static method of the DES and TripleDES classes named IsWeakKey , which takes a byte array containing a key as a parameter, and it returns a boolean result. As you can guess by its name , this method can be used to determine if a particular key is weak. As you can also guess, a weak key results in a cipher that is easy to break. A weak key has the following characteristic: If plaintext is encrypted with a weak key, then encrypting it again (rather than the normal decrypting ) with the same weak key returns the original plaintext. Because 3DES is based directly on DES, the keys that are weak with respect to DES are also weak when used with 3DES. There are no known weak keys for RC2 and Rijndael algorithms, and therefore, the RC2 and Rijndael classes do not support the IsWeakKey method. There have been four DES keys that have been found to be weak, and, obviously, these weak keys should be avoided. However, it turns out that this whole issue of weak keys is not really much to worry about, since picking a weak key at random is an extremely low probability. Since DES has a 56-bit key there are 2 56 keys available. The number of weak keys is only four, which is 2 2 . That means that the probability of getting a bad key is only 2 “54 . There are also 12 keys that are considered semi-weak, but that still leaves the probability of using one of these keys so low that we probably do not need to worry about it. In any case, most programs use the secret key that is generated automatically by the symmetric cipher object's constructor, which will never produce a weak key. If you create a new key by calling the GenerateKey method, you are again guaranteed not to get a weak key. In cases where you use a key obtained in some other way, such as from a CSP or another external source, the DES and TripleDES classes will throw a CryptographicException if you try to encrypt with a weak key anyway. In any case, for those of you who tend to worry about such things, you can explicitly test whether any particular key is in fact a weak key using the IsWeakKey method. |