Chapter 27
Cryptography

What’s in This Chapter

  • Symmetric key encryption and decryption
  • Asymmetric key encryption and decryption
  • Creating, saving, and retrieving public and private keys
  • Cryptographic random numbers
  • Generating keys, initialization vectors, and salts

Wrox.com Downloads for This Chapter

Please note that all the code examples for this chapter are available as a part of this chapter’s code download on the book’s website at www.wrox.com/go/csharp5programmersref on the Download Code tab.

Cryptography is the study of methods for protecting communications between two people in the presence of an adversary who wants to intercept the communications. Early forms of cryptography, which were used thousands of years ago, use techniques such as rearranging letters or replacing letters with other letters. For example, in a Caesar substitution cipher (named after Julius Caesar who used it more than 2,000 years ago), you shift letters by some fixed amount. For a shift of 3, A becomes D, B becomes E, C becomes F, and so forth.

These encryption schemes are interesting and fun to experiment with, but they’re relatively easy to break with modern techniques. My book Essential Algorithms: A Practical Approach to Computer Algorithms (John Wiley and Sons, 2013) includes a chapter that describes some of these historical methods and how to break them (mostly for entertainment value).

Other books such as Applied Cryptography: Protocols, Algorithms, and Source Code in C (by Bruce Schneier, John Wiley and Sons, 1996) explain modern cryptographic techniques that cannot be easily broken even by today’s computers.

This chapter describes only a few of the most useful cryptographic methods provided by the .NET Framework. Those include generating cryptographically secure random numbers, symmetric encryption, and asymmetric encryption.

This chapter explains only how to use .NET Framework tools to perform those tasks, but it should be enough to get you started with cryptography. Unfortunately, there isn’t room here to explain all the methods for performing even those limited tasks or to explain how those methods work. See a book about cryptography (such as Bruce Schneier’s book) for details about how some of these algorithms work.

Cryptography was originally used to send messages from one person to another securely. Modern cryptography has expanded to include other sorts of secure communication. The following section describes some of the operations that modern cryptographic programs perform. The sections after that explain some of the cryptographic tools you can use in your C# programs.

Cryptographic Operations

The following list describes some of the most common cryptographic operations.

  • Symmetric key encryption (private key encryption)—This technique uses a secret key to encrypt and decrypt messages. You use the key to encrypt a message. You then send the result to someone else who knows the key. That person uses the key to decrypt the message.
  • Asymmetric key encryption (public key encryption)—This technique uses two keys, a public key and a secret key, to encrypt and decrypt messages. Everyone knows the public key that is used to encrypt messages. Only those who know the secret key (typically a single person) can decrypt the messages. (This is sort of like a mail slot in a door. Anyone can slip a message through the slot, but only the person with the key to the door can open the door and read the messages.)
  • Hashing—This technique uses the contents of a file to produce a hash code that represents the file, sort of like a fingerprint. Hash codes have the property that two files are extremely unlikely to have the same hash code. If you know a file’s hash code, then you can rehash the file to see if it has the same hash code. If it does not, then the file has been modified since the original code was calculated.
  • Authentication—This is the process of verifying a person’s identity. For example, if someone sends you a message, authentication lets you determine that the message was sent by the person you think it is and not some imposter.
  • Message signing—Digital message signing plays a role similar to signing a document in the physical world: It lets you verify that the signer created the message. For example, if Microsoft sends you a software update, its digital signature guarantees that the update actually came from Microsoft and not some imposter.
  • Random number generation—This is the process of generating cryptographically strong random numbers.

The last item, random number generation, deserves a little more explanation.

Randomness

Cryptographic techniques do not all use random number generators but randomness is closely tied with cryptography. If you have a good source of random numbers, you can use it to encrypt messages. Conversely, if you have a good encryption scheme, you can use it to generate random numbers. Because you can use random numbers to encrypt and vice versa, the two are in some sense equivalent.

The following two sections explain this equivalency. The section after that explains cryptographically secure randomness.

Using Random Numbers for Encryption

