Design of the Cryptography Application Block


The design goals for the Cryptography Application Block were to make it easy for you to accomplish the most common cryptography tasks without incurring significant performance degradation. Additionally, like all the other application blocks, the Cryptography Application Block contains extension points to provide consumers of the block with the ability to create and use their own providers. In short, the primary goal for this block was to provide you with the capability to forgo writing the same, often complex, code to perform cryptographic functions in your applications. The Cryptography Application Block provides a programming interface that supports encryption and hashing by letting you take advantage of configuration data and a service façade.

The Cryptographer Class

The Cryptography Application Block achieves its design goal of a simplified application programming interface (API) through the Cryptographer class. The Cryptographer is a service façade that leverages the HashProviderFactory and classes that implement the IHashProvider interface to provide hashing functionality, and the SymmetricCryptoProviderFactory and classes that implement the ISymmetricCryptoProvider interface to supply encryption/decryption functionality. The Cryptographer supports the static public methods listed in Table 8.2.

Each of the methods in Table 8.2 are overloaded with a function that takes strings for the cleartext, hashtext, and ciphertext arguments and another function that takes byte arrays. The string overloads are nice because they make it easy for you to support hashing and encryption calls without requiring the need to convert everything to byte arrays. Instead, the string overloads convert the contents of the string to the byte array, call the overloaded method that accepts the byte arrays, and fill the byte array with a cryptographically strong random set of bytes when the function is complete. This is an important point. In the .NET Framework 1.1, strings are immutable and cannot be overridden. [1] The byte arrays that hold the secrets are not left populated in memory in an effort to minimize an attack.

[1] Writing Secure Code, Howard and LeBlanc, Second Edition, p. 335.

Table 8.2. The Cryptographer Class' Methods

Method

Description

CompareHash

Compares cleartext input with a computed hash.

CreateHash

Computes the hash value of cleartext.

DecryptSymmetric

Decrypts ciphertext using a specified SymmetricCryptographyProvider.

EncryptSymmetric

Encrypts a secret using a specified SymmetricCryptographyProvider.


In addition to the cleartext, hashtext, or ciphertext, each of these methods also expects a string that represents the cryptography instance. For example, if an application were configured to use an SHA1 hashing algorithm named SHA1Managed, the Cryptographer would expect the call CreateHash("SHA1Managed", "myStringToHash")[2] to create a hash for myStringToHash.

[2] Actually, the call would be CreateHash(SR.SHA1Managed, SR.myStringToHash), where each of the properties for the SR class equates to the appropriate string. The SR class can be automatically created using the String Resource Tool, which is in the Downloads section of the Enterprise Library workspace at http://practices.gotdotnet.com/projects/entlib (you need to join but it's free).

The String Resource Tool is used extensively throughout Enterprise Library as a way of creating and managing the string resources in a particular project. I did not use the SR.stringname notation so as not to distract you from the primary point, which is that the CreateHash method expects two strings: the name of a hashing algorithm and the name of a cryptography instance.

For the CompareHash and CreateHash functions, the Cryptographer passes the string that represents the algorithm name to the HashProviderFactory so that an instance of a class that implements the IHashProvider interface can be created through reflection. Then the Cryptographer calls the newly created class' CreateHash method and passes it to the myStringToHash string. Similarly, for the DecryptSymmetric and EncryptSymmetric methods, the Cryptographer passes a string that represents the name of the SymmetricAlgorithmProvider to the SymmetricCryptoProviderFactory in order to create an instance of a class that implements the ISymmetricCryptoProvider interface. It then passes the rest of the arguments to the newly created class to encrypt and decrypt data. Figure 8.1 shows how the Cryptographer class uses the HashProviderFactory and SymmetricCryptoProviderFactory to create the providers it needs.

Figure 8.1. Cryptographer, HashProviderFactory, and SymmetricCryptoProviderFactory


The SymmetricProviderFactory and HashProviderFactory Classes

There's nothing very complicated about the SymmetricProviderFactory or the HashProviderFactory. These classes simply use the names of the providers that are passed to them to look up and create the proper provider from configuration. Once the type of the provider is found in the configuration information, it is created via reflection.

SymmetricProviders

All SymmetricProviders created via the SymmetricProviderFactory must implement the ISymmetricCryptoProvider interface. The ISymmetricCryptoProvider interface contains two methods: Encrypt (byte[]) and Decrypt(byte[]). The Cryptography Application Block includes two implementations: the DpapiSymmetricCryptographyProvider and the SymmetricAlgorithmProvider. The DpapiSymmetricCryptographyProvider uses DPAPI to provide cryptography services, while the SymmetricAlgorithmProvider lets you use any symmetric algorithm that is derived from System.Security.Cryptography.SymmetricAlgorithm.

