- Problems with Symmetric Algorithms
- The Idea Behind Asymmetric Cryptography
- Existing Asymmetric Algorithms
- RSA: The Most Used Asymmetric Algorithm
- Caveat: Provability Issues
- Programming with .NET Asymmetric Cryptography
- Digital Certificates
- Summary
Programming with .NET Asymmetric Cryptography
In this section, we look at the RSAAlgorithm and SavingKeysAsXml example programs provided for this chapter. These two code examples show how to encrypt and decrypt using the RSA algorithm as well as how to store and retrieve key information using an XML format. The RSA code example uses the concrete RSACryptoServiceProvider class. Figure 4-2 shows where this class resides in the class hierarchy, under the abstract AsymmetricAlgorithm class. The other concrete class, DSACryptoServiceProvider, is discussed in Chapters 5, where we look at digital signatures.
Figure 4-2. The asymmetric algorithm class hierarchy.
An RSA Algorithm Example
The RSAAlgorithm example uses the Encrypt method of the RSACryptoServiceProvider class. This method takes two parameters, the first of which is a byte array containing the data to be encrypted. The second parameter is a boolean that indicates the padding mode to be used. Padding is required, since the data to be encrypted is usually not the exact number of required bits in length. Since the algorithm requires specific bit-sized blocks to process properly, padding is used to fill the input data to the desired length. If this second parameter is true, then the improved OAEP16 padding is used. Otherwise, the traditional PKCS#1 v1.5 padding is used. PKCS#1 v1.5 has been traditionally the most commonly used padding scheme for RSA usage. However, it is recommended that all new RSA applications that will be deployed on platforms that support OAEP should use OAEP. Note that OAEP padding is available on Microsoft Windows XP and Windows 2000 with the high-encryption pack installed. Unfortunately, previous versions of Windows do not support OAEP, which will cause the Encrypt method, with the second parameter set to true, to throw a CryptographicException. The Encrypt method returns the resulting encrypted data as a byte array. Here is the syntax for the Encrypt method.
public byte[] Encrypt( byte[] rgb, bool fOAEP );
The complementary method to Encrypt is of course Decrypt. You can probably guess how it works. The second parameter is a byte array containing the ciphertext to be decrypted. The second parameter is the same as that in the Encrypt method, indicating the padding mode, as described previously. The return value is a byte array that will contain the resulting recovered plaintext. Here is the syntax for the Decrypt method.
public byte[] Decrypt( byte[] rgb, bool fOAEP )
Figure 4-3 shows the RSAAlgorithm example being used to encrypt and decrypt a plaintext message. You enter the plaintext in the TextBox at the top of the form. You then click on the Encrypt button, which fills in all but the last form field, including the resulting ciphertext and RSA parameters that were used. You then click on the Decrypt button, which displays the recovered plaintext in the field at the bottom of the form. Of course, the recovered plaintext should be identical to the original plaintext.
Figure 4-3. The RSAAlgorithm example program.
Now let's look at the code in the RSAAlgorithm example code. The buttonEncrypt_Click method is called when the user clicks on the Encrypt button. This encrypts the contents of the plaintext textbox using the established public RSA key. The public/private RSA key pair is provided by the program automatically when it starts, but it may subsequently be changed using the New RSA Parameters button. There are a few places in the code where user interface elements are being enabled and disabled, which are not germane to our focus on RSA functionality. Therefore, these user interface code sections are ignored here. If you are curious about how these user interface details work, please study the simple code sections following each of the //do UI stuff comments.
We first generate the initial RSA parameters by calling the GenerateNewRSAParams method in the RSAAlgorithm_Load method. The GenerateNewRSAParams method is also called each time the user clicks on the New RSA Parameters button, which is handled by the buttonNewRSAParams_Click method. The GenerateNewRSAParams method is very simple. It just creates an RSACryptoServiceProvider class object, stores its public and private RSA parameters by calling the RSA class's ExportParameters method, and displays a few of the more important of these parameters in the user interface. These RSA parameters are actually stored in two fields of type RSAParameters. The RSAParameters field named rsaParamsExcludePrivate gets a copy of the public-only RSA parameters (i.e., the modulus and exponent values only), which is required for encryption purposes in the buttonEncrypt_Click method. The other RSAParameters field, named rsaParamsIncludePrivate gets a copy of the combined public and private RSA parameters, which is required in the buttonDecrypt_Click method.
Here is the GenerateNewRSAParams method. Note that the ExportParameters method is called twice. The first time, the parameter passed into this method is true, and the second time, it is false. Passing true indicates that you want to include all key parameter information, including the private key information. False indicates that only the public key information is to be stored. We separate these cases into two distinct fields to demonstrate how the encryption will use only the public information, but the decryption will use both the public and private key information. This is a crucial point in understanding asymmetric cryptography. This would perhaps be even clearer if we broke the encryption and decryption portions of this example into two separate applications, but this example is provided as a simple monolithic program purely for easy study. You should at some point take a moment to verify that the encryption and decryption functions in this program do indeed use only their own appropriate version of this RSA parameter information, using the corresponding ImportParameters method.
private void GenerateNewRSAParams() { //establish RSA asymmetric algorithm RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); //provide public and private RSA params rsaParamsIncludePrivate = rsa.ExportParameters(true); //provide public only RSA params rsaParamsExcludePrivate = rsa.ExportParameters(false);
When we create an instance of the RSACryptoServiceProvider class, we actually get the RSA implementation provided by the underlying cryptographic service provider (CSP). This class is directly derived from the RSA class. The RSA class allows other RSA implementations to be implemented as other derived classes; however, the CSP implementation is currently the only one available.
The two fields that store the RSA parameter information when ExportParameters is called are declared as RSAParameters type fields, as shown in the following code snippet. The rsaParamsExcludePrivate filed will be used for encryption, and the rsaParamsIncludePrivate field will be used in decryption in this example.
//public modulus and exponent used in encryption RSAParameters rsaParamsExcludePrivate; //public and private RSA params use in decryption RSAParameters rsaParamsIncludePrivate;
In the buttonEncrypt_Click method we then create a new instance of RSACryptoServiceProvider class, and we initialize it with the stored public key information by calling the RSA object's ImportParameters method, specifying rsaParamsExcludePrivate as the parameter. Next, we obtain the plaintext in the form of a byte array named plainbytes. Finally, we perform the main function of this method by calling on the Encrypt method of the RSA object. This returns another byte array, which is an instance field named cipherbytes. This is an instance field rather than a local variable, because we need to communicate this byte array to the decryption method, and local variables are not maintained across method calls.
private void buttonEncrypt_Click( object sender, System.EventArgs e) { //do UI stuff ... //establish RSA using parameters from encrypt RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); //import public only RSA parameters for encrypt rsa.ImportParameters(rsaParamsExcludePrivate); //read plaintext, encrypt it to ciphertext byte[] plainbytes = Encoding.UTF8.GetBytes(textPlaintext.Text); cipherbytes = rsa.Encrypt( plainbytes, false); //fOAEP needs high encryption pack //display ciphertext as text string ... //display ciphertext byte array in hex format ... //do UI stuff ... } ... //variable communicated from encrypt to decrypt byte[] cipherbytes;
The buttonDecrypt_Click method is called when the user clicks on the Decrypt button. Again, an RSA object is created. The RSA object is repopulated with the information provided by calling the RSA object's ImportParameters method, but this time, the parameter to this method is the rsaParamsIncludePrivate, which includes both public and private RSA key information. The plaintext is then obtained by calling the Decrypt method of the RSA object. Since a matching set of RSA algorithm parameters were used for both encryption and decryption, the resulting plaintext matches perfectly with the original plaintext.
private void buttonDecrypt_Click( object sender, System.EventArgs e) { //establish RSA using parameters from encrypt RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); //import public and private RSA parameters rsa.ImportParameters(rsaParamsIncludePrivate); //read ciphertext, decrypt it to plaintext byte[] plainbytes = rsa.Decrypt( cipherbytes, false); //fOAEP needs high encryption pack //display recovered plaintext ... //do UI stuff ... } ... //variable communicated from encrypt to decrypt byte[] cipherbytes;
Saving Keys as XML
You might not always want to transmit the contents of the ExportParameters object directly between arbitrary applications, especially between different platforms and cryptographic libraries. After all, the ExportParameters class is very Microsoft- and .NET-specific. A much more convenient and generalized format for transmitting a public key is via an XML stream.17 The SavingKeysAsXml example program shows how to read and write keys in XML format. This example is almost identical to the RSAAlgorithm example we just looked at. The significant difference is that we use XML for storing and transmitting the public key information from the encryption method to the decryption method rather than use an ExportParameters object. Another slight difference is that the RSA parameter information is not displayed; the contents of the key XML stream is displayed instead, but that is of course only a user interface detail.
For simplicity and ease of demonstration, this example is again implemented as a single monolithic application. This is purely for ease of demonstration, and it would be straightforward to take this example and break it up into two separate encrypting and decrypting programs. Our purpose here is to show both the sending (encrypting) and receiving (decrypting) code and how the XML data is used to store key information between the two. To make this example somewhat more realistic, the XML data is written to a file rather than stored in a shared field, as was done in the previous example. This simulates the case in a real-world scenario in which you would need to read and write this information to some type of external storage or perhaps via a socket stream. From the programmer's perspective, the most significant change from the previous example is that the calls to the ExportParameters and ImportParameters methods of the RSACryptoServiceProvide class have been replaced with calls to the ToXmlString and FromXmlString methods of the same class. Once again, a boolean parameter is used to indicate whether private information is included or excluded in the stored key information.
Here is the GenerateNewRSAParams method, which serves the same basic purpose as described in the previous program example. The difference is that we are storing the key information in XML format, in two files named PublicPrivateKey.xml and PublicOnlyKey.xml, by calling the ToXmlString method with a boolean parameter. These two files will be used later in the encryption and decryption functions.
private void GenerateNewRSAParams() { //establish RSA asymmetric algorithm RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); //provide public and private RSA params StreamWriter writer = new StreamWriter("PublicPrivateKey.xml"); string publicPrivateKeyXML = rsa.ToXmlString(true); writer.Write(publicPrivateKeyXML); writer.Close(); //provide public only RSA params writer = new StreamWriter("PublicOnlyKey.xml"); string publicOnlyKeyXML = rsa.ToXmlString(false); writer.Write(publicOnlyKeyXML); writer.Close(); //display public and private RSA key textBoxPublicKeyXML.Text = publicPrivateKeyXML; //do UI stuff ... }
Next, let's look at the buttonEncrypt_Click method. We create a new RSACryptoServiceProvider object and initialize it by calling the FromXmlString method with the public key information stored in the PublicOnlyKey.xml file. Then we call the RSA object's Encrypt method to perform the cryptographic transformation on the plaintext.
private void buttonEncrypt_Click( object sender, System.EventArgs e) { //do UI stuff ... //establish RSA asymmetric algorithm RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); //public only RSA parameters for encrypt StreamReader reader = new StreamReader("PublicOnlyKey.xml"); string publicOnlyKeyXML = reader.ReadToEnd(); rsa.FromXmlString(publicOnlyKeyXML); reader.Close(); //read plaintext, encrypt it to ciphertext byte[] plainbytes = Encoding.UTF8.GetBytes(textPlaintext.Text); cipherbytes = rsa.Encrypt( plainbytes, false); //fOAEP needs high encryption pack //display ciphertext as text string ... //display ciphertext byte array in hex format ... //do UI stuff ... }
Finally, the buttonDecrypt_Click method creates its own new RSACryptoServiceProvider object, but it initializes it by calling FromXmlString using the PublicPrivateKey.XML file, which contains both public and private key information—a requirement of RSA decryption.
private void buttonDecrypt_Click( object sender, System.EventArgs e) { //establish RSA using key XML from encrypt RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); //public and private RSA parameters for encrypt StreamReader reader = new StreamReader("PublicPrivateKey.xml"); string publicPrivateKeyXML = reader.ReadToEnd(); rsa.FromXmlString(publicPrivateKeyXML); reader.Close(); //read ciphertext, decrypt it to plaintext byte[] plainbytes = rsa.Decrypt( cipherbytes, false); //fOAEP needs high encryption pack //display recovered plaintext ... //do UI stuff ... }
Figure 4-4 shows the SavingKeysAsXml example being used to encrypt and decrypt a plaintext message. Notice the XML display shows contents of the PublicPrivateKey.xml file that is being used by the decryption method. It is a bit difficult to read with all the XML elements running in a single, continuous stream, but if you look closely at it, you should be able to see each of the RSA parameter values used. The encryption method uses only the modulus and exponent elements.
Figure 4-4. The SavingKeysAsXml example program.