Suppose you have a good random number generator. (I’ll say more about what “good” means shortly.) To use random numbers to encrypt a message, generate a new random byte R for each byte P in the message. The byte’s encrypted value is C = P R.

Here is the bitwise exclusive-or (XOR) operation. (In C# the XOR operator is ^.) A bit in the result is 1 if exactly one of the corresponding bits in the two input bytes is 1. For example, 1 0 = 1 because exactly one of the input bits is 1. In contrast, 1 1 = 0 because both input bits are 1.

To decrypt the message, simply repeat the previous steps, because (P R) R = P, which restores the original plaintext bytes.

Note that the message’s receiver must use the same sequence of bytes that the sender used to encrypt the message. The information needed to produce the same sequence of bytes forms the system’s private key that is shared by the sender and receiver. For example, the key might be a password or sequence of numbers that both the sender and receiver can use to initialize their random number generators.

Using Encryption for Random Numbers

Suppose you have a good encryption algorithm that converts plaintext bytes into unpredictable ciphertext. Then to produce random numbers, simply encrypt the values 1, 1, 1, and so forth for as many bytes as you need. If the encryption algorithm is good, this should produce a stream of apparently random bytes.

If the resulting stream of bytes does not appear random, then the algorithm is not a good secure encryption algorithm.

Cryptographically Secure Randomness

In cryptography, a “good” stream of random numbers roughly means one that is unpredictable. If I give you a sequence of numbers produced by a random number generator, you should not be able to predict with any certainty what the next number will be.

A “good” cryptographic algorithm also produces uncertainty. The bytes of a ciphertext message should look random to an attacker.

The .NET Framework includes a Random class that you can use to generate random numbers, but the algorithm it uses is not cryptographically secure. To see why, you need to know that you can initialize a Random object by passing its constructor a 32-bit integer as a seed value. If you use a Random object to encrypt messages, the seed value is basically the key.

A 32-bit integer can hold 232 (4.3 billion) different values, so there are approximately 4.3 billion ways you can initialize a Random object. To think of it another way, there are roughly 4.3 billion possible keys. That may seem like a lot of keys, but to a computer it’s actually not many. It wouldn’t be hard to write a program that tries every possible seed value.

For each seed value, the program would try to decrypt the message. If the result looks like English (or whatever language you use), the seed is probably correct. For example, in English some letters such as E appear more often than others. If the seed is correct, some letters should appear more often than others. If the seed is incorrect, all the letters should appear with roughly the same frequency.

Generating Random Numbers

Generating cryptographically strong random numbers is actually fairly easy in C#. Simply create a cryptographic random number generator object and use its methods to generate the numbers. The .NET Framework’s RNGCryptoServiceProvider class provides this kind of random number generator. (The “RNG” at the beginning of the class’s name stands for “random number generator.”)

The RandomNumbers example program, which is available for download on the book’s website, uses the following code to generate a list of random numbers.

// Generate random numbers.
private void generateButton_Click(object sender, EventArgs e)
{
    numbersListBox.Items.Clear();
    int numNumbers = int.Parse(numNumbersTextBox.Text);

    // Make the RNG.
    RNGCryptoServiceProvider rand = new RNGCryptoServiceProvider();

    // Make a buffer to hold 4 random bytes.
    byte[] bytes = new byte[4];

    // Make the random numbers.
    for (int i = 0; i < numNumbers; i++)
    {
        // Get 4 random bytes.
        rand.GetBytes(bytes);

        // Convert the bytes into an integer.
        int number = BitConverter.ToInt32(bytes, 0);

        // Display the number.
        numbersListBox.Items.Add(number);
    }
}

The program starts by clearing its output ListBox and parsing the number of random numbers it should generate.

The code then creates an RNGCryptoServiceProvider object. That object generates random bytes but this program needs to display random numbers. To convert bytes into numbers, the program creates a byte array large enough to hold the number of bytes it needs. This program generates int values, which are 4-byte integers, so it makes an array holding 4 bytes.

The program then enters a loop to generate the values. For each value, it uses the RNGCryptoServiceProvider object’s GetBytes method to fill the byte array with random bytes. It then uses the BitConverter class’s ToInt32 method to convert the bytes into an integer value. Finally, it adds the result to the ListBox.

This example uses four random bytes to generate an integer value, so it can produce any possible value. If you want to generate values between lower and upper bounds, you need to do some more work.

The first step is to generate a random floating-point value. The following code snippet shows one way you can generate a value in the range 0 (inclusive) and 1 (exclusive).

RNGCryptoServiceProvider rand = new RNGCryptoServiceProvider();
byte[] bytes = new byte[8];
rand.GetBytes(bytes);
UInt32 randomInt = BitConverter.ToUInt32(bytes, 0);
float randomFloat = randomInt / (1.0 + UInt32.MaxValue);

The code creates an RNGCryptoServiceProvider and uses it to fill a 4-byte array with random bytes. It then converts the result into a UInt32.

Next, the code divides the random UInt32 by 1 more than the largest possible UInt32. Because the random UInt32 is between 0 and the largest possible value, the result of the division is between 0 and a tiny bit less than 1.

Now that you can generate random floating-point values, you can use those values to generate integers within a range. The following code shows how you can generate an integer in the range [min, max).

int randomInRange = (int)(min + (randomFloat * (max - min)));

Symmetric Key Encryption

One of the most obvious uses for encryption is to encrypt and decrypt strings or files. The .NET Framework’s encryption services work with streams, so you can use them to encrypt and decrypt anything that you can treat as a stream. That includes strings, files, arrays of bytes, serializations, or just about any other piece of data.

The basic idea is reasonably straightforward: Create a cryptographic service provider object and use its methods to write data into an output stream. As data passes through the provider, it is encrypted or decrypted. Unfortunately, the details are rather confusing, so they are described in the following sections.

Simple Encryption and Decryption

The DefaultKeyAndIV example program, which is shown in Figure 27-1 and available for download on the book’s website, is one of the simplest programs you can write to encrypt and decrypt a string.

When you enter a string and click Encrypt, the program encrypts and then decrypts the string. The following code shows how the program encrypts the string.

private void encryptButton_Click(object sender, EventArgs e)
{
    // Get the plaintext.
    string plaintext = plaintextTextBox.Text;
    // Create an AesManaged provider.
    using (AesManaged provider = new AesManaged())
    {
        // Make a byte array to hold the encrypted result.
        byte[] cipherbytes;

        // Encrypt. Create an encryptor.
        using (ICryptoTransform encryptor =
            provider.CreateEncryptor(provider.Key, provider.IV))
        {
            // Create a memory stream to hold the result.
            using (MemoryStream ms = new MemoryStream())
            {
                // Create an associated CryptoStream.
                using (CryptoStream cs =
                    new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                {
                    // Make a StreamWriter associated with the CryptoStream
                    // so we can write into it.
                    using (StreamWriter writer = new StreamWriter(cs))
                    {
                        // Write the message into the writer.
                        writer.Write(plaintext);
                    }

                    // Save the resulting ciphertext bytes.
                    cipherbytes = ms.ToArray();

                    // Display the key, IV, and encrypted bytes.
                    keyTextBox.Text = BitConverter.ToString(provider.Key);
                    ivTextBox.Text = BitConverter.ToString(provider.IV);
                    cipherbytesTextBox.Text = BitConverter.ToString(cipherbytes);
                }
            }
        }
c27f001.tif

Figure 27-1: The DefaultKeyAndIV example program encrypts and decrypts a string.

The code gets the message text and then creates an AesManaged object. This is a managed object that lets C# programs easily interact with an Advanced Encryption Standard (AES) service provider.

Next, the code creates the variable cipherbytes to hold the encrypted data later. It then creates an encryptor object that it can use to encrypt the string.

To use the encryptor, the program must create one stream from which to read data and a second stream into which to write results. The program creates a MemoryStream to hold the results.

The program then creates a CryptoStream object to transform any data written into the input stream. It passes the memory stream and the encryptor into the CryptoStream’s constructor, so it knows where to write results and what encryptor to use to transform the data. It also passes the constructor the value CryptoStreamMode.Write so the CryptoStream knows it will be writing into the memory stream.

Next, the code creates a StreamWriter into which it can write the plaintext data. It associates the writer with the CryptoStream.

Finally, the code calls the StreamWriter’s Write method to write the string.

Figure 27-2 shows the process schematically. The plaintext is written into the StreamWriter. The StreamWriter writes the data into the CryptoStream. The CryptoStream uses the associated encryptor to transform the data and writes the result into the MemoryStream.

c27f002.eps

Figure 27-2: To encrypt a message, a StreamWriter writes data into a CryptoStream, which transforms the data and writes the result into a MemoryStream.

After the program writes the data, it saves the MemoryStream’s contents in a byte array.

Next, the program displays the AesManaged object’s Key and IV values, and the encrypted bytes. (The following section says more about the Key and IV values.) It uses the BitConverter.ToString method to convert each of those values, all of which are byte arrays, into strings displaying the bytes as sequences of hexadecimal numbers.

After it encrypts the message string, the program uses the following code to decrypt the encrypted bytes.

        // Decrypt. Create a decryptor.
        using (ICryptoTransform decryptor =
            provider.CreateDecryptor(provider.Key, provider.IV))
        {
            // Create a memory stream from which to read the encrypted bytes.
            using (MemoryStream ms = new MemoryStream(cipherbytes))
            {
                // Create an associated CryptoStream.
                using (CryptoStream cs =
                    new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                {
                    // Make a StreamReader associated with the CryptoStream
                    // so we can read from it.
                    using (StreamReader reader = new StreamReader(cs))
                    {
                        // Read the message from the reader.
                        string decrypted = reader.ReadToEnd();

                        // Display the result.
                        decryptedTextBox.Text = decrypted;
                    }
                }
            }
        }
    }
}

The general pattern of this code is similar to the code that encrypts the string. The biggest difference is that this code uses a decryptor instead of an encryptor.

The code starts by creating the decryptor, passing its constructor the Key and IV values that were used to encrypt the message. It then creates a MemoryStream associated with the encrypted bytes so it can read from it.

The program then creates a CryptoStream associated with the MemoryStream and decryptor, this time passing the constructor the CryptoStreamMode.Read parameter, so the CryptoStream knows it will be reading data from the MemoryStream.

The code makes a StreamReader to read from the CryptoStream and uses its ReadToEnd method to read the data from the stream. Finally, the program displays the decrypted result.

Figure 27-3 shows the decryption process schematically. The StreamReader’s ReadToEnd method pulls data from the CryptoStream. The CryptoStream pulls data from the MemoryStream and uses its associated decryptor to decrypt the data.

c27f003.eps

Figure 27-3: To decrypt a message, a StreamReader pulls data from a CryptoStream, which pulls data from the MemoryStream and uses its associated decryptor to decrypt it.

Keys and Initialization Vectors

Recall from the earlier discussion of randomness that there are only approximately 4.3 billion seeds you can use to initialize a System.Random object. That means this class can’t give you a very secure encryption scheme because an attacker can simply try all possible seed values until finding the right one.

To avoid this problem, cryptographic service providers allow far more initialization values than those available to the Random class. Those values are grouped into two pieces: a key and an initialization vector (IV). Both the key and IV are arrays of bytes, so they don’t map to easily remembered passwords.

If you don’t care what values the key and IV have, you can simply create an AesManaged object as is done by the example in the preceding section and it will randomly generate those values for you.

That works for this simple example, but only because the same program performs the encryption and decryption. When it decrypts the message, it knows the AesManaged object’s Key and IV values.

What if you want to encrypt a message and send it to someone else? They need to use the same Key and IV values if they want to decrypt the message. Somehow you must get them those values or the message is meaningless.

One solution is to have the encryption program write the Key and IV values onto a flash drive or piece of paper. (You could even write the values on a microdot and hide them in the secret compartment of a ring, so you can feel like you’re in a Tom Clancy novel.) Then you can transfer those values to the receiver through secure means. For example, you might have a courier carrier the flash drive or paper (or ring) to the recipient.

In this example, that may seem like more work than it’s worth. If you look closely at Figure 27-1, you’ll see that the Key contains 32 bytes and the IV contains 16 bytes, whereas the entire encrypted message contains only 32 bytes. You could just as easily have had the courier carry the original plaintext message to the receiver and skip the encryption.

However, the same Key and IV values could be used to encrypt a message of any size. As long as the courier gets those values to the receiver safely, you could encrypt and transmit an entire encyclopedia securely.

Another approach would be to generate a collection of Key and IV values and send them securely to the receiver in advance. Then when you need to send an encrypted message, the receiver can use the next Key and IV values in the collection to decrypt the message.

Generating Key and IV Values

These methods work reasonably well (assuming you can safely carry the Key and IV values to the receiver), but unless you have an amazing memory, you’re unlikely to remember the Key and IV values.

Often large pseudorandom arrays of bytes make good keys and IVs. If you truly want to use a password, however, you can use one to generate a key and IV.

The Rfc2898DeriveBytes class provides a GetBytes method that returns an array containing a specified number of bytes.

The following code shows a method that uses this class to initialize key and IV arrays.

// Use a password to generate key and IV bytes.
private static void MakeKeyAndIV(string password, byte[] salt,
    int iterations, int numKeyBytes, int numIVbytes, out byte[] key, out byte[] iv)
{
    Rfc2898DeriveBytes deriver =
        new Rfc2898DeriveBytes(password, salt, iterations);

    key = deriver.GetBytes(numKeyBytes);
    iv = deriver.GetBytes(numIVbytes);
}

The method starts by creating a new Rfc2898DeriveBytes object, passing its constructor the password, a salt, and a number of iterations.

The salt is an array of bytes that you specify to make it harder for an attacker to build a dictionary containing key and IV values for all possible passwords. Basically, this serves as an initialization vector for the Rfc2898DeriveBytes object.

The number of iterations tells the object how many times to apply the object’s internal algorithm. This number is intended to slow the operation down. If you know the correct password, repeating the algorithm 1,000 or so times will slow you down only a few milliseconds. However, if you’re an attacker trying to guess all possible passwords, performing all those iterations for every possible password will add up and slow down your attack. (Microsoft recommends that you use at least 1,000 iterations.)

After it has initialized the Rfc2898DeriveBytes object, the code simply calls its GetBytes method to generate however many bytes it needs for the key and IV values.

Asymmetric Key Encryption

Asymmetric key encryption uses a public key known to everyone (or at least everyone you want to be a message sender) and a private key known only to the message receiver. Anyone can encrypt messages but only the receiver can decrypt them.

The most widely known asymmetric algorithm is RSA, which was named for the three people who first publicized it: Ron Rivest, Adi Shamir, and Leonard Adleman. RSA is interesting but it’s also fairly complicated, so there’s no room to explain how it works here. The rest of this section provides an overview of how to use RSA in a C# program. The sections that follow show code that performs the steps.

For now what you need to know is that RSA uses a collection of numbers to encrypt and decrypt data. Two of the numbers, called Exponent and Modulus, make up the public key. The others, which are called D, DP, DQ, InverseQ, P, and Q, make up the private key.

The .NET Framework’s RSACryptoServiceProvider class uses RSA to encrypt and decrypt data. It uses the RSAParameters structure to store the public and private key data.

To retrieve key data from an RSACryptoServiceProvider object, you call the object’s ExportParameters method, passing it the parameter false if you want only the object’s public parameters and true if you also want the object’s private parameters.

To load key values into an RSACryptoServiceProvider object, you call the object’s ImportParameters method, passing it an RSAParameters structure holding the necessary parameters.

Unlike the key and IV data used by the AES algorithm, the bytes used in RSA’s keys are not random. They are carefully selected large integers that satisfy specific mathematical relationships. These numbers are also enormous (64 bytes or longer) so finding numbers that work isn’t as simple as generating random bytes.

You can create a new set of key parameters by making an instance of the RSACryptoServiceProvider class. You can then publish the public parameters and save the private parameters so that you can later decrypt messages.

Microsoft correctly points out that you should never store key values in plaintext on a computer, so cyber-crooks can’t find them. You could write down the D, DP, DQ, InverseQ, P, and Q parameters on a piece of paper, but that’s a lot of data.

To help solve this problem, Microsoft suggests that you store key information in a key container, an object that stores key information encrypted, so it’s not easy to steal.

If this all seems complicated, you’re right, it is.

The following list summarizes the steps for creating RSA keys in a C# program.

  1. Create a new RSACryptoServiceProvider object. (It is created with a usable set of key values.)
  2. Use the object’s ExportParameters method to extract the key values.
  3. Save the key values in a key container for later use.
  4. Publish the public key values Exponent and Modulus.

The following list summarizes the steps for using RSA to encrypt data in a C# program.

  1. Create a new RSAParameters structure.
  2. Initialize the structure’s Exponent and Modulus parameters with the public values.
  3. Create an RSACryptoServiceProvider object.
  4. Call the object’s ImportParameters method to set its public key parameters.
  5. Call the object’s Encrypt method, passing it the bytes to encrypt.

The following list summarizes the steps for using RSA to decrypt data in a C# program.

  1. Create an RSACryptoServiceProvider object, initializing its parameters with the values saved in the key container. (Alternatively, you can create the object and then use ImportParameters to load the key parameters from an RSAParameters structure.)
  2. Call the provider’s Decrypt method, passing it the encrypted bytes.

The following sections show code that performs these steps.

Creating, Saving, and Retrieving Keys

The following code shows how a program might create new RSA key data and save it into a key container.

// Create the parameters object and set the container name.
CspParameters parameters = new CspParameters();
parameters.KeyContainerName = "TestKey";

// Create an RSACryptoServiceProvider that uses the parameters.
return new RSACryptoServiceProvider(parameters);

The code first creates a CspParameters object and sets its KeyContainerName property to the name of the key’s container. It then creates a new RSACryptoServiceProvider object, passing its constructor the CspParameters object.

If the key container already exists, the new RSACryptoServiceProvider object is loaded with the saved key information.

If the container does not already exist, the RSACryptoServiceProvider object is loaded with a new set of valid RSA keys and they are saved in a new key container.

Encrypting Data

The following RSAEncrypt method uses RSA to encrypt data.

private byte[] RSAEncrypt(byte[] plainbytes, RSAParameters publicParams)
{
    // Create a RSACryptoServiceProvider. 
    using (RSACryptoServiceProvider provider = new RSACryptoServiceProvider())
    {
        // Import the public key information.
        provider.ImportParameters(publicParams);

        // Encrypt the data and return the result. (Don't use OAEP padding.)
        return provider.Encrypt(plainbytes, false);
    }
}

This method first creates an RSACryptoServiceProvider object. It then calls the object’s ImportSettings method to import the public key information in the RSAParameters structure passed into the routine as a parameter.

The method finishes by calling the provider’s Encrypt method. It passes the method the data to encrypt and returns the resulting encrypted data.

Decrypting Data

The following RSADecrypt method uses RSA to decrypt data.

private byte[] RSADecrypt(byte[] cipherbytes, RSAParameters privateParams)
{
    // Create a RSACryptoServiceProvider. 
    using (RSACryptoServiceProvider provider = new RSACryptoServiceProvider())
    {
        // Import the private key information.
        provider.ImportParameters(privateParams);

        // Decrypt the data and return the result. (Don't use OAEP padding.)
        return provider.Decrypt(cipherbytes, false);
    }
}

This method is similar to the RSAEncrypt method. The only differences are that its privateParams parameter contains the private RSA key parameters rather than the public parameters, and that the method calls the provider’s Decrypt method instead of its Encrypt method.

Example Encryption

The RSAEncryptDecrypt example program, which is shown in Figure 27-4 and available for download on the book’s website, demonstrates RSA encryption and decryption. You can see from the figure that all the key values except Exponent are quite large. In this example running on my computer, Exponent is a 3-byte integer, D and Modulus are 128-byte integers, and the other values are 64-byte integers.

c27f004.tif

Figure 27-4: The RSAEncryptDecrypt example program uses RSA to encrypt and decrypt a string.

The following code shows how the example encrypts and decrypts data when you click the Encrypt button.

private void encryptButton_Click(object sender, EventArgs e)
{
    // Get the plaintext.
    string plaintext = plaintextTextBox.Text;

    // Create a UnicodeEncoding object to convert between byte array and string.
    UnicodeEncoding converter = new UnicodeEncoding();

    // Make byte arrays to hold the original, encrypted, and decrypted data.
    byte[] plainbytes = converter.GetBytes(plaintextTextBox.Text);
    byte[] cipherbytes;
    byte[] decryptedbytes;

    // Create an RSA provider.
    using (RSACryptoServiceProvider provider = new RSACryptoServiceProvider())
    {
        // Get the provider's public key properties.
        RSAParameters publicParams = provider.ExportParameters(false);

        // Use the public parameters to encrypt the data.
        cipherbytes = RSAEncrypt(plainbytes, publicParams);

        // Display the encrypted bytes.
        cipherbytesTextBox.Text = BitConverter.ToString(cipherbytes);

        // Display public properties.
        exponentTextBox.Text = BitConverter.ToString(publicParams.Exponent);
        modulusTextBox.Text = BitConverter.ToString(publicParams.Modulus);

        // Decrypt.
        // Get the provider's private key properties.
        RSAParameters privateParams = provider.ExportParameters(true);

        // Use the private parameters to decrypt the data.
        decryptedbytes = RSADecrypt(cipherbytes, privateParams);

        // Display private properties.
        DTextBox.Text = BitConverter.ToString(privateParams.D);
        DPTextBox.Text = BitConverter.ToString(privateParams.DP);
        DQTextBox.Text = BitConverter.ToString(privateParams.DQ);
        inverseQTextBox.Text = BitConverter.ToString(privateParams.InverseQ);
        PTextBox.Text = BitConverter.ToString(privateParams.P);
        QTextBox.Text = BitConverter.ToString(privateParams.Q);

        // Display the result.
        decryptedTextBox.Text = converter.GetString(decryptedbytes);
    }
}

The code starts by getting the plaintext to encrypt. It then creates a UnicideEncoding object to translate between strings and byte arrays. It converts the plaintext into bytes and makes two other byte arrays to hold the encrypted and decrypted data.

The program then creates an RSACryptoServiceProvider object. That object is initialized with new RSA key information.

The program calls the provider’s ExportParameters method to get its public key values. It passes those parameters and the data to encrypt into the RSAEncrypt method described earlier to encrypt the data.

The program then displays the encrypted data and the public key parameters.

Next, the program calls the provider’s ExportParameters method again, this time to get its private key values. It passes those and the encrypted data to the RSADecrypt method described earlier to decrypt the data.

The program finishes by displaying the private key parameters and the decrypted message.

Summary

Cryptography is a fascinating topic. Modern cryptographic tools use sophisticated mathematics to let you perform such operations as encrypting and decrypting files and strings, hashing files to see if they have been modified, digitally signing files to provide assurance that you actually wrote the file, and generating cryptographically secure random numbers.

There isn’t room to cover all these topics here, so this chapter focused on three of the most useful tasks: generating cryptographically secure random numbers, symmetric encryption, and asymmetric encryption. This should provide enough introduction for you to continue your study of the .NET Framework’s cryptographic methods on your own.

For more information about cryptographic tools in the .NET Framework, see “Cryptographic Services” at msdn.microsoft.com/library/92f9ye3s.aspx.

Exercises

  1. Write a program that uses the Random class to encrypt and decrypt messages.

    Unfortunately, if you use the XOR operator ^ to combine a letter with a random byte, the result may not be a letter, so it’s hard to visualize the result. To make it easier to visualize the results, assume the message contains only uppercase letters A through Z. (For bonus points, use string methods and LINQ to convert the plaintext.) To encrypt a letter, shift it by a random number of letters between 0 and 25. For example, A + 1 = B, A + 2 = C, and so forth.

    Use the program to encrypt a message and then decrypt the result to make sure it works properly. (For testing purposes, the message “This is a test” with key 123456 should be ZJIBDEFDSNV.)

  2. Write a program similar to the one shown in Figure 27-5 that breaks the encryption you used for Exercise 1.
    c27f005.tif

    Figure 27-5: The BreakRandomCipher example program uses character frequencies to break the encryption scheme used in Exercise 1.

    Make the program loop through possible key values. (To reduce the search slightly, use only non-negative keys.) For each possible key, decipher the ciphertext and use LINQ to calculate the frequencies of the letters in the resulting plaintext. Compare the maximum frequency in the plaintext to the test value entered by the user. If the maximum frequency is greater than the test value, display the plaintext so that the user can see if it looks like a message. (If you look closely at Figure 27-5, you can see that the plaintext doesn’t make sense. In that case, you should increase the test frequency and try again.)

    To test your program, decrypt the text in the file DecipherMe.txt available in this chapter’s downloads. What is the name of the author of the encrypted quote?

  3. Will the approach you used for Exercise 2 work for short messages? Why?
  4. The instructions for Exercise 2 tell you to look at the maximum frequency of the letters in the decrypted plaintext. Why don’t you need to look at all the letters’ frequencies? What are the advantages and disadvantages of this approach? Can you think of a simple way to make the search more reliable?
  5. What changes would you need to make for the solution you wrote for Exercise 2 to work with other languages such as French or German?
  6. The RandomNumbers example program described in the section “Generating Random Numbers” works but is long and cumbersome. Write a static MyRandom class that has a GetInt method that returns a cryptographically secure random integer. Then rewrite the example program to use the new method.
  7. Rewrite the program you wrote for Exercise 6 so that it generates random doubles in the range [0, 1). (Hint: Add a NextDouble method to the MyRandom class to generate the random value, and make the main program use that method.)
  8. Rewrite the program you wrote for Exercise 6 so that it generates integers between minimum and maximum values entered by the user. (Hint: Use the NextDouble method you added for Exercise 7 to add an overloaded version of GetInt that takes lower and upper bounds as parameters.)
  9. The DefaultKeyAndIV example program encrypts and decrypts a string within its button Click event handler. That’s not a reusable approach. Rewrite the program to move the encryption and decryption steps into separate methods. (Hints: Make the methods take the key and IV values as parameters. Make the main program create an AesManaged object and use its default key and IV values.)
  10. Modify the program you wrote for Exercise 9 to move the encryption and decryption methods into extension methods.
  11. Write a program similar to the one shown in Figure 27-6 to encrypt and decrypt strings. What happens if you try to decrypt a message with the wrong password? What if the password is wrong only by a single character?
    c27f006.tif

    Figure 27-6: For Exercise 11, write a program that uses a password to encrypt and decrypt messages.

  12. Write programs to act as a sender and receiver for RSA communication. The receiver program should have these features:
    1. When it starts, the program should create an RSACryptoServiceProvider object associated with a key stored in a key container. (If the key already exists, it will load into the provider. If the key doesn’t yet exist, the provider will create a key and store it in the container.)
    2. After it has created the provider, the program should display the public key values Exponent and Modulus in TextBoxes. (You can also make it display the private data if you like.)
    3. The user should enter encrypted data in the format used by the BitConverter.ToString method (as in F9-9A-98-6F-BC-9F).
    4. When the user clicks the Decrypt button, the program should convert the entered data into a byte array and use the private key data to decrypt it.

    The sender program should have these features:

    1. The user should copy and paste the public key data from the receiver program into TextBoxes in the sender. (The sender should not know the public key data.)
    2. When the user enters a message and clicks Encrypt, the program should use the private key data to encrypt the message and display the result. (Remember to use a short message because the RSA provider won’t encrypt long ones.)

    To test the programs:

    1. Run both programs.
    2. Copy and paste the public key data from the receiver into the sender.
    3. Enter a message in the sender and encrypt it.
    4. Copy and paste the encrypted data from the sender to the receiver.
    5. Use the receiver to decrypt the message and verify that it is correct.
..................Content has been hidden....................

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