Chapter 16. Protecting Sensitive Data with Encryption

 

The only thing more frightening than a programmer with a screwdriver or a hardware engineer with a program is a user with a pair of wire cutters and the root password.

 
 --Elizabeth Zwicky

With the highly distributed software populating the computing world these days, there is often a need to protect sensitive data so that it is accessible only by a select group of people. Some applications are network- or Internet-driven, and they must maintain secure communication so that malicious attackers cannot modify incoming and outgoing packets. Other applications need to store sensitive data locally in the file system or a remote database in a format that is unreadable by humans.

Developers look towards encryption to accomplish this feat, but very few of them implement it correctly. These developers throw around buzzwords like “128-bit encryption” and claim that their applications are secure, when, in fact, they have introduced security flaws that can be exploited by anyone with the knowledge to do so.

Some developers also think that they can roll their own implementation of a particular algorithm and claim that it works correctly. Just because you see data that you think is encrypted does not actually mean that it is. Many of these developers could probably hand their implementations over to a knowledgeable cryptologist who would discover flaws. Experts were hired to implement the algorithms provided in the .NET Class Framework, and it is advisable to use their implementations because they more than likely have a better understanding of encryption than you do.

This chapter covers the encryption ciphers available in the .NET Class Framework, how to implement encryption properly, and common pitfalls and issues when securing data with encryption.

Encryption Rudiments

Encryption is a complex subject, and it is impossible to cover all aspects in a single chapter. While still quite ambitious, in this chapter, I will attempt to summarize the common characteristics of encryption and present a solution that takes care of the majority of the underlying mechanisms and theory behind encryption. This chapter will only cover the usage of algorithm implementations currently provided by the .NET Class Framework, and not how to implement the algorithms yourself.

To fully understand this chapter and encryption in general, we must define a few common characteristics and terms.

Public-Key Encryption

This type of encryption is commonly referred to as asymmetric encryption and uses a public and private key pair to perform encryption or decryption of data. The public key is available to everyone and is used to encrypt data that will be decrypted by the owner of the private key. The private key is kept secure by the owner and is used to decrypt data that has been encrypted with the public key.

Asymmetric encryption is generally only efficient on relatively small sets of data. The .NET Class Framework contains two asymmetric encryption algorithms: Digital Signature Algorithm (DSA) and RSA.

Private-Key Encryption

This type of encryption is commonly referred to as symmetric encryption and uses a single key to perform encryption or decryption of data. The private key must be kept safe from anyone other than the owner of the data.

Symmetric encryption is generally fast and can operate on large sets of data. The .NET Class Framework contains four symmetric encryption algorithms: DES, Triple DES, RC2, and Rijndael.

Ciphers

Ciphers are cryptographic algorithms that use a private key to transform plain text input into an encrypted output, also known as cipher text. There are two forms of ciphers: block-based and stream-based.

A block-based cipher takes a fixed size input block and transforms the data into a fixed size cipher text block.

A stream-based cipher does not encrypt data but instead generates a key stream that produces the cipher text by XORing the stream bytes with the input bytes.

The .NET Class Framework only provides block-based ciphers, but it is possible to make a block-based cipher behave in a streaming fashion.

Block Padding

Block ciphers were designed to operate on complete blocks of data. Padding is used when processing a partial block of data to append extra data to the incomplete block, making it an even multiple of the block size.

The .NET 2.0 Class Framework supports three types of block padding, as described in Table 16.1.

Table 16.1. Padding Modes Available in .NET 2.0

Padding Mode

Description

PaddingMode.None

Informs the cipher that no padding should occur. This mode requires that you ensure that only complete blocks are processed or any exception will be thrown.

PaddingMode.Zeros

Informs the cipher that zeros should be appended to the incomplete data block to make it an even multiple of the block size.

 

The problem with this mode is that the decryption process will not be able to distinguish the padding from the actual data, which will result in the padding being appended to the decrypted data.

 

