Chapter 12

Using Encryption and Managing Assemblies

What You Will Learn in This Chapter

  • Understanding encryption
  • Using symmetric encryption
  • Using asymmetric encryption
  • Signing and hashing data
  • Creating strong name assemblies
  • Deploying assemblies in the GAC

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

You can find the code downloads for this chapter at www.wrox.com/remtitle.cgi?isbn=1118612094 on the Download Code tab. The code is in the chapter12 download and individually named according to the names throughout the chapter.

When you deal with sensitive data, you must protect it from unauthorized access or modification, and for that you need to use techniques such as encryption, digital signatures, and hashing of data. One application of digital signatures and hashing of data used often by .NET is with strong name assemblies.

This chapter begins by going through different technologies that you can use to ensure privacy, integrity, and authenticity of your data. You will look at both symmetric and asymmetric algorithms. In the second part of this chapter you will look in details at how you can ensure the integrity of you assemblies by using digital signatures.

Table 12-1 introduces you to the exam objectives covered in this chapter.

Table 12-1: 70-483 Exam Objectives Covered in This Chapter

ObjectiveContent Covered
Perform symmetric and asymmetric encryptionChoose an appropriate encryption algorithm. This includes discussing the symmetric and asymmetric algorithms.
Manage and create certificates. This includes working with different kind of certificates and certificates store.
Implement key management. This includes discussing the options available in .NET to store the encryption keys.
Implement the System.Security namespace. This includes discussing the classes and interfaces available in the System.Security namespace.
Hash data. This includes discussing the options available to hash data and to create digital signatures.
Encrypt streams. This includes using the CryptoStream class to encrypt streams
Manage assembliesVersion assemblies. This includes choosing a versioning scheme for the assembly.
Sign assemblies using strong names. This includes signing the assemblies from Visual Studio, as well as using the sn.exe.
Implement side-by-side hosting. This includes discussing techniques you can use to have different version of the same assembly on the same machine at the same time.
Put an assembly in the global assembly cache. This includes using different tools to deploy the assemblies in the global assembly cache.

Using Encryption

Encryption is the process of transforming plain data in a way that makes it harder for an unauthorized person to make sense of it. The encrypted data is called ciphertext. Decryption is the reverse process, meaning that having the ciphertext, you must apply a transformation to it to get back the original information. The harder it is to decrypt the ciphertext, the better the algorithm. You might have reacted to saying “harder” to make sense of it instead of saying “impossible.” The reason for that is because any encrypted data can be decrypted eventually, but some algorithms are so complicated that it can take a long time (such as hundreds of years) to decrypt the data, so the information is useless. Cryptography is the practice and study of encryption and decryption techniques.

Romans used simple algorithms such as transposition. They replaced every letter with another one, like the next one in the alphabet, or the one found three positions after the current one. But this was just a simple algorithm. To decrypt the message, you only had to figure out the algorithm used (transposition in this example) and the algorithm parameters (how many positions were used for the transposition). In time the algorithms evolved, making it harder and harder to decrypt the data. A notable effort before computers appeared was the German machine Enigma that used an electromechanical rotor cipher to encrypt and decrypt secret messages.

With the invention of computers, the encryption became more and more advanced, and with today’s technologies the algorithms are open because their parameters are the ones improving the security of any encrypted data. Today’s encryption techniques make heavy use of mathematics.

Sometimes you might not need to hide the data, but to make sure that the data is not tampered with, or that the data comes from the right source. If you want to ensure that the data is not tampered with, you can use a Secured Hash Algorithm (SHA), whereas for authenticity of the data you can use a Message Authentication Code (MAC) algorithm. So let’s assume that you want to send an order to a supplier. To make sure that the order doesn’t get altered on its way to the supplier, you can add the SHA signature to the message. If you want to guarantee the identity of the sender of the order as well as the integrity of the order, then you would use a MAC.

Choosing an Appropriate Encryption Algorithm

You saw that encryption and decryption protect data from unauthorized access. Encryption can be done in two ways. One way is called symmetric encryption, or shared secret, and the second one is called asymmetric encryption or public key. (Both types of encryption are described in more detail in the following sections.)

Microsoft implemented some of the existing algorithms in .NET, which are implemented in three ways:

  • Managed classes (in .NET): The class name for those is the algorithm name suffixed with Managed, for instance RijndaelManaged is the managed class that implements the Rijndael algorithm. The managed implementations are somewhat slower than the other implementations and are not certified by the Federal Information Processing Standards (FIPS).
  • Wrapper classes around the native Cryptography API (CAPI) implementation: The class name for those is the algorithm name suffixed with CryptoServiceProvider, for instance DESCryptoServiceProvideris the wrapper class that implements the Data Encryption Standard (DES) algorithm. The CAPI implementations are suitable for older systems, but they are no longer being developed.
  • Wrapper classes around the native Cryptography Next Generation (CNG) API implementation: The class name for those is the algorithm name suffixed with CNG, for instance ECDiffieHellmanCngis the wrapper class that implements the Elliptic Curve Diffie-Hellman (ECDH) algorithm. CNG algorithms require a Windows Vista or newer operating system.

All cryptography classes are defined in the System.Security.Cryptography namespace and are part of the core .NET library.

Symmetric Encryption

As mentioned symmetric encryption is also known as shared secret encryption, and that is because the encryption of the data is done with an encryption key, a byte array, and the same key is used to decrypt the data. The symmetric algorithms rely on the fact that only an authorized person has access to the encryption key. The main drawback of the symmetric encryption is that if the encryption key becomes compromised, the data will not be secured.

The symmetric algorithms that come with .NET use a chaining mode called cipher block chaining. This kind of algorithm works in the following way. If the data is bigger than a predefined size, called block size, the data is split in blocks of that predefined size. The first block is encrypted using a random block of data of the same size, called initialization vector (IV), and the encryption key. The next block is encrypted using the result of the previous encrypted block instead of the IV and the same encryption key, and so on until it reaches the last block. The block size is determined by the algorithm used. If the last block is less than that size, it will be padded with data so it will have the same size as the size used by the algorithm. To decrypt the cipher text, you must have the IV and the key. The IV doesn’t need to be kept secret, but the private key does. You will see later in this chapter how the encryption key can be handled.

.NET Framework implements five different symmetric algorithms, as shown in Table 12-2.

Table 12-2: Symmetric Algorithms Implemented in .NET

Algorithm Short NameDescription
AesAdvanced Encryption Standard (AES). There are two classes implementing this algorithm:
AesManaged and AesCryptoServiceProvider.
DESData Encryption Standard algorithm implemented by DESCryptoServiceProvider.
RC2Rivest Cipher (or Ron’s code) algorithm implemented by RC2CryptoServiceProvider.
RijndaelRijndael algorithm implemented by RijndaelManaged.
TripleDESTriple Data Encryption Standard (DES) algorithm implemented by TripleDESCryptoServiceProvider.

All the classes mentioned in Table 12-2 inherit from the System.Security.Cryptography.SymmetricAlgorithm class. This class contains properties and methods that are useful when working with symmetric algorithms. Tables 12-3 and 12-4 list the properties and methods of that class.

Table 12-3: System.Security.Cryptography.SymmetricAlgorithm Properties

PropertyDescription
BlockSizeGets or sets the size of the block used by the cryptographic operation. The block size is specified in bits and represents the basic unit of data that can be encrypted or decrypted in one operation. Messages longer than the block size are split into blocks of this size; messages shorter than the block size are padded with extra bits until they reach the size of a block. The algorithm used determines the validity of the block size.
FeedbackSizeGets or sets the size of the feedback size used by the cryptographic operation. The feedback size represents the amount of data in bits that is fed back to the next encryption or decryption operation. The feedback size must be lower than the block size.
IV Gets or sets the IV. Whenever you create a new instance of a symmetric algorithm, the IV is set to a new random value. You can generate one as well by calling the GenerateIV method. The size of the IV property must be the same as the BlockSize property divided by eight.
KeyGets or sets the secret key used by the algorithm. The secret key has to be the same for both encryption and for decryption. For a symmetric algorithm to be successful, the secret key must be kept secret. The valid key sizes are specified by the particular symmetric algorithm implementation and are listed in the LegalKeySizes property.
KeySizeGets or sets the size of the secret key used by the symmetric algorithm. The valid key sizes are specified in bits by the particular symmetric algorithm implementation and are listed in the LegalKeySizes property.
LegelBlockSizesGets the block sizes in bits that are accepted by the algorithm.
LegalKeySizesGets the key sizes in bits that are accepted by the algorithm.
ModeGets or sets the mode for operation of the algorithm. See the System.Security.Cryptography.CipherMode enumeration for a description of specific modes.
PaddingGets or sets the padding mode used by the algorithm. See the System.Security.Cryptography.PaddingMode enumeration for a description of specific modes.