In addition to the Initialize method for initializing its data from configuration data, each of these providers must support the public Encrypt and Decrypt methods to implement the ISymmetricCryptoProvider interface. Figure 8.2 shows the classes the Cryptography Application Block uses to provide support for symmetric cryptography algorithms. However, before going too far into explaining how these providers work, let's discuss DPAPI.

Figure 8.2. Classes Used for Providing Support for Symmetric Algorithms


What's DPAPI?

The Data Protection API (DPAPI) is a Win32 application programming interface that uses Windowsgenerated keys that are tied to a specific machine or user account to protect the data. Currently, this is considered the best way to protect secrets. The CryptProtectData method is used to encrypt the data and the CryptUnprotectData method is used to decrypt the data. You can use DPAPI to protect files in situations where other security measures cannot. For example, if someone mounts a disk remotely, access control lists (ACLs) cannot protect the file, but DPAPI can.

DPAPI operates in either user or machine mode. In user mode, the ability to decrypt the file is restricted to the account of the user who was logged in when the file was encrypted. Machine mode restricts the ability to decrypt the file to the computer used when the file was encrypted. This means that any user with an account on that computer can decrypt the file. There exist additional mechanisms to further protect data when encrypted in machine mode, though. For example, the file (or registry) in which the data is stored can be further protected with ACLs. This ensures that an application must be running with an account that has been granted permission to the data in the file (or registry) in order to access it.

Using ACLs, however, does not prevent other applications that might also be running under that same account from accessing the data. Fortunately, the CryptProtectData method does allow for further protection by letting an additional password or random number be applied when the data is encrypted. This password is known as the entropy, and if it is used when encrypting the data, it must also be used when the CryptUnpro tectData method is called to decrypt the data. The entropy can be used to further protect the data when DPAPI is used in machine mode. In fact, the entropy is required by the Cryptography Application Block when using DPAPI in machine mode.

Machine mode must be used with caution. It is imperative that any ciphertext that is encrypted using machine mode be backed up. Otherwise, if the machine fails and must be rebuilt or replaced, the key that was used to encrypt the data is lost, and thus the data itself will also be lost. Also, by using machine mode, the data protection is tied to a single machine. This means that it cannot be used for applications that are intended to be distributed to multiple different machines where it is possible that data can be encrypted on one machine and decrypted on another.

The DpapiSymmetricCryptoProvider and the DpapiCryptographer Class

The DpapiSymmetricCryptoProvider takes advantage of the Data Protection API to protect secret information. The DpapiSymmetricCryptoProvider is initialized with data read from configuration. It uses this information to populate and pass to the DpapiCryptographer class. The DpapiCryptographer class is actually part of Enterprise Library's Common namespace and not officially in the Cryptography namespace. This enables its use by the Configuration Application Block to encrypt the configuration information for all of the application blocks (if enabled). The Common namespace contains helper and utility classes for use by all application blocks.

The DpapiCryptographer class is where the "real" encrypting and decrypting is accomplished. In the DpapiCryptographer's EncryptInternal method, the cleartext and entropy are converted to a series of bytes, aggregated, and the CryptProtectData method is called to protect the data using DPAPI. Similarly, in the DecryptInternal method, the entropy is converted to a series of bytes, the CryptUnprotectData method is called, and the byte array that represents the cleartext secret is returned.

The SymmetricAlgorithmProvider and the SymmetricCryptographer Class

It's not always necessary (or even desirable) to encrypt all application data using DPAPI. Doing so may bind all data to a particular user or machine. Rather, it is often better to simply encrypt a single master key using DPAPI and leverage the other symmetric or asymmetric algorithms to encrypt the application data. Like the DpapiSymmetricCryptoProvider, the SymmetricAlgorithmProvider passes control to a separate cryptographer class that is part of the Common namespace. In this case, it is the SymmetricCryptographer. The SymmetricCryptographer creates an instance of the algorithm provider for which it is configured via reflection. It then uses this algorithm provider to encrypt and decrypt the data.

Custom Symmetric Algorithms

When the algorithm is created in the SymmetricCryptographer class, it is cast to the System.Security.Cryptography.SymmetricAlgorithm type. You can configure the Cryptography Application Block to use any algorithm that is derived either directly or indirectly from this abstract base class. Therefore, creating a cryptographic algorithm that works with the Cryptography Application Block means deriving a class from the SymmetricAlgorithm abstract base class or a class derived from it. While it is certainly possible to create such a class, this type of endeavor should really be avoided. To quote Howard and LeBlanc:

Producing good cryptographic algorithms is a difficult task, one that should be undertaken only by those who well understand how to create such algorithms… The best way to use encryption is to use tried and trusted encryption algorithms defined in libraries such as CryptoAPI included with Windows.[3]

[3] Writing Secure Code, Second Edition, pp. 281282.

Still, if you don't want to heed this advice and absolutely feel that you must create your own custom symmetric algorithm to use in your applications, you should feel confident that the Cryptography Application Block can use it. There are three steps you must take.

  1. Create a class that is derived from the SymmetricAlgorithm class.

  2. Override all abstract methods.

  3. Deploy the assembly.

Listing 8.1 shows such an implementation, named DontTryThisSymmAlgorithm. The implementation does not do any encryption or decryption; rather, it is included simply to show that it is possible to add a symmetric algorithm and have it used by the Cryptography Application Block.

Listing 8.1. Custom Symmetric Algorithm

[C#] public class DontTryThisSymmAlgorithm : SymmetricAlgorithm {     // Properties.     private RNGCryptoServiceProvider RNG     {          get          {               if (this._rng == null)               {                    this._rng = new RNGCryptoServiceProvider();               }               return this._rng;          }     }     // Fields.     private RNGCryptoServiceProvider _rng;     public DontTryThisSymmAlgorithm()     {     }     public override ICryptoTransform CreateDecryptor          (byte[] rgbKey, byte[] rgbIV)     {          // TODO:  Add CreateDecryptor implementation.          return null;     }     public override ICryptoTransform CreateDecryptor()     {          // TODO:  Add CreateDecryptor implementation.          return base.CreateDecryptor ();     }     public override ICryptoTransform CreateEncryptor          (byte[] rgbKey, byte[] rgbIV)     {          // TODO:  Add CreateEncryptor implementation.          return null;     }     public override ICryptoTransform CreateEncryptor()     {          // TODO:  Add CreateEncryptor implementation.          return base.CreateEncryptor ();     }     public override void GenerateIV()     {          // TODO:  Add GenerateIV implementation.          this.IVValue = new byte[this.BlockSizeValue / 8];          this.RNG.GetBytes(this.IVValue);     }     public override void GenerateKey()     {          // TODO:  Add GenerateKey implementation.          this.KeyValue = new byte[this.KeySizeValue / 8];          this.RNG.GetBytes(this.KeyValue);     } } [Visual Basic] Public Class DontTryThisSymmAlgorithm : Inherits SymmetricAlgorithm     ' Properties.     Private ReadOnly Property RNG() As RNGCryptoServiceProvider          Get               If Me._rng Is Nothing Then                    Me._rng = New RNGCryptoServiceProvider()               End If               Return Me._rng          End Get     End Property     ' Fields.     Private _rng As RNGCryptoServiceProvider     Public Sub New()     End Sub     Public Overrides Function CreateDecryptor _          (ByVal rgbKey As Byte(), ByVal rgbIV As Byte()) _          As ICryptoTransform          ' TODO:  Add CreateDecryptor implementation.          Return Nothing     End Function     Public Overrides Function CreateDecryptor() As ICryptoTransform          ' TODO:  Add CreateDecryptor implementation.          Return MyBase.CreateDecryptor ()     End Function     Public Overrides Function CreateEncryptor _          (ByVal rgbKey As Byte(), ByVal rgbIV As Byte()) _          As ICryptoTransform          ' TODO:  Add CreateEncryptor implementation.          Return Nothing     End Function     Public Overrides Function CreateEncryptor() As ICryptoTransform          ' TODO:  Add CreateEncryptor implementation.          Return MyBase.CreateEncryptor ()     End Function     Public Overrides Sub GenerateIV()          ' TODO:  Add GenerateIV implementation.          Me.IVValue = New Byte(Me.BlockSizeValue / 8 - 1) {}          Me.RNG.GetBytes(Me.IVValue)     End Sub     Public Overrides Sub GenerateKey()          ' TODO:  Add GenerateKey implementation.          Me.KeyValue = New Byte(Me.KeySizeValue / 8 - 1) {}          Me.RNG.GetBytes(Me.KeyValue)     End Sub End Class

When this class has been deployed so the Enterprise Library Configuration Tool recognizes it, it becomes as easy to support this algorithm as it is for any of the ones that are supported by default. Figure 8.3 displays the Type Selector dialog that is used to select a symmetric algorithm in the Configuration Tool. It illustrates how the new symmetric algorithm can be selected for use in an application.

Figure 8.3. Using a Custom Symmetric Algorithm with the Cryptography Application Block


Hash Providers

SymmetricAlgorithmProviders are included with the Cryptography Application Block to facilitate encrypting and decrypting data. Even encrypted data, however, is vulnerable to data tampering. Therefore, in addition to symmetric providers, hashing providers are included in the Cryptography Application Block to make it easier to validate the integrity of the data in an application.

Some of the most common uses for such a need revolve around password validation. It is often necessary for an application to verify that a user knows his or her password; however, to do this, the application may not actually need to store and use the password itself. Instead, a hash for that data can be stored. When the user enters his or her password, a hash for the entered information is compared against the hash that is stored. If the two hashes are equal, then the application can verify that the password has been properly entered.

The Cryptography Application Block includes two implementations of hash providers: the HashAlgorithmProvider and the KeyedHashAlgorithmProvider. As the names imply, the HashAlgorithmProvider allows hash algorithms that do not require a key to be selected and configured, while the KeyedHashAlgorithmProvider is for hash algorithms that require a key. Both providers allow a salt value to be generated and prepended to the data before it is hashed. Figure 8.4 shows the classes used to support hashing in the Cryptography Application Block.

Figure 8.4. Classes Used for Providing Support for Hashing Algorithms


The HashAlgorithmProvider and HashCryptographer Classes

Just as all symmetric providers need to implement the ISymmetricCryptoProvider interface, all hash algorithm providers created via the HashProviderFactory must implement the IHashProvider interface. The IHashProvider interface contains two methods: CreateHash(byte[]) and CompareHash(byte[], byte[]). The CompareHash method compares two hashes and determines if they are equal. Many people incorrectly assume that if the results of two CreateHash methods with the same byte array are equal, then the hashes are equal. However, this is not always true if salt is used. The CompareHash method takes the salt into account when comparing the hashes.

Table 8.3. Public Methods and Properties for the HashAlgorithmProvider Class

Method/Property

Description

CompareHash

Compares cleartext input with a computed hash.

CreateHash

Computes the hash value of cleartext.

Initialize

Initializes the provider with a CryptographyConfigurationView.

SaltLength

Returns the salt length used by the provider. This is a static property.


As Figure 8.4 shows, the HashAlgorithmProvider not only implements the IHashProvider interface, but it also derives from the ConfigurationProvider class. Therefore, the HashAlgorithmProvider class is a ConfigurationProvider that implements the CreateHash and CompareHash methods. As a ConfigurationProvider, it initializes the data that it will use from configuration data. It uses this data to determine the appropriate hash algorithm to use and to determine if the hash should be salted. Table 8.3 lists all of the public methods and properties of the HashAlgorithmProvider class.

Like the SymmetricAlgorithmProvider and DpapiSymmetricCryptoProvider, the HashAlgorithmProvider uses many of the helper classes that exist in Enterprise Library's Common namespace. In the CreateHash method, the HashAlgorithmProvider uses the CryptographyUtility class that resides in the Common namespace to get a byte array of cryptographically strong random data to use as the salt (if salting is enabled). This method is very useful because it uses the RNGCryptoServiceProvider.Create().GetBytes(bytes) to create this array. This is a recommended best practice for generating cryptographically strong random data.[4]

[4] Writing Secure Code, Second Edition, pp. 268269. Manually generating keys or using System.Random to generate keys is not sufficient because the results are deterministic and repeatable.

The HashAlgorithmProvider then uses the same CryptographyUtility class to combine the bytes for the salt with the bytes for the cleartext. A HashCryptographer, which also resides in the Common namespace, is then used to compute the actual hash. The HashCryptographer uses the algorithm for which the HashAlgorithmProvider has been configured to compute this hash. Then the bytes for the salt are combined once again, this time with the actual hashed value.

When comparing hashes, the HashAlgorithmProvider first extracts the salt from the hashed text. It then uses this salt to create a hash value for the plaintext to which it is comparing the hash. Then the CryptographyUtility class is used to compare the bytes of the two hashes. Finally, the CryptographyUtility class is used to zero out the bytes in memory for the salt value.

The KeyedHashAlgorithmProvider Class

The KeyedHashAlgorithmProvider class derives from the HashAlgorithmProvider class. The only difference between the two classes is how the HashCryptographer class gets created. When the KeyedHashAlgorithmProvider class creates the HashCryptographer class, it passes a key into the constructor. If the algorithm that is being used to hash the data is a keyed hash algorithm, for example, HMAC SHA1 or MAC Triple DES, then the key is read from the configuration data and used to set the key value for the actual underlying algorithm.

Custom Hash Algorithms

The HashCryptographer class first casts the algorithm that it creates to a System.Security.Cryptography.HashAlgorithm. After this step, it then attempts to cast it to a System.Security.Cryptography.KeyedHashAlgorithm to determine if the class is a KeyedHashAlgorithm or not. If the cast is successful, and therefore the object is not equal to null, then it is assumed to be a KeyedHashAlgorithm and its key is set. Otherwise, it is assumed to be a nonkeyed hash algorithm. Listing 8.2 shows the code for the HashCryptographer's GetHashAlgorithm method. The algorithm that is returned is then used to compute and compare hashes.

Listing 8.2. How the HashCryptographer Sets the Key for a KeyedHashAlgorithm

[C#] try {     Type type = Type.GetType(algorithmType);     algorithm = Activator.CreateInstance(type, true) as HashAlgorithm;     KeyedHashAlgorithm keyedHashAlgorithm =          algorithm as KeyedHashAlgorithm;     if ((null != keyedHashAlgorithm) && (key != null))     {          keyedHashAlgorithm.Key = key;     } } [Visual Basic] Try     Dim type As Type = Type.GetType(algorithmType)     algorithm = IIf(TypeOf Activator.CreateInstance(type, True) _                    Is HashAlgorithm, _                    CType(Activator.CreateInstance(type, True), _                    HashAlgorithm), CType(Nothing, HashAlgorithm))     Dim keyedHashAlgorithm As KeyedHashAlgorithm = _          IIf(TypeOf algorithm Is KeyedHashAlgorithm, _          CType(algorithm, KeyedHashAlgorithm), _          CType(Nothing, KeyedHashAlgorithm))          If (Not Nothing Is keyedHashAlgorithm) AndAlso _          (Not key Is Nothing) Then               keyedHashAlgorithm.Key = key          End If End Try

Custom hash algorithms use the same method that custom symmetric algorithms use; the only difference is that the abstract base class from which the custom class must derive is the System.Security.Cryptography. HashAlgorithm (or System.Security.Cryptography.KeyedHashAlgorithm) class. The same caveats apply as they did for the custom symmetric algorithm (see the section Custom Symmetric Algorithms earlier in this chapter). You must do the following three steps.

1.

Create a class that is derived from the HashAlgorithm or Keyed HashAlgorithm class.

2.

Override all abstract methods.

3.

Deploy the assembly.

Listing 8.3 shows an implementation named DontTryThisHashAlgorithm. The implementation is intended to just show that it is possible to add a hash algorithm and have it used by the Cryptography Application Block.

Listing 8.3. Custom Hashing Algorithm

[C#] public class DontTryThisHashAlgorithm : HashAlgorithm {     public DontTryThisHashAlgorithm()     {     }     protected override void HashCore          (byte[] array, int ibStart, int cbSize)     {          // TODO:  Add HashCore implementation.     }     public override void Initialize()     {          // TODO:  Add Initialize implementation.     }     protected override byte[] HashFinal()     {          // TODO:  Add HashFinal implementation.          return null;     } } [Visual Basic] Public Class DontTryThisHashAlgorithm : Inherits HashAlgorithm     Public Sub New()     End Sub     Protected Overrides Sub HashCore _          (ByVal array As Byte(), ByVal ibStart As Integer, _          ByVal cbSize As Integer)          ' TODO:  Add HashCore implementation.     End Sub     Public Overrides Sub Initialize()          ' TODO:  Add Initialize implementation.     End Sub     Protected Overrides Function HashFinal() As Byte()          ' TODO:  Add HashFinal implementation.          Return Nothing     End Function End Class

After the class has been deployed, the Enterprise Library Configuration Tool can recognize and support this hashing algorithm just like any of the ones that are a part of the .NET Framework. Figure 8.5 displays the Type Selector dialog that is used to select a hashing algorithm in the Configuration Tool.

Figure 8.5. Using a Custom Hash Algorithm with the Cryptography Application Block





Fenster Effective Use of Microsoft Enterprise Library(c) Building Blocks for Creating Enterprise Applications and Services 2006
Effective Use of Microsoft Enterprise Library: Building Blocks for Creating Enterprise Applications and Services
ISBN: 0321334213
EAN: 2147483647
Year: 2004
Pages: 103
Authors: Len Fenster

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net