A possible solution is to transmit the padding length with the data so that the padding can be removed when the decryption process has finished.

PaddingMode.PKCS7

Informs the cipher to append a sequence of bytes that has a value equal to the number of padding bytes.

 

In a 128-bit data block, if the source data looks like [AA BB CC DD], then 12 padding bytes must be added to make it an even multiple of the block size. The hex value [0C] represents the numeric value 12 in base 10, so the final data block will look like: [AA BB CC DD 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C].

PaddingMode.ISO10126

This padding mode works very much like PaddingMode.PKCS7, except it sets the value of the final byte in the block to the number of padding bytes and sets the remaining padding bytes to random data.

PaddingMode.ANSIX923

This padding mode works very much like PaddingMode.PKCS7, except it sets the value of the final byte in the block to the number of padding bytes and sets the remaining padding bytes to zero.

It is advisable to use PaddingMode.PKCS7 when developing with .NET 1.1 and PaddingMode.ISO10126 when developing with .NET 2.0. Both versions of the .NET framework default to PaddingMode.PKCS7, so you will have to explicitly set the padding mode to ISO10126.

Keep in mind that your software does not exist in a vacuum, so it is important that you make sure that using a different padding mode will not break any existing code.

Key Strength

The key strength, also known as key size, of an encryption algorithm refers to the length of the underlying key, and the higher the number the better. Consider the case of an 8-bit key. It would take an attacker roughly 256 guesses to land on the key, whereas a 40-bit key would take an attacker roughly over a trillion guesses to land on the key. The key length is very important, as is the data comprising the key.

Pseudo-random number generators (PRNG) are sometimes used by developers to generate private keys. Computers are fairly predictable, so achieving randomness is difficult to do. Do not try to create your own PRNG classes; use the built-in functionality provided by the RNGCryptoServiceProvider class in the .NET Class Framework.

Lastly, some developers also derive the key from a password, which may result in a key of substantial length, but the key is only as random as its source. If a 256-bit key is derived from a 12-character password, the key is not as secure as one might think. If a malicious attacker understands how the key is derived, he only has to attack the 12-character password to reveal the key itself.

Cipher Modes

One of the most important security issues to correctly configure is the cipher mode. This mode determines how the individual blocks of a transform are assembled to form the final data.

Messages are usually more than one block in length, so how does the data get encrypted? The obvious solution would be to encrypt each block individually and slap them all together in the end. In actuality, this is one of the most insecure cipher modes (ECB—Electronic Code Book), which can lead to security compromises of the encrypted data.

Cipher modes are used to modify the encryption process based on data carried over from previous block encryptions. The resulting encryption provides a much higher level of security than performing a simple block-level encryption.

The .NET class framework has a variety of other cipher modes available at your disposal, each with its own pros and cons. These cipher modes are listed in Table 16.2. We will be using the CBC mode for this chapter as it offers the best security.

Table 16.2. Cipher Modes Available in .NET 2.0

Cipher Mode

Description

CipherMode.CBC

This cipher mode (Cipher Block Chaining Mode) appends a number of bytes equal to the number of padding bytes used. Before each block is encrypted, it is combined with the previous block using an exclusive bitwise OR operation. This allows for each cipher block to be unique. The initialization vector is combined with the first plain text block before encryption occurs. If a single bit of the cipher block is corrupted, the corresponding plain text block will also be corrupted. In addition, a bit in the subsequent block in the same position will also be corrupted.

CipherMode.CFB

This cipher mode (Cipher Feedback Mode) processes small amounts of plain text instead of an entire block at a time. A shift register is used that is one block in length and is divided into sections. If the block size is eight bytes, the shift register is divided into eight sections. If a bit in the cipher text is corrupted, a plain text bit is corrupted as well as the shift register. Then all results in the next several plain text processes will be corrupted until the bad bit is shifted out of the register.

CipherMode.CTS