Table 12-4: System.Security.Cryptography.SymmetricAlgorithm Methods

MethodDescription
ClearReleases all resources used by the SymmetricAlgorithm class. You need to call this method to clear all the resources allocated by the algorithm to ensure that no sensitive data remains in the memory when you finish with the cryptographic object. Do not rely on garbage collector to clear the data.
Create()This static method creates a new cryptographic using the default algorithm, which in .NET 4.5 is RijndaelManaged.
Create(String)This static method creates a new cryptographic object using the specified cryptographic algorithm. The name of the algorithm can be either one of the names on the left column of Table 12-2 or the name of the type itself, with or without the namespace. Aes corresponds to the AesCryptoServiceProvider algorithm. If you want to use the managed version of the algorithm, you need to specify AesManaged.
CreateDecryptor()Creates a decryptor object using the Key and IV currently set in the properties.
CreateDecryptor(Byte[], Byte[])Creates a decryptor object using the Key and IV values specified as parameters.
CreateEncryptor()Creates an encryptor object using the Key and IV currently set in the properties.
CreateEncryptor(Byte[], Byte[])Creates an encryptor object using the Key and IV properties specified as parameters.
GenerateIVGenerates a random IV to be used by the algorithm. Normally there is no need to call this method.
GenerateKeyGenerates a random Key to be used by the algorithm. The secret key has to be the same for both encryption and for decryption. For a symmetric algorithm to be successful, the secret key must be kept secret. The valid key sizes are specified by the particular symmetric algorithm implementation and are listed in the LegalKeySizes property.
ValidKeySizeReturns true if the specified key size is valid for this specific algorithm.

The workflow of encrypting plain text into chipper text is straightforward:

1. Create a symmetric algorithm object by calling the Create method of the SymmetricAlgorithm class setting the optional string parameter to the name of the wanted algorithm.
2. If you want you can set a key and an IV, but this is not necessary because they are generated by default.
3. Create an encryptor object by calling the CreateEncryptor method. Again, you can choose to send the key and the IV as parameters to this method or use the default, generated one.
4. Call the TransformFinalBlock method on the encryptor, which takes as input a byte array, representing the plain data, the offset where to start the encryption from, and the length of the data to encrypt. It returns the encrypted data back.

The code should look like this:

byte[] EncryptData(byte[] plainData, byte[] IV, byte[] key) {

    SymmetricAlgorithm cryptoAlgorythm = SymmetricAlgorithm.Create();
    ICryptoTransform encryptor = cryptoAlgorythm.CreateEncryptor(key, IV);
    byte[] cipherData = encryptor.TransformFinalBlock(plainData, 0, 
                                                    plainData.Length);

    return cipherData;
}

The workflow of decrypting chipper text to get back the plain text is straightforward as well:

1. Create a symmetric algorithm object by calling the Create method of the SymmetricAlgorithm class setting the optional string parameter to the name of the same algorithm used for encryption.
2. If you want you can set a key and an IV, but this is not necessary now because you can set them on the next step.
3. Create a decryptor object by calling CreateDecryptor method. You must now set the key and the IV by sending them as parameters to this method, if you didn’t do it in the previous step. The key and the IV must be the same as the ones used for encryption.
4. Call the TransformFinalBlock method on the decryptor, which takes as input a byte array, which is the chipper data, the offset where to start the decryption from, and the length of the data to decrypt, and it returns the plain data back.

The code should look like this:

byte[] DecryptData(byte[] cipherData, byte[] IV, byte[] key) {
    SymmetricAlgorithm cryptoAlgorythm = SymmetricAlgorithm.Create();
    ICryptoTransform decryptor = cryptoAlgorythm.CreateDecryptor(key, IV);
    byte[] plainData = decryptor.TransformFinalBlock(cipherData, 0,
cipherData.Length);
    return plainData;
}

The biggest challenge of symmetric encryption algorithms is to keep the key secret. If the data doesn’t have to leave the machine, you must save it somehow in a safe place so that only you can get access to it when you need to decrypt the data. You see later in this chapter in the “Implementing Key Management” section how to solve that.

If you need to send the data across the wire, make sure that the other party has the key as well. Transmitting the key needs to be done securely so that no unauthorized person gets access to it; otherwise, the whole data becomes compromised.


ADVICE FROM THE EXPERTS: Working with Symmetric Algorithms
By using the abstract base class SymmetricAlgorithm instead of the concrete classes and calling the factory method Create, you have good flexibility in your system. If later the requirements for your encryption change and you need to use a different algorithm, the only thing that you need to do, in principle, is to change the name of the algorithm.

Asymmetric Encryption

An alternative to symmetric encryption is to use asymmetric encryption. The main reason to use asymmetric encryption is to avoid sharing the encryption key, which is considered a vulnerability. Asymmetric encryption uses two mathematically related keys that complement each other, such as whatever is encrypted with one key can be decrypted only with the other key. One key is made public, and is known as the public key, by the receiving party, so whoever wants to transmit secured data can encrypt the data. Make sure at the same time only the receiving party can decrypt the data by using the other key, known as the private key. (The mathematics behind generating the two keys is outside the scope of this book.)

The main disadvantage of the asymmetric encryption is that it is slower than the symmetric encryption, but the biggest advantage is that there is no need to have a shared secret for the algorithm to work.

.NET Framework implements five different symmetric algorithms, as shown in Table 12-5.

Table 12-5: Asymmetric Algorithms Implemented in .NET

Algorithm Short NameDescription
DSADigital Signature algorithm. Used to create digital signatures that help protect the integrity of data.
There is one class implementing this algorithm: DSACryptoServiceProvider. Use DSA only for compatibility with legacy applications and data.
ECDiffieHellmanElliptic Curve Diffie-Hellman algorithm implemented by ECDiffieHellmanCng.
ECDsaElliptic Curve Digital Signature Algorithm (ECDSA) algorithm implemented by ECDsaCng.
RSARSA algorithm implemented by RSACryptoServiceProvider.

In .NET all classes that implement an asymmetric algorithm inherit from System.Security.Cryptography.AsymmetricAlgorithm.

The workflow of encrypting data using asymmetric encryption follows:

1. Obtain the public key of the receiver.
2. Create a new asymmetric encryption object.
3. Set the public key.
4. Encrypt the data.
5. Send the data to the receiver.

The workflow of decrypting data using asymmetric encryption follows:

1. Get the data from the sender.
2. Create a new asymmetric encryption object.
3. Set the private key.
4. Decrypt the data.

If the data were changed or it not encrypted using the corresponding public key, a CryptographicException will be thrown.

There are two ways to handle the keys. The first is to send the key directly; the second is to save the keys in a cryptographic service provider container. How to handle the key will be discussed later in this chapter in the “Implementing Key Management” section. To see how those two are implemented, look at the code lab at the end of this section.

One common scenario is to use asymmetric encryption to exchange the keys needed for symmetric encryption using the following workflow:

1. Both parties generate a public/private key pair.
2. The parties exchange the public keys.
3. Each party generates a symmetric key that can use the encrypt data.
4. Each party encrypts the symmetric key using an asymmetric algorithm and the other party’s public key.
5. Each party sends the encrypted key to the other party.
6. Each party decrypts the key using the same asymmetric algorithm and their own private key to obtain the symmetric key generated by the other party.
7. They start exchanging data using the same symmetric algorithm and the keys from previous step.

Another common scenario is to use the asymmetric encryption to digitally sign data, ensuring in this way both the authenticity and the identity.

Stream Encryption

As discussed in Chapter 9, “Working with Data,” streams represent a file, an I/O device, or a communication channel and can perform three fundamental operations:

  • Read: You can transfer data from the stream into a data structure.
  • Write: You can transfer data to the stream from a data structure.
  • Seek: You can change the current position within the stream where the next read-or-write operation operates.

One important property of streams in .NET is that they can be chained by feeding the output data from a stream into the input of another stream. Sometimes you might want to encrypt the data the goes through those streams, in order to ensure the privacy or integrity of the data. You can either encrypt the data before it is sent through the stream, or you can just chain the streams so one of them will be in charge of encryption. Remember that one of the streams classes presented in Chapter 9 was CryptoStream, which you can use to encrypt or decrypt data coming through it.

The workflow of encrypting streams is straightforward:

1. Create a symmetric algorithm object by calling the Create method of the SymmetricAlgorithm class, setting the optional string parameter to the name of the wanted algorithm.
2. If you want you can set a key and an IV, but this is not necessary because they will be generated by default.
3. Create an encryptor object by calling CreateEncryptor method. Again, you can choose to send the key and the IV as parameters to this method or use the default generated one.
4. Create a CryptoStream object. The constructor of CryptoStream expects three parameters. The first parameter is the stream where you send the encrypted data; the second one is the encryptor you created in the previous step; and the third one is the stream operation mode, which in this case is Write.
5. Write data to the CryptoStream object either calling one of the Write methods exposed by CryptoStream, by using a StreamWriter, or by chaining it to another stream.
6. When you finish, clear the CryptoStream object of all the sensitive data by calling the Clear method, and then dispose of the object.

