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

  • SymmetricAlgorithm -derived classes encapsulate symmetric algorithms such as DES and Rijndael.

  • AsymmetricAlgorithm -derived classes encapsulate the RSA and DSA asymmetric algorithms.

  • CryptoStream connects a source data stream to a cryptographic algorithm.

  • CspParameters encapsulates algorithm-specific parameter information that can be stored and retrieved via a cryptographic service provider (CSP).

  • HashAlgorithm represents the base class from which all cryptographic hash algorithms are derived.

  • RandomNumberGenerator represents the base class from which cryptographic pseudorandom number generators (PRNGs) are derived.

  • ToBase64Transform and FromBase64Transform are used to convert between a byte stream and a base-64 representation.

  • CryptographicException encapsulates error information for various cryptographic operations.

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.

  • DES is an abstract class that encapsulates the DES symmetric algorithm.

  • TripleDES is an abstract class that encapsulates the Triple DES symmetric algorithm, which was an effective, backward-compatible alternative to DES, providing much greater security.

  • Rijndael is an abstract class that encapsulates the Rijndael symmetric algorithm that is the new standard replacing DES.

  • RC2 is an abstract class that encapsulates the RC2 symmetric algorithm that was developed by Ronald Rivest as a potential replacement for DES.

Table 3-1. Public Virtual Properties in the SymmetricAlgorithm Class

Public Property

Description

BlockSize

Gets or sets the block size in bits for the algorithm, which is the amount of data that is encrypted or decrypted in a single operation. Messages that are larger than this are broken into blocks of this size. The final block must be padded to match this block size. Valid block sizes are specified by the LegalBlockSizes property for each symmetric algorithm. This property is of type int .

FeedbackSize

Gets or sets the feedback size in bits for the algorithm, which is the amount of data that is fed back into successive encryption or decryption operations. This is required in OFB and CFB modes of operation. Valid feedback sizes depend on the symmetric algorithm, but must not be greater than the block size. This property is of type int .

IV

Gets or sets the initialization vector for the symmetric algorithm, which is required in CBC mode. This property is of type array of byte .

Key

Gets or sets the secret key to be used by the symmetric algorithm for encryption and decryption. This property is of type array of byte .

KeySize

Gets or sets the size of the secret key used by the symmetric algorithm in bits. Valid key sizes are specified by the LegalKeySizes property for each symmetric algorithm. This property is of type int .

LegalBlockSizes

Gets the block sizes that are supported by the symmetric algorithm. This read-only property is of type array of KeySizes elements (there is no BlockSizes type). Only block sizes that match an element in this array are supported by the symmetric algorithm.

LegalKeySizes

Gets the key sizes that are supported by the symmetric algorithm. This read-only property is of type array of KeySizes elements. Only key sizes that match an element in this array are supported by the symmetric algorithm.

Mode

Gets or sets the mode for operation for the symmetric algorithm. This property is of type CipherMode , which may be ECB, which encrypts each block individually; CBC, which introduces feedback; CFB or OFB, which use a shift register to process data in smaller chunks ; or CTS, which is a slight variation of the CBC mode of operation.

Padding

Gets or sets the padding mode used in the symmetric algorithm, which is used to fill any remaining bytes of the last block. This property is of type PaddingMode , which may be one of three values: PKCS7 , which indicates that each padding byte is equal to the total number of padding bytes; Zeros , which indicates that the padding bytes are all zero; or None , which means that no padding is used. For encryption algorithms that specify a specific padding scheme, this property is ignored.

Table 3-2. Public Methods in the SymmetricAlgorithm Class

Public Method

Description

Clear

This method calls Dispose , which frees resources used by the symmetric algorithm. This method returns void .

Create

This overloaded static method is used to create a SymmetricAlgorithm -derived object to perform encryption and decryption. This method returns a reference to a SymmetricAlgorithm object.

CreateDecryptor

This overloaded method is used to create a symmetric decryptor object using a key and initialization vector that is either implicitly or explicitly provided. This method returns a reference to an ICryptoTransform interface that can be used to transform data blocks.

CreateEncryptor

This overloaded method is used to create a symmetric encryptor object using a key and initialization vector that is either implicitly or explicitly provided. This method returns a reference to an ICryptoTransform interface that can be used to transform data blocks.

Equals

This overloaded virtual method, inherited from Object , is used to compare two SymmetricAlgorithm -derived objects for equality. This method returns bool .

GenerateIV

This abstract method, when overridden in a derived class, generates a new random initialization vector. This method returns void ; however, the generated IV becomes the new default IV for the SymmetricAlgorithm -derived object.

GenerateKey

This abstract method, when overridden in a derived class, generates a new random key. This method returns void ; however, the generated key becomes the new default key for the SymmetricAlgorithm -derived object.

GetHashCode

This method, inherited from Object , produces a hash value of the SymmetricAlgorithm -derived object. This method returns int .

GetType

This method, inherited from Object , is used to get the type of the SymmetricAlgorithm -derived object. This method returns Type .

ToString

This virtual method, inherited from Object , is used to produce a String object that represents the SymmetricAlgorithm derived object.

ValidKeySize

This method determines whether the specified key size is valid for the current algorithm. This method returns bool .

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

  • DESCryptoServiceProvider [9] is a wrapper class that provides access to the underlying default CSP implementation of DES, which is part of the Win32 CryptoAPI.

    [9] Classes with names ending in CryptoServiceProvider are wrapper classes that use the underlying Win32 CryptoAPI for their implementation. These are typically older algorithms. Classes with names ending in Managed are entirely new implementations that are written in managed code. These are typically newer algorithms, although a few older algorithms are implemented in both forms.

  • TripleDESCryptoServiceProvider is a wrapper class that provides access to the CSP implementation of Triple DES, which is part of the CryptoAPI.

  • RC2CryptoServiceProvider is a wrapper class that provides access to the default CSP implementation of RC2, which is part of the CryptoAPI.

  • RijndaelManaged is a managed class implementation of the Rijndael algorithm. This class is not a wrapper for the underlying unmanaged CryptoAPI.

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.

Категории