This cipher mode (Cipher Text Stealing Mode) handles any length of plain text data and produces cipher text that has a length equal to the plain text length. This cipher mode behaves exactly like the CBC mode except for the last two blocks of plain text.

CipherMode.ECB

This cipher mode (Electronic Code Book) encrypts each block individually. Any blocks of plain text that are in the same message or in a different message using the same key will produce identical cipher text blocks. If the plain text contains a large amount of repetition, it is quite possible to break the cipher one block at a time. It is also possible to substitute and exchange cipher blocks without detection. If a single bit in the cipher text is corrupted, the entire corresponding plain text will also be corrupted.

CipherMode.OFB

This cipher mode (Output Feedback Mode) processes small amounts of plain text instead of an entire block at a time. This cipher mode is very similar to CipherMode.CFB except the shift register is filled differently. If a bit in the cipher text is corrupted, the corresponding bit of plain text will also be mangled. If there are missing bits from the cipher text, the plain text will be corrupted from that point on.

Note

CTS and OFB are defined but not currently implemented by any algorithms in the .NET framework.

Initialization Vectors

Symmetric algorithms will encrypt the same input block into the same output block based on the key. This is a weakness that can be potentially exploited by malicious attackers if they determine the structure of the data. Attackers could locate patterns and eventually reverse-engineer the private key.

In order to protect against this, the algorithms in the .NET Class Framework perform data chaining, where information from the previously encrypted block is used to encrypt the current block. This technique requires what is known as an initialization vector (IV) to perform the encryption with increased cryptographic variance.

There are a couple of ways to generate an initialization vector, but one approach is to run a hashing algorithm on a secret phrase and use a segment of the result as the encryption IV.

The following code shows how to do this:

using System.Security.Cryptography;

static public byte[] GenerateIV(byte[] key, int size)
{
    byte[] result = new byte[size];

    SHA384Managed sha384 = new SHA384Managed();
    sha384.ComputeHash(key);

    for (int byteIndex = 0; byteIndex < result.Length; byteIndex++)
    {
        result[byteIndex] = sha384.Hash[byteIndex];
    }

    return result;
}

The following generates a correctly sized key using a variation of the code for the initialization vector generation.

static public byte[] GenerateKey(byte[] key, int size)
{
    byte[] result = new byte[size];

    SHA384Managed sha384 = new SHA384Managed();
    sha384.ComputeHash(key);

    int counter = 0;

    for (int byteIndex = sha384.Hash.Length - 1;
           byteIndex > = (sha384.Hash.Length - size);
           byteIndex—)
    {
        result[counter++] = sha384.Hash[byteIndex];
    }

    return result;
}

Selecting a Cipher

As discussed previously, there are a few ciphers that can be used for private-key encryption. They all fundamentally do the same thing, except there are some notable differences between them in terms of performance, efficiency, and security.

The symmetric encryption algorithms provided by the .NET Class Framework are described in Table 16.3.

Table 16.3. .NET Symmetric Encryption Algorithms

Algorithm

Description

DES

This symmetric algorithm, also known as the Digital Encryption Standard, has existed for quite some time and is fairly weak by current standards. The DES algorithm was specifically designed to be efficient when implemented in hardware and inefficient when implemented in software. Because of its design, this algorithm is relatively slow compared to more modern algorithms. Another limitation is the short block and key sizes, available only in a 64-bit flavor.

Triple DES

This symmetric algorithm is basically a strengthened version of DES, offering stronger keys of 128-bit and 192-bit. Triple DES runs the DES algorithm over the input data three times, resulting in an algorithm that is stronger but three times slower than DES.

RC2

This symmetric algorithm is fairly good, and it performs more than twice as fast as DES when implemented in software. The 64-bit block size is relatively small, but at least the algorithm supports key lengths of 40 to 128 bits in 8-bit increments.

Rijndael