The code should look like this:

byte[] EncryptString(string plainData, byte[] IV, byte[] key) {

    SymmetricAlgorithm cryptoAlgorythm = SymmetricAlgorithm.Create();
    ICryptoTransform encryptor = cryptoAlgorythm.CreateEncryptor(key, IV);
    byte[] cipherData = new byte[0];

    using (MemoryStream msEncrypt = new MemoryStream()) {
        using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, 
                                                encryptor, 
                                                CryptoStreamMode.Write)) {
            StreamWriter swEncrypt = new StreamWriter(csEncrypt);

            swEncrypt.Write(plainData);
            swEncrypt.Close();
            csEncrypt.Clear();
            cipherData = msEncrypt.ToArray();
        }
    }
    return cipherData;
}

The workflow of decrypting streams is straightforward as well:

1. Create a symmetric algorithm object by calling Create method of the SymmetricAlgorithm class, setting the optional string parameter to the name of the same algorithm used for encryption.
2. If you want you can set a key and an IV, but this is not necessary now because you can set them on the next step.
3. Create a decryptor object by calling CreateDecryptor method. You now have to set the key and the IV by sending them as parameters to this method if you didn’t do it in the previous step. The key and the IV must be the same as the ones used for encryption.
4. Create a CryptoStream object. The constructor of CryptoStream expects three parameters. The first parameter is the stream where to send the encrypted data; the second one is the decryptor you created in the previous step; and the third one is the stream operation mode, which in this case is Read.
5. Read data from the CryptoStream object either calling one of the Read methods exposed by CryptoStream, by using a StreamReader or by chaining it to another stream.
6. When you finish, clear the CryptoStream object of all the sensitive data by calling the Clear method, and then dispose of the object.

The code should look like this:

string DecryptString(byte[] cipherData, byte[] IV, byte[] key) {

    SymmetricAlgorithm cryptoAlgorythm = SymmetricAlgorithm.Create();
    ICryptoTransform decryptor = cryptoAlgorythm.CreateDecryptor(key, IV);
    string plainText = string.Empty;

    using (MemoryStream msDecrypt = new MemoryStream(cipherData)) {
        using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, 
                                                decryptor, 
                                                CryptoStreamMode.Read)) {
            StreamReader srDecrypt = new StreamReader(csDecrypt);

            plainText = srDecrypt.ReadToEnd();
            srDecrypt.Close();
            csDecrypt.Clear();
        }
    }
    return plainText;
}

ADVICE FROM THE EXPERTS: Chaining Streams to Encrypt and Compress Data
One common scenario is to encrypt and compress the data that is sent via a network. The order in which you do that is important for two reasons. First, compressing text is more effective than compressing binary data, resulting in less data to encrypt. Second, you have to apply the transformations in reverse order, meaning that if you first compress and then encrypt the data on the sender side, you must first decrypt data and then decompress it on the receiver side.

Hashing Data

Hashing is the process of mapping binary data of a variable length to a fixed size binary data. Applying the same hash function to two identical data structures yields the same result. Hashing functions are used in several scenarios:

  • Indexing data: Instead of matching the data when the index key is a variable length, calculate the hash value of the index key and locate that instead. The hash value resulting from a data structure is usually shorter than the original value, so when searching for a shorter amount of data, the search time will be shorter as well. It is possible that two or more index keys can yield the same hash value. In this situation the indexing algorithm uses a technique called hash bucket, where all the index keys having the same hash value are grouped together. The kind of hashing used in this scenario has nothing to do with cryptography, but it is worth mentioning.
  • Data integrity: Data integrity is used to ensure that the data reaches the destination unchanged. The sender computes a cryptographic hash of the data that wants to send, and then the sender sends the data, the hash, and information about the technique used to compute the hash to the receiver. The receiver can apply the same algorithm to the data, and it will compare the resulting hash with the one received. If they are the same, it means that the data wasn’t changed after the hash was computed. This doesn’t guarantee that the data is not changed, though. If someone wants to change the message, the only thing that person will have to do is to compute the hash of the new message and send that instead.
  • Data authenticity: Data authenticity is used when a receiver wants to make sure that the data is coming from the right sender and that it is not changed on its way. It works in this way: The sender computes a cryptographic hash and signs it with its own private key. The receiver hashes the data again and then decrypts the received signature, uses the senders’ public key to decrypt the signature, and verifies that is the same as the hash.
  • Password storage: Storing a password in plain text is an unsecure technique, and if the data store becomes compromised, all the passwords will be compromised as well. To protect the passwords, they are usually hashed, and instead of saving the password, you save the hash of the password. When someone attempts to log in, you can hash the entered password and verify that the two hashes are the same.

The strength of the cryptographic hash is that it is improbable that two different inputs generate the same hash. Two passwords that are not the same and differ little from each other can produce two completely different hashes.

There are two kind of hashing algorithms: with or without a key. The algorithms without keys are used only to calculate secure hashes for data to ensure integrity, whereas the keyed algorithms are used together with a key as a MAC for both integrity and authenticity.

.NET Framework comes with implementations for different algorithms, as shown in Table 12-6.

Table 12-6: Hash Algorithms Implemented in .NET

Algorithm Short NameDescription
SHA1Implementation of SHA algorithm with a resulting hash size of 160 bits
SHA256Implementation of SHA algorithm with a resulting hash size of 256 bits
SHA512Implementation of SHA algorithm with a resulting hash size of 512 bits
SHA384Implementation of SHA algorithm with a resulting hash size of 384 bits
MD5Implementation of MD5 hash algorithm
RIPEMD160Implementation of RIPEMD hash algorithm

In .NET all hashing algorithms inherit from the System.Security.Cryptography.HashAlgorithm abstract class. Tables 12-7 and 12-8 list the properties and methods of the HashAlgorithm class.

Table 12-7: System.Security.Cryptography.HashAlgorithm Properties

PropertyDescription
CanReuseTransformRead-only property that returns true if the current transform can be reused
CanTransformMultipleBlocksRead-only property that returns true if multiple blocks can be transformed
HashRead-only property that returns the calculated hash code
HashSizeRead-only property that returns, in bits, the size of the computed hash code
InputBlockSizeRead-only property that returns the size of the input block
OutputBlockSizeGets the size of the output block

Different Families of Hash Algorithms
There are several families of hash algorithms:
  • Message Digest (MD), with different versions such as MD2, MD4, and the current one called MD5: The resulting hash size for MD5 is 128 bits. Choose this algorithm only if you work with legacy applications. Use instead SHA256 or SHA512 because they offer better security and performance. Implementations of this algorithm must inherit from the System.Security.Cryptography.MD5 abstract class. There are two concrete implementations in .NET 4.5: the CAPI implementation in the MD5CryptoServiceProvider class and the CNG one in the MD5Cng class.
  • RACE Integrity Primitives Evaluation Message Digest (RIPEMD): This family of algorithms has resulting hash sizes of 128, 160, 256, and 320 bits. The version implemented in .NET is the one with the hash size of 160 bits. Choose this algorithm only if you work with legacy applications. Use instead SHA256 or SHA512 because they offer better security and performance. Implementations of this algorithm must inherit from the System.Security.Cryptography.RIPEMD160 abstract class. There is only one implementation of this algorithm in .NET 4.5, the managed one in the RIPEMD160Managed class.
  • SHA-1 is the second implementation of the Secure Hash Algorithm designed by National Security Agency (NSA). The first implementation was called SHA-0, but it proved to have errors, which were corrected by SHA-1. The resulting hash size for SHA-1 is 160 bits. Implementations of this algorithm must inherit from the System.Security.Cryptography.SHA1 abstract class. There are three concrete implementations in .NET 4.5: the managed implementation in the SHA1Managed class, the CAPI implementation in the SHA1CryptoServiceProvider class, and the CNG one in the SHA1Cng class.
  • SHA-2 is the third implementation of SHA by NSA, which addresses some of the vulnerabilities found in SHA-1. This family of algorithms has resulting hash sizes of 224, 256, 384, and 512 bits. .NET is not implementing the 224 bit hash size version of the algorithm. Implementations of this algorithm must inherit from the System.Security.Cryptography.SHA256 for the 256-bit size hash, the System.Security.Cryptography.SHA384 for the 384 bits size hash, and the System.Security.Cryptography.SHA512 for the 512-bit size hash abstract classes. All three versions are implemented as managed, CAPI wrappers and CNG wrappers.

