What’s in This Chapter
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.
The following list describes some of the most common cryptographic operations.
The last item, random number generation, deserves a little more explanation.
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.
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.
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.
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 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)));
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.
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);
}
}
}
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
.
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.
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.
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 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.
RSACryptoServiceProvider
object. (It is created with a usable set of key values.)ExportParameters
method to extract the key values.Exponent
and Modulus
.The following list summarizes the steps for using RSA to encrypt data in a C# program.
RSAParameters
structure.Exponent
and Modulus
parameters with the public values.RSACryptoServiceProvider
object.ImportParameters
method to set its public key parameters.Encrypt
method, passing it the bytes to encrypt.The following list summarizes the steps for using RSA to decrypt data in a C# program.
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.)Decrypt
method, passing it the encrypted bytes.The following sections show code that performs these steps.
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.
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.
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.
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.
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 string
s and byte
arrays. It converts the plaintext into byte
s 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.
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.
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.)
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?
MyRandom
class that has a GetInt
method that returns a cryptographically secure random integer. Then rewrite the example program to use the new method.double
s 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.)NextDouble
method you added for Exercise 7 to add an overloaded version of GetInt
that takes lower and upper bounds as parameters.)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.)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.)Exponent
and Modulus
in TextBox
es. (You can also make it display the private data if you like.)BitConverter.ToString
method (as in F9-9A-98-6F-BC-9F).byte
array and use the private key data to decrypt it.The sender program should have these features:
TextBox
es in the sender. (The sender should not know the public key data.)To test the programs:
3.142.197.212