This symmetric algorithm, also known as the Advanced Encryption Standard (AES), supports block and key sizes of 128, 192, and 256 bits.

 

While scrutinized for being new and not yet standing the test of time, the Rijndael algorithm has become a U.S. Federal Government standard, and is the recommended symmetric encryption algorithm to use whenever possible.

ICryptoTransform Interface

All symmetric encryption implementations provided in the .NET Class Framework implement the ICryptoTransform interface, which provides a uniform way to encrypt and decrypt data independently of the selected cipher.

Table 16.4 describes the members of the ICryptoTransform interface.

Table 16.4. Members of the ICryptoTransform Interface

Member Name

Description

CanReuseTransform

This property indicates whether the current transform can be reused or not. All ciphers in the .NET Class Framework always return true.

CanTransformMultipleBlocks

This property indicates whether or not multiple blocks can be transformed in a single call to either TransformBlock or TransformFinalBlock. All ciphers in the .NET Class Framework always return true.

InputBlockSize

This property indicates the size of the input blocks, which will always be identical to the OutputBlockSize. The return value is dependent on the value of the BlockSize property set in the cipher.

OutputBlockSize

This property indicates the size of the output blocks, which will always be identical to the InputBlockSize. The return value is dependent on the value of the BlockSize property set in the cipher.

TransformBlock

This method encrypts or decrypts one or more blocks before the end of the message.

TransformFinalBlock

This method encrypts or decrypts one or more blocks at the end of the message. Ending blocks must be transformed differently from other blocks due to padding issues.

Since all symmetric algorithms inherit from this interface, encryption and decryption is accomplished using the same calls independently of the cipher used. The following code shows how to encrypt or decrypt binary data of arbitrary length.

using System.Security.Cryptography;

static public byte[] EncryptMessage(SymmetricAlgorithm cipher,
                                    byte[] key,
                                    byte[] plainText)
{
    ICryptoTransform transform = cipher.CreateEncryptor(GenerateKey(key, 16),
                                                        GenerateIV(key, 16));
    byte[] result = transform.TransformFinalBlock(plainText, 0, plainText.Length);
    return result;
}

static public byte[] DecryptMessage(SymmetricAlgorithm cipher,
                                    byte[] key,
                                    byte[] cipherText)
{
    ICryptoTransform transform = cipher.CreateDecryptor(GenerateKey(key, 16),
                                                        GenerateIV(key, 16));
byte[] result = transform.TransformFinalBlock(cipherText, 0, cipherText.Length);
    return result;
}

You will notice in the above example that TransformFinalBlock is called, but TransformBlock is never called. Since we know that CanTransformMultipleBlocks will always return true with any of the symmetric algorithms in the .NET Class Framework, we can transform all of our data in one pass.

The following code shows a simple example using the code shown in this chapter.

using System.Text;

static void Main(string[] args)
{
    byte[] key = Encoding.Default.GetBytes("This is my secret key!");
    string plainText = "This is a test!";

    // You can also use RC2, DES, and TripleDES
    RijndaelManaged cipher = new RijndaelManaged();
    cipher.Mode = CipherMode.CBC;

    Console.WriteLine("Original: [" + plainText + "]");

    byte[] encryptedData = EncryptMessage(cipher, key,
                                  Encoding.Default.GetBytes(plainText));
    Console.WriteLine("Encrypted: [" +
                      Encoding.Default.GetString(encryptedData) + "]");

    byte[] decryptedData = DecryptMessage(cipher, key, encryptedData);
    string decryptedText = Encoding.Default.GetString(decryptedData);
    Console.WriteLine("Decrypted: [" + decryptedText + "]");

    Console.WriteLine("");
    Console.WriteLine("Press any key to continue.");
    Console.Read();
}

Conclusion

This chapter covered the theory and implementation details of using the built-in cryptography functionality in the .NET framework. While the presented solution could be improved upon and extended into a more reusable encryption manager, the fundamental code stays the same.

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

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