Table 12-8: System.Security.Cryptography.HashAlgorithm Methods

MethodDescription
ClearReleases all resources used by the HashAlgorithm class.
ComputeHash(Byte[])Calculates the hash for the a byte array.
ComputeHash(Stream)Calculates the hash for the a Stream object.
ComputeHash(Byte[], Int32, Int32)Calculates the hash for a region of a byte array.
Create()This static method creates a new cryptographic using the default algorithm, which in .NET 4.5 is SHA1.
Create(String)This static method creates a new hash algorithm object using the specified algorithm. The name of the algorithm can be either one of the names on the left column of Table 12-6 or the name of the type itself, with or without the namespace.
TransformBlockCalculates the hash for the specified region of the inputBuffer byte array and copies the result to the specified region of the outputBuffer byte array.
TransformFinalBlockCalculates the hash for the specified region of the inputBuffer byte array.

Normally, you use only Create and the ComputeHash methods. TransformBlock and TransformFinalBlock methods are used when you want to compute the hash for portions of your data.


BEST PRACTICES: Implementing Your Own Algorithm
If you check the online documentation on MSDN for the HashAlgorithm class, you can notice three extra methods not mentioned on the Table 12-8. They are HashCore, HashFinal, and Initialize. All three methods are abstract methods that need to be implemented by the implementers of the hash algorithm. This is the way Microsoft implemented the existing algorithms. Although it sounds tempting, you should not start implementing your own hash algorithms if this is not what your main business is. You should instead use the existing implementations.

The keyed hashing algorithms inherit from the System.Security.Cryptography.KeyedHashAlgorithm class, which has only one extra property compared with the HashAlgorithm class, as shown in Table 12-9.

Table 12-9: System.Security.Cryptography.KeyedHashAlgorithm Properties

PropertyDescription
KeyRead/write property representing the key to be used by the hash algorithm. If you attempt to change the key after the hashing has begun, a CryptographicException is thrown.

The workflow of hashing data is as follows:

1. Create a hashing algorithm object.
2. Set the hashing key if the algorithm used is a keyed one.
3. Call the ComputeHash method.
4. Save the hash of the data.
The code should look something like this:
string ComputeHash(string input)
{
    HashAlgorithm sha = SHA256.Create();
    byte[] hashData = sha.ComputeHash(Encoding.Default.GetBytes(input));
    return Convert.ToBase64String(hashData);
}

The workflow of verifying a hash for data follows:

1. Create a hashing algorithm object using the same algorithm you used for hashing the data.
2. If a hashing keyed algorithm was used, set the key to the same value used for hashing.
3. Extract the original hash of the data.
4. Call the ComputeHash method.
5. Compare the extracted hash with the computed one. If they are the same, it means that the data wasn’t changed.
bool VerifyHash(string input, string hashValue) {

    HashAlgorithm sha = SHA256.Create();
    byte[] hashData = sha.ComputeHash(Encoding.Default.GetBytes(input));
    return Convert.ToBase64String(hashData) == hashValue;
}

Managing and Creating Certificates

Although the communication might seem secure at times, when two parties communicate, they need to make sure that they are talking with the right partner. For instance, when you want to do a bank transaction via the Internet, you need to make sure that you are on your bank site and not on some website that is spoofing the identity of your bank. You also want to know that the communication is secured. For web applications there are two protocols that solve this problem: Transport Layer Security (TLS) and Secure Socket Layer (SSL).


COMMON MISTAKES: Avoiding Convert.ToBase64String
The Convert.ToBase64String transforms the input byte array into a string-encoded base64. There is no need to use this function. The only reason we use it here is just to make it easier to compare the result of the hashing functions. The alternative would have been to write a function that compares two arrays if they are the same in both length and content. The way the two previous methods are used resembles the following code:
public static void Run() {

    string input = "Data to be hashed!";
    string hash = ComputeHash(input);
    bool sameHash = VerifyHash(input, hash);

    Console.WriteLine("Input:{0}", input);
    Console.WriteLine("Hash: {0}", hash);
    Console.WriteLine("Same hash: {0}", sameHash);
}
The output of this code should look exactly like the one in Figure 12-1.

Figure 12-1: Output of string hashing

c12f001.tif

Both encrypt data and ensure data authenticity. For authenticity they use Public Key Infrastructure (PKI) (the infrastructure needed to handle digital certificates). PKI uses a notion called Certificate Authority (CA). A CA is an entity that issues digital certificates. Digital certificates bind a public key with an identity. By doing this, when two parties wants to communicate and the sending party wants to make sure that it talks with the right party, the sending party can use PKI to verify the identity of the receiving party.

The principle is simple. You can either choose to trust the other party directly, but this might become cumbersome if you have to do that for every party, or you can choose to trust a third party instead that only verifies the identity of different entities. This second choice is a hierarchic one, meaning the number of entities you choose to trust is limited. Those entities can verify the identity of other entities, which become trusted entities and so on, until one of them will trust the entity you want to communicate with and confirm its identity.

The top-level certificates that you choose to trust are called root certificates. Normally, you won’t add any root certificates. They come with your Windows installation or via Windows Updates. If you want to see which roots certificates your computer is set up to trust by default, go to Control Panel and open the Internet Options dialog. From there open the Content tab, and press the Certificates button; then choose the Trusted Root Certification Authorities tab. A dialog similar with the one in Figure 12-2 appears. As you can see, the list is long, but it can save you a lot of trouble if you want to have secure communication.

Figure 12-2: The root certificate authorities list

c12f002.tif

If a site now wants to guarantee its identity, it must verify only its identity with one of the entities you chose to trust. One example is Microsoft. On your computer, as you can see in the previous figure, there is a Root Certificate Authority called GTE CyberTrust Global Root. If you point your browser to https://www.microsoft.com (note the https) and you want to see the certificate of the site and go to the Certification Path tab, a dialog similar with the one in Figure 12-3 appears.

As you can see, www.microsoft.com is trusted by Microsoft Secure Server Authority, which is trusted by Microsoft Internet Authority, which is trusted by GTE CyberTrust Global Root, which you can choose to trust, as one of your Root Certificate Authorities.

The standard used by the PKI is X.509, which specifies the format the PKIs, the Certificate Revocation List (CLR), attributes for the certificates, and how to validate the certificate path.

.NET Framework implements the X.509 standard, and all the classes needed to create and manage the certificates are defined in the System.Security.Cryptography.X509Certificates namespace. If you have a certificate with the private key installed locally, you can use it to decrypt data encrypted with the corresponding public key.

Figure 12-3: SSL certificate info for Microsoft.com

c12f003.tif

NOTE Microsoft offers a tool called Makecert.exe (Certificate Creation Tool) that can you can use to create certificates. You can find more information about this tool in the “Additional Reading and Resources” section at the end of this chapter.

To work with certificates programmatically, you can use the X509Certificate2 class.

Certificates use the notion of certificate stores, which are the places where the certificates are securely kept. In .NET stores are implemented in the X509Store class. Windows offers two store locations represented by the StoreLocation enumeration. The possible values are shown in Table 12-10.

Table 12-10: System.Security.Cryptography.X509Certificates.StoreLocation

Member NameDescription
CurrentUserRepresents the certificate store used by the current user
LocalMachineRepresents the certificate store common to all users on the local machine

Windows offers eight predefined stores represented by the StoreName enumeration. The possible values are shown in Table 12-11.

Table 12-11: System.Security.Cryptography.X509Certificates.StoreName

Member NameDescription
AddressBookCertificate store for other users
AuthRootCertificate store for third-party CAs
CertificateAuthorityCertificate store for intermediate CAs
DisallowedCertificate store for revoked certificates
MyCertificate store for personal certificates
RootCertificate store for trusted root CAs
TrustedPeopleCertificate store for directly trusted people and resources
TrustedPublisherCertificate store for directly trusted publishers

X509Store class offers a string property called Name, which you can use if you want to create your own store.

The following program shows how you can use the X509Store to show the X509Certificates information similar to the one in Figure 12-2.

using System;
using System.Security.Cryptography.X509Certificates;

namespace Encryption {
    class CertificateTest {

        static void Main() {

            X509Store store = new X509Store(StoreName.Root, 
                                            StoreLocation.LocalMachine);
            store.Open(OpenFlags.ReadOnly);

            Console.WriteLine("Friendly Name					 Expiration date");
            foreach (X509Certificate2 certificate in store.Certificates) {
                Console.WriteLine("{0}	{1}", certificate.FriendlyName
                                            , certificate.NotAfter);
            }
            store.Close();
        }

    }
}

This small program starts by opening in read-only mode the root store from the local machine and then prints the name and the expiration date of all the certificates in there.


NOTE Figure 12-2 includes two more fields, called Issued To and Issued By. If you want to obtain this information, it is part of the Subject and Issuer property, respectively. Those are of type X509DistinguishedName. From there you must extract the value of the Common Name (CN) attribute.

Implementing Key Management

Sometimes you might just need to keep data safely without thinking too much about the algorithm used or how it is implemented. For example, in order for an encryption algorithm to be effective, you will need to protect the shared secret for a symmetric algorithm, and the private key for an asymmetric algorithm.

To solve this kind of problems, .NET Framework offers in the System.Security.Cryptography namespace one static class named ProtectedData. This class has two static methods: Protect and Unprotect. As the name implies, the first one is used to encrypt the data, and the second one is used to decrypt the data.

Their signatures are as follows:

public static byte[] Protect(
    byte[] userData,
    byte[] optionalEntropy,
    DataProtectionScope scope
)
public static byte[] Unprotect(
    byte[] encryptedData,
    byte[] optionalEntropy,
    DataProtectionScope scope
)

As you can see, both methods accept three parameters. The first parameter represents the data to by encrypted (userData) or decrypted (encryptedData); the second one is called optionalEntropy, and it is used to increase the complexity of the encrypted data; and the last one is called scope and is of type DataProtectionScope. The scope parameter specifies who can decrypt the data. The values it can take are: DataProtectionScope.CurrentUser and DataProtectionScope.LocalMachine. First, one specifies that only the current user can decrypt the encrypted data, and the second one specifies that any logged-in user on the local machine will be able to decrypt the data.

Protect returns the encrypted data, and Unprotect returns the unencrypted data.


REAL-WORLD CASE SCENARIO: Encrypt data using the ProtectData class
Use the ProtectedData class to write a method called ProtectString that takes a string in clear text and encrypts it so that only the current user can decrypt it.
Solution
A possible solution might look like this:
byte[] ProtectString(string data) {

    byte[] userData = System.Text.Encoding.Default.GetBytes(data);
    byte[] encryptedData = ProtectedData.Protect(userData, null, 
                                    DataProtectionScope.CurrentUser);
    return encryptedData;
}
First you convert the string into a byte array, and then you call the ProtectedData.Protect method to encrypt the data.

Choosing When to Use Which

Microsoft recommends the following algorithms to be used in the following situations:

  • For data privacy, use Aes.
  • For data integrity, use HMACSHA256 or HMACSHA512.
  • For digital signatures, use RSA or ECDsa.
  • For key exchange, use RSA or ECDiffieHellman.
  • For random number generation, use RNGCryptoServiceProvider.
  • For generating a key from a password, use Rfc2898DeriveBytes.

You can find a link to more information about the Cryptography Model on MSDN in the “Additional Reading and Resources” section.


Code Lab: Using the RSA asymmetric algorithm [Chapter12AsymmetricRSASample.cs]
Consider the following complete piece of code:
using System;
using System.Text;
using System.Security.Cryptography;

namespace EncryptionSamples.Asymmetric
{
    public class RSASample
    {
        public static void Main() {

            string keyContainerName = "MyKeyContainer";
            string clearText = "This is the data we want to encrypt!";
            CspParameters cspParams = new CspParameters();
            cspParams.KeyContainerName = keyContainerName;

            RSAParameters publicKey;
            RSAParameters privateKey;

            using (var rsa = new RSACryptoServiceProvider(cspParams)) {

                rsa.PersistKeyInCsp = true;
                publicKey = rsa.ExportParameters(false);
                privateKey = rsa.ExportParameters(true);

                rsa.Clear();
            }

            byte[] encrypted = EncryptUsingRSAParam(clearText, publicKey);
            string decrypted = DecryptUsingRSAParam(encrypted, privateKey);

            Console.WriteLine("Asymmetric RSA - Using RSA Params");
            Console.WriteLine("Encrypted:{0}", Convert.ToBase64String(encrypted));
            Console.WriteLine("Decrypted:{0}", decrypted);

            Console.WriteLine("Asymmetric RSA - Using Persistent Key Container");
            encrypted = EncryptUsingContainer(clearText, keyContainerName);
            decrypted = DecryptUsingContainer(encrypted, keyContainerName);

            Console.WriteLine("Encrypted:{0}", Convert.ToBase64String(encrypted));
            Console.WriteLine("Decrypted:{0}", decrypted);

        }

        static byte[] EncryptUsingRSAParam(string value, RSAParameters rsaKeyInfo)
        {
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            {
                rsa.ImportParameters(rsaKeyInfo);
                byte[] encodedData = Encoding.Default.GetBytes(value);
                byte[] encryptedData = rsa.Encrypt(encodedData, true);

                rsa.Clear();
                return encryptedData;
            }
        }

        static string DecryptUsingRSAParam(byte[] encryptedData, 
                                          RSAParameters rsaKeyInfo) {
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            {
                rsa.ImportParameters(rsaKeyInfo);
                byte[] decryptedData = rsa.Decrypt(encryptedData, true);
                string decryptedValue = Encoding.Default.GetString(decryptedData);

                rsa.Clear();
                return decryptedValue;
            }
        }

        static byte[] EncryptUsingContainer(string value, string containerName)
        {
            CspParameters cspParams = new CspParameters();
            cspParams.KeyContainerName = containerName;
            using (var rsa = new RSACryptoServiceProvider(cspParams))
            {
                byte[] encodedData = System.Text.Encoding.Default.GetBytes(value);
                byte[] encryptedData = rsa.Encrypt(encodedData, true);

                rsa.Clear();
                return encryptedData;
            }
        }

        static string DecryptUsingContainer(byte[] encryptedData, string container)
        {
            CspParameters cspParams = new CspParameters();
            cspParams.KeyContainerName = container;
            using (var rsa = new RSACryptoServiceProvider(cspParams))
            {
                byte[] decryptedData = rsa.Decrypt(encryptedData, true);
                string decryptedValue = Encoding.Default.GetString(decryptedData);

                rsa.Clear();
                return decryptedValue;            }
        }
    }
}
Code Lab Analysis
Start by generating new encryption keys pair, by creating a new instance of the RSACryptoServiceProvider and not specifying any keys. This object won’t be used for encryption or decryption, only to get or create the keys. Because you send the cspParams as a parameter and set the PersistKeyInCsp property to true (which is true by default, anyway, you are just being explicit), specify that you want the keys to be saved in a container called MyKeyContainer. When you run the code second time, it uses the keys that exist in that container. By setting PersistKeyInCsp property to false, when you call the Clear method, the parameter is removed from the container.
By calling ExportParameters on the RSA object, you can export the parameters necessary to re-create the public key (when you invoke it with false as a parameter) or both keys (when you invoke it with true as a parameter).
Now examine the EncryptUsingRSAParam method, which takes two parameters: the string to encrypt and an RSAParameters value needed to create the public key.
1. Create a new instance of RSACryptoServiceProvider that implements the RSA algorithm.
2. Call ImportParameters that takes as a parameter the RSAParameters value exported earlier, which can generate the public key.
3. The call to the System.Text.Encoding.Default.GetBytes method transforms the input value from a string into a byte array, so you can prepare it for the Encrypt method.
4. Call Encrypt to encrypt the data. The second parameter is to be set to false only if you run your application on a version of Windows prior to Windows XP; otherwise, set it to true.
5. Call Clear to remove any sensitive data from memory and return the encrypted data in a byte array format.
Now examine the DecryptUsingRSAParam method, which takes two parameters: the encrypted data as a byte array and an RSAParameters value needed to create the public and private key.
1. Create a new instance of RSACryptoServiceProvider that implements the RSA algorithm.
2. Call ImportParameters that takes as a parameter the RSAParameters value exported earlier, which can generate the public and private key. You don’t need the public key here, but it will be created anyway.
3. Call Decrypt to decrypt the data. The second parameter is to be set to false only if you run your application on a version of Windows prior to Windows XP; otherwise, set it to true.
4. The call to the System.Text.Encoding.Default.GetString method is transforming the input value from a byte array into the decrypted string.
5. Call Clear to remove any sensitive data from memory and return the decrypted string.
EncryptUsingContainer and DecryptUsingContainer works in exactly the same manner with the only difference that instead of sending RSAParamaters that are used to generate the keys, you use the same Crypto Service Provider container that you used when you generated the keys.

Managing Assemblies

Compiling projects results in executable files, generally known as assemblies. Assemblies are the building blocks of every .NET applications. They are the fundamental unit for deployment, versioning, reuse, and security.

What Is an Assembly?

An assembly contains Intermediate Language (IL), metadata, and resources. IL is the result of compiling the source code files into a common language that the Common Language Runtime (CLR) understands. CLR has a component called a Just In Time (JIT) compiler that compiles the IL code into binary code to run it on a specific platform, such as Windows. An assembly can be a single-file assembly or a multifile assembly. Because Visual Studio can generate only single-file assemblies, this book does not cover the multifile assembly, but you can find additional information in the “Additional Reading and Resources” section at the end of this chapter.

An assembly is obtained by compiling a project. The resulting assembly could be a process assemblies (EXE files) or library assemblies (DLL files); the only difference between the two of them is the presence of the main entry point in process assemblies. The main entry point indicates which method should be called when the application is started, aka the Main method.

Because an assembly is the unit of deployment of any .NET application, if you want to split your application in smaller units of deployment to make it more manageable and reusable by different products, you need to split the functionality into different assemblies. Every assembly can then represent a module in your application, which can be updated independent of the other projects. The simplest way to implement this kind of modularization is to create different projects, where one project is one module, and group those into one big solution. To differentiate between different versions of the same assembly, you need to use Assembly Versions.

Understanding Assembly Versions

Software evolves, and the way to mark that is by creating new versions, with every new version adding new features, fixing bugs, or being a complete rewrite of the software, hopefully for the better.

The .NET assembly version is implemented as a string, normally made up of four numeric parts separated by dots: Major, Minor, Build number, and Revision. For instance, the current version of .NET is known as 4.5. This is the marketing version number, or what the customers get to see. Instead, the assemblies that were released with this version have the following version string embedded: 4.0.30319.17929, where 4 represents the Major number, 0 the Minor number, 30319 the Build number, and 17929 the Revision. If you choose to have a version number that is in another format, the compiler generates an error. The recommendation is to use the number format to give the assembly version a meaning.


ADVICE FROM THE EXPERTS: Versioning Your Assemblies
Choose the versioning scheme carefully because this can help you in the long run. If you use the recommended way with numbers, it is much easier to decide which version is newer based on the version number. The way to think about the different parts of the version follows.
The Revision is normally a random number used to differentiate two versions that have the same build number. Normally, you must set a new number every time you check in and your project is built.
The Build number is a number that normally increases every day, while you are working on a certain version of your product. This is done normally every night by your nightly build process.
The Minor number is increased with every public release of your product. Normally, two versions of the same assembly having the same major number, but different minor numbers are backward compatible—although this is not always the case. Backward compatibility means that when replacing an assembly with a newer version of the same assembly, the system will still work. The way to achieve this is by adding new features, but never removing existing features or changing the public face of the existing ones.
The Major number is increased every time when you have a major release of your product, either by changing existing features or rewriting the entire application.

You can set the version of your assembly in two ways. First is via your project settings. Right-click your project settings and choose Properties. On the Assembly property page, press the Assembly Information button. A dialog similar to the one in Figure 12-4 appears. The four Assembly Version fields represent the aforementioned parts of the assembly version.

The second way to set the version is to go directly and edit the AssemblyInfo.cs file. To find the file, you have to click the project for which you want to change the version, and then expand the property node, as shown in Figure 12-5.

Figure 12-4: Assembly Information dialog

c12f004.tif

Figure 12-5: Opening the AssemblyInfo.cs file

c12f005.tif

At the end of the file, you can find a line resembling the following code:

[assembly: AssemblyVersion("2.1.42.15")].

As explained in Chapter 8, “Creating and Using Types with Reflection, Custom Attributes, the CodeDOM, and Lambda Expressions,” this is an attribute implemented in the AssemblyVersionAttributeapplied to the assembly that specifies which version the assembly should have.

The Assembly Version is ignored by CLR unless it is used together with the strong names. Strong names are covered in the next section.


Using Different Kinds of Versions
Referring to Figure 12-4, you can see two kinds of versions: Assembly Version and File Version. The corresponding attribute for the file version is called AssemblyFileVersionAttribute.
The CLR doesn’t use the File Version. This information is something that has a meaning for you or your product. Normally, the marketing department cares about the File Version. Although the File Version is normally specified in the same format as the assembly version with a Major, Minor, Build, and Revision number, you can have any string you want. If you use a different format, though, the compiler emits a harmless warning. You cannot set the File Version to a string via the Assembly Information window. You must do it in code by passing the value to the AssemblyFileVersionAttribute attribute.
.Net Framework offers a third attribute for versioning called the AssemblyInformationalVersionAttribute. This is plain text and is normally the value of your product version. If you don’t set it, it will have the value of AssemblyVersionAttribute.
To see the values of the AssemblyFileVersionAttribute and the AssemblyInformationalVersionAttribute, you can compile your assembly, using Windows Explorer navigate to the output folder, and then right-click the assembly. Then choose the Property menu to bring up the file properties window, and choose the Details tab. A dialog similar to the one in Figure 12-6 should appear.

Figure 12-6: File and product version information

c12f006.tif
The value of the AssemblyFileVersionAttribute is represented by the value of the File Version property and the value of AssemblyInformationalVersionAttribute is represented by the value of the Product Version property.

Signing Assemblies Using Strong Names

One of the major problems developers were facing prior to .NET was the DLL hell. DLL stands for dynamic linked library, and it was a way to share code and optimize the space an application takes on the disk. The problem that the DLL hell refers to could appear when different applications used the same library. If they were compiled against different version of the same library, the one that was installed last would override the existing DLL. If there were compatibility issues between different versions of the same assembly, one application started to act erratic, or even refused to start. .NET solved this problem by introducing a new concept called side-by-side versioning. For this to work, you must make a distinction between different versions of the same assembly, while assuring that the two different versions are coming from the same source.

In .NET the name of the assembly consists of four parts:

  • The friendly name
  • The version
  • The culture
  • The public key token

The friendly name is what you previously called the library name. In the normal situation, this is the name of the assembly file without the extension, so if your assembly file were called ProgrammingCSharp.dll, then the assembly-friendly name would be ProgrammingCSharp.

The version is the Assembly Version discussed in the previous section.

The culture represents the culture of the assembly. The culture is used when you want to localize your application for different markets. To localize your application, you create one assembly containing the code and one assembly for each region you want your application to be localized for. The localized assemblies contain only resources for that specific region such as translated strings or specific images. The executable assembly always has the culture set to neutral. If you try to set it to something else, an error will be generated by the complier. Globalization and localization are outside the scope of this exam, but if you want to know more about this, please see the “Additional Reading and Resources” section for a link to the MSDN list of articles about this subject.

For an assembly to have a strong name, it needs to be signed with a public/private pair of keys using the techniques discussed in the previous section. The private key is used to digitally sign the assembly, whereas the public key is used to verify the signature. The Public Key Token is a 64-bit hash of the assembly public key. The reason that the .NET uses a hash of the signature instead of the signature itself is to save space because the signature is much bigger than 64 bits. An assembly that is not digitally signed can have the Public Key Token set to null.

As mentioned earlier, to sign an assembly, you need a pair of public/private keys. You can generate those keys in two ways. The first way is to create the file via Visual Studio. To do that, you right-click the project and then choose Properties. On the properties page, choose the Signing tab. There, check the Sign the Assembly check box, as shown in Figure 12-7.

Figure 12-7: The Signing tab in Visual Studio 2012

c12f007.tif

When you do that, a dialog similar to the one shown in Figure 12-8 appears.

Figure 12-8: Create Strong Name Key dialog

c12f008.tif

As you can see, there you can specify the name of the key file, a password that you can use to protect the private key in the file, and the algorithm to be used for the signature. The choices for the algorithm are sha256RSA and sha1RSA. After you create the file, it will be added to your project. When your project is opened by another user, or by you on another machine, you must to introduce the password you just set in the previous step.


BEST PRACTICES: Signing Assemblies
It is recommend that you use a strong password for the key file and make the password available only for a few selected developers—or even better, for the person responsible for the deployment of your applications. If the password is compromised, it will be easy for someone else to create assemblies and distribute them as if they come from you. To keep the password secret but still have other developers working with your application, you must use Delay sign only. Delay sign is outside the scope of this exam. For extra information about the Delay sign process, look at the “Additional Reading and Resources” section.

The second way to generate a key is to use the sn.exe program. To do that, you need to open the Developer Command Prompt for VS2012. From the command line, run the following:

sn.exe -k myFile.snk

The command output should look similar to the one in Figure 12-9.

Figure 12-9: Sn.exe output

c12f009.tif

After you generate the file, instead of choosing <New…> in Visual Studio on the Signing tab, you need to choose <Browse…> and choose the newly created file. The downside of using sn.exe is that you cannot specify the password, but sn.exe is used in the delay sign scenario, to extract the public key from the file, or to sign the assembly before the deployment. You can find more information about sn.exe in the “Additional Reading and Resources” section.


NOTE Delay signing is not required for the exam but you should to read the article specified in the “Additional Reading and Resources” section about this subject.

When an assembly needs to be digitally signed, the compiler signs the assembly using the private key and embeds the public key in the assembly for later verification by other assemblies that refer to it. The next step is to hash the public key to create the Public Key Token, and embed that one as well into the assembly.

In conclusion, the assembly name is not only the filename or the friendly name. The complete name of an assembly is known as the Fully Qualified Name (FQN). For instance the FQN of the System assembly in .NET 4.5 is

System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

Implementing Side-by-Side Versioning

Strong naming would have only little value without the power of running side-by-side different versions of the same assembly. Consider the following scenario.

In your organization there are two teams working on two different products, but both of them need to use some common functionality. As mentioned earlier, the best way to deal with this kind of situation is to move the common code into a separate project, creating a separate assembly that can be used by both teams. Everything works fine; they deploy their products, and there is only one copy of the common assembly per machine, instead of having one per product. After a while the first team might need some additional functionality to be added into the common assembly, but the second team is “not there yet.” Without the side-by-side versioning, you must keep a copy of the assembly private for every installation. With two products this might be an acceptable solution, but if you have more products, or if you are a third-party vendor, then it will become much harder to maintain this kind of solution.

Side-by-side versioning works only with strong name assemblies because it requires them to be deployed into the Global Assembly Cache (GAC). GAC is covered in the next section. But for now, see how side-by-side versioning works.

When you add a reference to an assembly, information about the referred assembly is added to the manifest file of the assembly. The manifest is embedded into the assembly as part of the metadata. Inside the manifest file every referenced assembly is represented by a block resembling the following code:

// Metadata version: v4.0.30319
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .zV.4..
  .ver 4:0:0:0
}
.assembly extern CommonFunctionality
{
  .ver 1:0:0:0
}
.assembly 'Programming CSharp Assembly'
{ … }

The first .assembly extern line represents the reference to the mscorlib assembly, which is referenced by default by all .NET applications because this one contains all the definitions for all the base data types in .NET. The version of the assembly is 4.0.0.0 and the Public Key Token is (B7 7A 5C 56 19 34 E0 89). The second .assembly extern represents a reference to an assembly called CommonFunctionality that has version 1.0.0.0 and is not signed. If the second assembly would have been signed as well, the manifest information would have been something like this:

// Metadata version: v4.0.30319
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .zV.4..
  .ver 4:0:0:0
}
.assembly extern CommonFunctionality
{
  .publickeytoken = (48 27 85 37 58 E3 97 63 )                         // H'.7X..c
  .ver 1:0:0:0
}
.assembly 'Programming CSharp Assembly'
{ …}

This information is shown using the Intermediate Language Disassembler (ildasm.exe) application. To see that, open the Developer Command Prompt for VS2012 and go the output folder of your assembly (normally <Your Project Folder>inDebug). From there type ildasm.exe <Your assembly name>.exe. The command output should look similar with the one in Figure 12-10.

Figure 12-10: Invoking ildasm.exe

c12f010.tif

This opens the IL DASM application, as shown in Figure 12-11.

From there you can double-click the MANIFEST node to open the manifest file, as shown in Figure 12-12.

Figure 12-11: The IL DASM main application window

c12f011.tif

Figure 12-12: The IL DASM MANIFEST window

c12f012.tif

Now when you try to run the application, the run time tries to locate the right assemblies for you.

If an assembly is not signed, CLR looks into the local folder of the application to try to find an assembly based only on the assembly name and assembly filename, ignoring the assembly version.

If the assembly is signed, CLR first tries to see if there are any policies specified for that particular assembly that can instruct CLR to use a different version. This process of replacing an assembly without requiring to recompile the assemblies that use it is called binding redirection. After the version is established, CLR first looks in the GAC for that particular version of the assembly, and if it is not there, it looks in the local folder of the application to try to find the right assembly. When the CLR finds the assembly, it verifies the digital signature of the assembly to ensure that the assembly wasn’t tampered with. If the CLR finds the assembly but doesn’t find the right version for it or the signature doesn’t match, it throws a System.IO.FileLoadException. If the CLR cannot find the assembly, a System.IO.FileNotFoundException is thrown.


NOTE Two different exceptions are thrown. FileLoadException is only thrown if the CLR doesn’t find the right version of a signed assembly or the assembly was tampered with. FileNotFoundException is thrown if the assembly is not found at all.

How CLR does probing on the local folders is outside the scope of this exam, but you can find a link on the “Additional Reading and Resources” section.


Processor Architecture
Since .NET 2.0, Microsoft added the processor architecture to the assembly name, which is optional. This means that you can have two identical versions of the same assembly that differ only by the ProcessorArchitecture attribute. The values permitted for the ProcessorArchitecture are described by the System.Reflection.ProcessorArchitecture enumeration. The values and their meaning are shown in Table 12-12.

Table 12-12: System.Reflection.ProcessorArchitecture Members

Member NameDescription
NoneNot specified or unknown
MSILProcessor independent
X86A 32-bit Intel processor
IA64A 64-bit Intel processor only
Amd64A 64-bit AMD processor only
ArmAn ARM processor
The processor architecture attribute is shown only if it has a value different than None, so if the System assembly that you saw earlier would set the ProcessorArchitecture to MSIL, the assembly name would have been
System, Version=4.0.0.0, Culture=neutral, 
    PublicKeyToken=b77a5c561934e089, ProcessorArchitecture=MSIL

Adding Assemblies to the Global Assembly Cache

As mentioned earlier, only signed assemblies can be placed in the GAC, but what is the GAC?

If your application is a non-.NET application and uses dynamic link libraries, it looks for them either on the folder that the application was installed on or on the folders specified in the PATH environment variable. If you want to see the value of the PATH environment variable, open the Developer Command Prompt for VS2012 and run the following commend:

set PATH

This outputs something similar with the text shown in Figure 12-13.

Figure 12-13: Output from set PATH command

c12f013.tif

Instead, .NET uses the concept of GAC, which is an assembly repository that acts as a cache for shared libraries. The location of the GAC is <Your Windows Installation Folder>assembly. If you want to see which assemblies are in your GAC, you can open Windows Explorer and navigate to this folder, and you see something resembling Figure 12-14.

As you can see, the list shows all the aforementioned attributes of an assembly: Assembly Name, Version, Culture, Public Key Token, and Processor Architecture. The empty Culture means that this is the neutral culture, whereas the empty Processor Architecture means NONE for the processor architecture. There is no empty Public Key Token because only strong named (digitally signed) assemblies can be deployed on the GAC.

Before talking about how to add assemblies to GAC, you’ll first explore the advantages of using the GAC.

Figure 12-14: The GAC view from Windows Explorer

c12f014.tif

First, two advantages already mentioned are side-by-side versioning and sharing of assemblies. If an assembly is already loaded in memory, the CLR does not load it again.

Another advantage is that the assembly signature is verified before it is installed on the GAC, so when the assembly will be loaded by the CLR when it is executed, it skips verification improving the startup time of your application.

Last is the possibility to precompile the assemblies, so they won’t need to be compiled by the JIT compiler every time you load them, as discussed at the beginning of this section. This can speed up the startup process even more. To do that you must run a utility called ngen.exe (Native Image Generator), which is outside the scope of this book, but as always you can find more information in the “Additional Reading and Resources” section.

You can install an assembly in the GAC in two ways.

First, use an installer that can do this. This is the preferred way. By using the installer you ensure that the installation is atomic, meaning that it either succeeds to install all the components or none at all, and it gives you the possibility to uninstall it later in the same manner. When an assembly is installed this way, the installer adds the assembly if is not there, or if it is there, it increases the reference count only, making sure that the assembly won’t be uninstalled if it is still used when you uninstall the application.

The second way is to use the utility called gacutil.exe, which can be run as follows:

gacutil.exe [options] [assemblyName | assemblyPath | assemblyListFile]

Table 12-13 shows the most common parameters for the gacutil.exe. For a complete reference, refer to the “Additional Reading and Resources” section.

Table 12-13: gacutil.exe Usage

OptionDescription
/iAdds an assembly to the GAC.
/uRemoves an assembly from the GAC.
/l [assemblyName]Lists all the assemblies in the GAC. Specifying the assemblyName parameter will list only the assemblies matching that name.
/rTraces references to an assembly by increasing a counter on every install and decreasing the counter on uninstall. Specify this option with the /i, /il, /u, or /ul options. If an assembly is traced, it will be removed from GAC only when the counter is 0.

Summary

In this chapter you learned how to encrypt sensitive data in your application using both symmetric and asymmetric algorithms. You looked at some of the existing algorithms, how you can manage and create certificates, and how to manage the encryption keys. Then you looked at how to hash data to ensure that the information is not modified and you ended by looking at how you can encrypt streams of data.

In this chapter you learned as well about assemblies, what they are, and how you can create strong-named assemblies to use several side-by-side versions. You then looked at the options available to add an assembly to the GAC, and how you can create a WINMD assembly.

Chapter Test Questions

The following questions are similar to the types of questions you will find on Exam 70-483. Read each question carefully and select the answer or answers that represent the best solution to the problem. You can find the answers in Appendix A, “Answers to Chapter Test Questions.”

1. You are a developer at company xyx. You have been asked to implement a method to safely save and restore data on the local machine. What kind of algorithm best fits the requirements?
a. Symmetric algorithm
b. Asymmetric algorithm
c. Hashing algorithm
d. X509Certificate
e. None of the above
2. You are a developer at the company xyx. You have been asked to implement a method to safely send data to another machine. What kind of algorithm best fits the requirements?
a. Symmetric algorithm
b. Asymmetric algorithm
c. Hashing algorithm
d. X509Certificate
e. None of the above
3. You are a developer at the company xyx. You have been asked to implement a method to handle password encryption without offering the possibility to restore the password. What kind of algorithm best fits the requirements?
a. Symmetric algorithm
b. Asymmetric algorithm
c. Hashing algorithm
d. X509Certificate
e. None of the above
4. Which of the following code snippets will you use to calculate the secure hash of a byte array called userData? If you already have created an algorithm object called sha.
a. userData.GetHashCode(sha);
b. sha.ComputeHash(userData);
c. sha.GetHash(userData);
d. sha.EncryptHash(userData);
5. Which of the following code snippets will you use to encrypt an array called userData that can be decrypted by anyone logged in on the current machine, and without using any entropy?
a. ProtectedData.Protect(userData, null, DataProtectionScope.CurrentUser);
b. ProtectedData.Protect(userData, null, DataProtectionScope.LocalMachine);
c. ProtectedData.Encrypt(userData, null, DataProtectionScope.CurrentUser);
d. ProtectedData.Unprotect(userData, null, DataProtectionScope.LocalMachine);
6. Which of the following code will you use to encrypt an array called encryptedData that can be encrypted by the current user, and without using any entropy?
a. ProtectedData.Unprotect(encryptedData, null, DataProtectionScope.CurrentUser);
b. ProtectedData.Protect(encryptedData, null, DataProtectionScope.LocalMachine);
c. ProtectedData.Decrypt(encryptedData, null, DataProtectionScope.CurrentUser);
d. ProtectedData.Unprotect(encryptedData, null, DataProtectionScope.LocalMachine);
7. What describes a strong name assembly?
a. Name
b. Version
c. Public key token
d. Culture
e. Processor Architecture
f. All the above
8. How can you deploy a strong named assembly?
a. By running gacutil.exe
b. By creating an installer
c. By running asm.exe
d. By copying the file to the Bin folder of the application
e. By running regsvcs.exe
9. How can you deploy a private assembly?
a. By running gacutil.exe
b. By adding a reference to the assembly in Visual Studio
c. By copying the file in the Bin folder of the application
d. By copying the file in C:Windows folder
10. What is a strong name assembly?
a. An assembly with the name marked as bold
b. An assembly with a major and minor version specified
c. An assembly with a full version specified
d. An assembly with the culture info specified
e. A signed assembly

Additional Reading and Resources

Here are some additional useful resources to help you understand the topics presented in this chapter:

Cryptography Model http://msdn.microsoft.com/en-us/library/0ss79b2x.aspx

Makecert.exe (Certificate Creation Tool)http://msdn.microsoft.com/en-us/library/bfsktky3.aspx

Working with Assemblies http://msdn.microsoft.com/en-us/library/8wxf689z.aspx

Globalization and Localization http://msdn.microsoft.com/en-us/library/hh965328.aspx

Delay signing of an assembly http://msdn.microsoft.com/en-us/library/t07a3dye.aspx

Ngen.exe (Native Image Generator) http://msdn.microsoft.com/en-us/library/6t9t5wcf(v=vs.110).aspx

Sn.exe (Strong Name Tool) http://msdn.microsoft.com/en-us/library/k5b5tt23(v=vs.110).aspx

How CLR Locates Assemblies http://msdn.microsoft.com/en-us/library/yx7xezcf.aspx

Gacutil.exe (Global Assembly Cache Tool) http://msdn.microsoft.com/en-us/library/ex0ss12c.aspx

Cheat Sheet

This cheat sheet is designed as a way for you to quickly study the key points of this chapter.

Choosing an encryption algorithm

  • If you need to encrypt data that is used locally, or you have a secure way to distribute the encryption key, use the symmetric encryption.
  • If you don’t have a secure way to send the encryption key data between parties, then asymmetric encryption is recommended.
  • If you need only to ensure integrity of the data, use a hashing algorithm.
  • If you need to ensure both integrity and authenticity, choose a MAC algorithm.

Symmetric encryption

  • Based on a common key called shared secret.
  • It needs an initialization vector (IV) that doesn’t need to be secret but is used to encrypt the first block of data.
  • You use it by instantiating a symmetric algorithm object and then calling CreateEncryptor or CreateDecryptor.
  • The encryptor/decryptor is then used with either by calling directly the TransformFinalBlock method or by sending it to a CryptoStream.

Asymmetric encryption

  • It is based on a pair of complementary keys. Encrypted data with one key can be decrypted only with the other key.
  • One key is kept secret and is called a private key; the other one is made available to anyone that wants to encrypt data, or verify encrypted data, and it is called a public key.

Hashing

  • Mapping binary data of a variable length to a fixed size binary data, called hash.
  • When you need to make sure that data is not modified while transferred, you can calculate the cryptographic hash and send it together with the data to be verified by the receiving party.
  • The two commonly used algorithms are SHA256 and SHA512 with resulting hashes of 256 and 512 bits, respectively (32 and 64 bytes).

Key management

  • Symmetric keys can be exchanged using asymmetric algorithms.
  • Asymmetric private keys can be secured either by using certificates or by using Crypto Service Providers containers.

Assembly version

  • An assembly version is specified by four parts: Major, Minor, Build, and Revision.

Strong name

  • An assembly that is digitally signed is called a strong named assembly.
  • A strong name has five parts: Friendly Name, Version, Culture, Public Key Token, and Processor Architecture.

GAC

  • Stands for Global Assembly Cache.
  • A repository to share .NET assemblies.
  • Only strong named assemblies can be deployed on the GAC.
  • Several versions of the same assembly can be deployed on the GAC at the same time.

Review of Key Terms

assembly An assembly is the unit of reuse, deployment, versioning, and security.

asymmetric encryption (public key) A cryptographic algorithm that uses two complementary keys, one for encryption and one for decryption. Data encrypted with the public key can only be decrypted using the private key.

Certificate Authority (CA) An entity that issues digital certificates.

Certificate Revocation List (CRL) A list of digital certificates that has been revoked for various reasons. You shouldn’t use a certificate if it is revoked.

certificate stores A special storage location on your computer, used to store encryption certificates.

Common Language Runtime (CLR) CLR is the component of .NET Framework responsible for running .NET applications and managing their running environment.

cryptography The practice and study of techniques for secure communication.

decryption The process of decoding previously encrypted data so that it can be used by your application.

encryption The process of encoding data so that it cannot be read by an unauthorized person.

Global Assembly Cache (GAC) GAC is a machine-wide code cache.

hash bucket A data structure that holds items that share the same hash value.

hashing Used to map data structures of variable length, to fixed size data structures. Hashing the same data using the same algorithm will always yield the same hash value.

initialization vector (IV) A data array used by the encryption algorithms to encrypt the first data block. The IV doesn’t need to be kept secret.

Intermediate Language (IL) The result of compiling a .NET application from source code.

Just In Time compiler (JIT) A component of the .NET that transforms the IL into binary code that can be run on the target platform.

Message Authentication Code (MAC) A family of cryptographic algorithms used to provide data integrity and authenticity.

private key The public and private keys are a pair of complementary keys used together in the asymmetric encryption. Data encrypted with the private key can only be decrypted using the public key, and data encrypted with the public key can only be decrypted using the private key.

public key See private key.

Public Key Infrastructure (PKI) The infrastructure needed to handle digital certificates.

Secured Hash Algorithm (SHA) A family of cryptographic algorithms used to calculate hashes published by NIST.

Secure Socket Layer (SSL) A cryptographic protocol used for secure communication over the Internet.

symmetric encryption (shared secret) A cryptographic algorithm that uses the same key for both encryption and decryption of data.

Transport Layer Security (TLS) A cryptographic protocol used for secure communication over the Internet, the successor of SSL.


EXAM TIPS AND TRICKS
The Review of Key Terms and the Cheat Sheet for this chapter can be printed to help you study. You can find these files in the ZIP file for this chapter at www.wrox.com/remtitle.cgi?isbn=1118612094 on the Download Code tab.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.99.71