© Stephen Haunts 2019
Stephen HauntsApplied Cryptography in .NET and Azure Key Vaulthttps://doi.org/10.1007/978-1-4842-4375-6_7

7. Asymmetric Encryption

Stephen Haunts1 
(1)
Belper, Derbyshire, UK
 
In Chapter 6, we looked at symmetric encryption, which is a two-way encryption process that uses the same key for both encryption and decryption of your message, as shown in Figure 7-1. In the diagram, we have plaintext, or our original data, represented on the left, which is then encrypted with a key to produce our encrypted data, or ciphertext. That data is decrypted using the same key to uncover the original data, or plaintext. This process is referred to as symmetric encryption because we use the same key for both encryption and decryption.
../images/457525_1_En_7_Chapter/457525_1_En_7_Fig1_HTML.png
Figure 7-1

Symmetric encryption

In Chapter 6, we looked at some advantages and disadvantages of symmetric encryption.

Advantage: Very Secure

When using a secure algorithm such as AES, symmetric encryption is exceptionally secure. One of the most widely used, symmetric, key encryption systems is the US government-designated AES. As of the writing of this book, AES is unbroken, so it is one of the recommended algorithms.

Advantage: Fast

One of the problems with public key encryption systems like RSA is that they need complicated mathematics to work, which makes them computationally intensive and slow. Encrypting and decrypting symmetric key data is easier, which gives you excellent read and write performance. In fact, many solid-state drives, which are very fast, use symmetric key encryption store data, yet they are still a lot faster than unencrypted standard hard drives.

Disadvantage: Sharing Keys Is Hard

One of the most significant problems with symmetric key encryption algorithms is that you need to have a way to get the key to the person with whom you are sending the encrypted data. Encryption keys aren’t simple strings of text like passwords; they are byte arrays of randomly generated data, such as the random numbers we created with RNGCryptoServiceProvider earlier in the book. As such, you need to have a safe way to get the key to the other person.

With this in mind, symmetric key encryption is particularly useful when encrypting your information as opposed to when sharing encrypted information. There are ways to use the power of symmetric encryption with a suitable key sharing scheme, which we explore in another chapter when I talk about hybrid encryption schemes.

Disadvantage: Dangerous If Compromised

When someone gets hold of one of your symmetric keys, they can decrypt everything encrypted with that key. When you’re using symmetric encryption for two-way communications, this means that both sides of the conversation get compromised. With asymmetric cryptography, such as RSA, if someone gets your private key, they can decrypt messages sent to you, but they can’t decrypt what you sent to the other recipient because it was encrypted with a completely different key pair.

With this in mind, let’s take a closer look at asymmetric encryption.

What Is Asymmetric Encryption?

The main problem with symmetric encryption is that of securely sharing keys. For a recipient to decrypt a message, they need the same key as the sender, and this exchange of keys can be difficult to do securely. An excellent solution to this problem is to use asymmetric cryptography, which is also referred to as public key cryptography .

With public key cryptography, you have two keys. A public key, which anyone can know, and a private key, which only the recipient of a message knows. These keys are mathematically linked. The message sender uses the public key to encrypt a message, and the recipient uses their private key to decrypt the message; this is demonstrated in Figure 7-2.

The word asymmetric is used because this method uses two different linked keys that perform inverse operations, whereas symmetric cryptography uses the same key to perform both operations.
../images/457525_1_En_7_Chapter/457525_1_En_7_Fig2_HTML.png
Figure 7-2

Asymmetric encryption

It is quite straightforward to generate both the public and private key pair, but the power of asymmetric cryptography comes from the fact it is impossible for a private key to be determined from its corresponding public key. It is only the private key that needs to be kept secret in the key pair.

The primary advantage to using asymmetric encryption is that two parties don’t need to have pre-shared a secret key to communicate using asymmetric encryption. The person encrypting a message only needs to know the recipient’s public key, which is available to anyone on request. Then only the recipient can decrypt the message with their private key. The main disadvantage is that the asymmetric algorithm is comparatively complex when compared to symmetric encryption, which means that messages take longer to encrypt and decrypt.

The History of RSA

RSA is a public key encryption technology developed by RSA Security LLC (formerly RSA Security, Inc.). The name RSA forms an acronym that stands for the names of its creators, Rivest, Shamir, and Adelman. RSA gets its power from the idea that there is no efficient way to factor huge prime numbers. Deducing an RSA key requires an extraordinary amount of computer processing power and time. The RSA algorithm has become the de facto standard for industrial-strength encryption, especially for data sent over the Internet. RSA is built into many software products.

There is a drawback to RSA, though. You can only encrypt data that is smaller than the size of the key, which makes RSA quite limited. It is more common to use RSA to encrypt a randomly generated, symmetric AES key, which means you can send the RSA-encrypted AES key safely to a recipient and then use AES with that key to encrypt your data.

We explore this option in the next module on hybrid encryption. You could, of course, split your data into smaller chunks and encrypt each of these in turn, and then when you decrypt your data chunks, combine the results back together to form your original message, but this would be incredibly inefficient with RSA.

How Does RSA Work?

RSA typically uses three key sizes: 1024 bit, 2048 bit, and 4096 bit. By today’s standards, you should use at least a 2048-bit key because 1024-bit keys are now considered weak. Ideally, use the largest key size, 4096 bits, but 2048 bits is acceptable.

Key Derivation

The public and private key pairs in RSA are based on prime numbers, and their security comes from the difficulty in factorizing prime numbers. What this means is that if you have a large prime number, it is tough to determine what two prime numbers are multiplied together to make the larger number.

Let’s look at a simple example.

If you were to pick two prime numbers, let’s say 23 and 17, and I asked you to multiply these numbers, you could work it out quite comfortably in your head or with a calculator. The answer is 391. But if I were to ask you which two prime numbers do you multiply to make 5963, it would be a harder mathematical problem to solve. The answer is 67 × 89 = 5963.

Now imagine numbers that are considerably larger, for example, a 2048-bit number or 256-byte number. The strength of RSA keys is because going from one large number back to the original primes that made that number initially is a tough problem to solve, which is where the strength of RSA comes into effect.

Now let’s equate this example back to private and public keys. Alice wishes to send an important message to Bob, so she uses Bob’s public key to encrypt the message. In the simple example we just looked at, the public key is 5963. Alice then encrypts the message and sends it to Bob, where he uses his private key to decrypt the message. In our example, the private key consists of the two prime numbers: 67 and 89.

This is a very simplified example. The reality is more complicated and goes outside the scope of this book because there are many more components to an RSA key than just the prime numbers, but it is beneficial for you to have this simplified mental model of an RSA key.

In Figure 7-3, the internals of a public and private key in .NET are shown in the debugger. The class we use to hold an RSA key is called RSAParameters.
../images/457525_1_En_7_Chapter/457525_1_En_7_Fig3_HTML.jpg
Figure 7-3

Observing RSA keys in the debugger

As you can see in the screenshot, there are more components to the public and private key.
  • P is one of our prime numbers.

  • Q is the other prime number.

  • Modulus is the result of both prime numbers multiplied together as demonstrated a moment ago.

  • Exponent is called the public exponent.

  • InverseQ is the inverse Q coefficient.

  • D is the private exponent that is only present in the private key.

  • DP and DQ are exponents that are only present in the private key.

The relevant parts to note are that in the private key, P and Q are both populated; they are the two prime numbers. In the public key, P and Q are not present because they are secret to anyone who uses the public key; but the modulus is populated, which is the result of multiplying the two prime numbers.

Encryption and Decryption

Unlike symmetric encryption algorithms such as AES, where the encryption and decryption process are algorithmic (the plaintext is split it into smaller blocks and run through several rounds of obfuscation with the key), RSA takes a more mathematical approach and is based on modular arithmetic.

In mathematics, modular arithmetic is an integer-based system, where numbers “wrap around” when reaching a certain value. This wrap around value is called the modulus. A favorite example for describing the use of modular arithmetic is defining a 12-hour clock, where a 24-hour day is divided into two 12-hour blocks.

If the time is 7:00 a.m., then it will be 3:00 p.m. eight hours later.

A standard addition would suggest that the time should be 15.00 (7 + 8 = 15), but because we are dealing with a day that is split into two 12-hour blocks, it is not the correct answer because the clock will wrap around every 12 hours.

As another example, if we have the clock set to at 12:00 (noon) and 21 hours pass, then the correct time will be 9:00 a.m. the next day, rather than 33:00; this is because the hour number will start over again once it reaches 12; this is referred to as arithmetic modulo 12.

Because the encryption process is mathematical and based on Modulus arithmetic, there are limits to the amount of data you can encrypt in one go. As a rule, you cannot encrypt data larger than the size of the key. So, if you are encrypting using a 2048-bit key, which is 256 bytes in length, then you cannot encrypt more than 256 bytes. The more data you encrypt, the slower the encryption process is even though RSA makes the key sharing problem easier, it is quite an inefficient encryption system.

RSA in .NET

As with the symmetric encryption implementation in .NET, the application of RSA is straightforward; if anything, it is easier, which is excellent news. The main class that you need is RSACryptoServiceProvider, which is used for key generation as well as encryption and decryption.

Let’s look at key generation first.

In-Memory Keys

You generate RSA key pairs by first constructing the RSACryptoServiceProvider class.
private RSAParameters _publicKey;
private RSAParameters _privateKey;
public void AssignNewKey()
{
    using (var rsa = new RSACryptoServiceProvider(2048))
    {
        rsa.PersistKeyInCsp = false;
        _publicKey = rsa.ExportParameters(false);
        _privateKey = rsa.ExportParameters(true);
    }
}

Valid lengths for the key size parameter that you pass into the constructor are 1024 bits, 2048 bits, and 4096 bits. In the preceding example code, we are creating a public and a private key and keeping them in objects in memory. We look at what the PersistKeyInCsp property means in a moment, but for this example, it is set to false. To export the key material, you call the ExportParameters method on the RSA object instance of RSACryptoServiceProvider. To export the public key, you pass false into ExportParameters; to export the private key, you pass in true.

If you are using .NET Core, then this method of creating keys using RSACryptoServiceProvider works cross-platform across Windows, macOS, and Linux, which is the only supported way of creating the RSA keys programmatically across all three platforms. The next two techniques for XML keys and CSP keys only work on Windows.

XML-Based Keys

The next method of key generation supported by RSACryptoServiceProvider is to export or import XML-based keys. In the following code example, we pass in two parameters, which represent the file names of the public and private keys that you want to generate. Next, we create an instance of RSACryptoServiceProvider and pass in our key length. In this example, we are using a 2048-bit key. Again, we are setting the PersistKeyInCsp flag to false again.
public void AssignNewKey(string publicKeyPath,
                         string privateKeyPath)
{
    using (var rsa = new RSACryptoServiceProvider(2048))
    {
        rsa.PersistKeyInCsp = false;
          File.WriteAllText(publicKeyPath,
               rsa.ToXmlString(false));
          File.WriteAllText(privateKeyPath,
               rsa.ToXmlString(true));
        }
 }

Next, in this example, we use the WriteAllText static method on the File class to save out the XML text of the public and private keys to disk. To export the actual key material, we use the ToXmlString method on the RSACryptoServiceParameter instance.

It is important to stress though, that just because you can export RSA keys as XML files, you should question your need to do this. If you need to do this for development tools or other tooling, then that’s fine. If you intend to export RSA keys and store them in a folder on a server somewhere for a production system, then this is not a good idea at all. Files are tough to protect. Your servers are susceptible to being accessed by people who shouldn’t, and they could copy, delete, or tamper with keys. There are much better ways to use keys: use the cryptographic service provider (Windows only), load keys from the certificate store (also Windows only), or use a protected key vault, such as Azure Key Vault, which is covered later in the book.

Cryptographic Service Provider

Storing private keys on the file system is not a great idea, but if you are using a Windows-based system, then you can use Windows inbuilt key container system called the cryptographic service provider (CSP). A key container in Windows is a place that the operating system can store keys, and they can be stored at the user level or the machine level. The machine-level key containers are available to all users, and the user level containers are only available to the user that created or imported that key container. The following code generates a set of keys and stores them in a named container.
const string ContainerName = "MyContainer";
public void AssignNewKey()
{
    CspParameters cspParams = new CspParameters(1)
    {
        KeyContainerName = ContainerName,
        Flags = CspProviderFlags.UseMachineKeyStore,
        ProviderName =
                "Microsoft Strong Cryptographic Provider"
    };
    var rsa = new RSACryptoServiceProvider(cspParams)
    {
         PersistKeyInCsp = true
    };
}

To generate an RSA asymmetric key and store it within a key container you need first to instantiate the CspParameters class and pass in the name that you want to call the key container to the CspParameters.KeyContainerName field. Using the Flags property, you have a choice of storing the key in either a local user key container or a machine-level key container.

In our example, we are using a machine-level key store. The ProviderName field represents a specific name used for a key container on the system. The default Windows provider is the Microsoft Strong Cryptographic Provider. If you are using any specific key management hardware that is compatible with the Windows CSP, then the name you provide in that string relates to the hardware whose drivers should be installed on the system.

Key containers that are at the user level are stored inside the Windows user profile for that user. The key container can then be used to encrypt and decrypt data for any applications that run using that user identity. Having key containers at the user level is useful if you require that the RSA keys are removed with the user profile is deleted. However, because you must be logged in with the specific user account that makes use of the user-level RSA key container to encrypt or decrypt protected configuration sections, they are inconvenient to use.

In addition to the user-level RSA key containers, there are machine-level containers that can be available to every user that logs into a computer. Machine-level key containers are useful because you can use them to both encrypt and decrypt data while logged in with any user account, including admin accounts. Although machine-level RSA key containers are available to all users, they can be secured with NTFS access control lists (ACLs) so that only required users can access them.

Next, you create a new instance of the RSACryptoServiceProvider and pass the previously created CspParameters object to its constructor.
var rsa = new RSACryptoServiceProvider(cspParams)
{
    PersistKeyInCsp = true
};
When you provide a CspParameters object to the RSACryptoServiceProvider, the PersistKeyInCsp flag is automatically set to true, but in this example, I have set it explicitly for illustrative purposes.
public void DeleteKeyInCsp()
{
    var cspParams = new CspParameters
    {
       KeyContainerName = ContainerName
    };
    var rsa = new RSACryptoServiceProvider(cspParams)
    {
       PersistKeyInCsp = false
    };
    rsa.Clear();
}

To remove a key from a key container, you first need to instantiate another instance of the CspParameters class and pass the name of the key container to the CspParameters.KeyContainerName property. Then you create a new instance of a class RSACryptoServiceProvider and pass the previously created CspParameters object to its constructor. Next, set the PersistKeyInCSP property of the RSACryptoServiceProvider class to false. Finally, you need to call the Clear method on the instance of RSACryptoServiceProvider. Calling this method releases all resources of the RSA instance and clears the key container.

Encryption and Decryption

Now that we have looked at generating RSA public and private keys, let’s take a look at the encryption and decryption process. Our first example is based on having your public and private keys loaded into local RSAParameters properties , as shown in the following code.
private RSAParameters _publicKey;
private RSAParameters _privateKey;
public byte[] EncryptData(byte[] dataToEncrypt)
{
    byte[] cipherbytes;
    using (var rsa = new RSACryptoServiceProvider())
    {
        rsa.PersistKeyInCsp = false;
        rsa.ImportParameters(_publicKey);
        cipherbytes = rsa.Encrypt(dataToEncrypt, true);
    }
    return cipherbytes;
}

Once you have your keys set up, the encryption process is very straightforward. If you look at the code snippet, you pass in your data to encrypt as a byte array and then create a new instance of RSACryptoServiceProvider. You import the public key to encrypt with using the ImportParamters method, and you have to make sure PersistKeyInCSP is set to false. Then you call the Encrypt method on the RSACryptoServiceProvider object.

The second parameter on the encrypt method is for optimal asymmetric encryption padding (OEAP), which is a padding scheme that is used within RSA. OEAP is a form of the Feistel network that is designed to add an element of randomness to the encryption process, which helps prevent the partial decryption of ciphertext by ensuring that an attacker cannot recover any portion of the plaintext.

Once you have called Encrypt, you get a byte array back containing your encrypted data. The encryption process is similar if you are using a key container.

Let’s now look at the decryption process as shown in the following code.
public byte[] DecryptData(byte[] dataToEncrypt)
{
    byte[] plain;
    using (var rsa = new RSACryptoServiceProvider())
    {
        rsa.PersistKeyInCsp = false;
        rsa.ImportParameters(_privateKey);
        plain = rsa.Decrypt(dataToEncrypt, true);
    }
    return plain;
}

The code looks virtually identical to the Encrypt method, apart from where we load the private key instead of the public key, and we call Decrypt instead of Encrypt. Remember that with RSA, we encrypt using our recipient’s public key, which they are free to share with us, and they decrypt the message using their private key, which only they know.

If we were to write a method to call this encryption and decryption process, it might look something like the following.
private static void RsaWithRsaParameterKey()
{
    var rsaParams = new RSAWithRSAParameterKey();
    const string original = "Text to encrypt";
    rsaParams.AssignNewKey();
    var encrypted =
         rsaParams.EncryptData(
             Encoding.UTF8.GetBytes(original));
    var decrypted =
          rsaParams.DecryptData(encrypted);
    Console.WriteLine("RSA Encryption Demonstration in .NET");
    Console.WriteLine("------------------------------------");
    Console.WriteLine();
    Console.WriteLine("In Memory Key");
    Console.WriteLine();
    Console.WriteLine("   Original Text = " + original);
    Console.WriteLine("   Encrypted Text = "
          + Convert.ToBase64String(encrypted));
    Console.WriteLine("   Decrypted Text = "
          + Encoding.Default.GetString(decrypted));
}

First, we create an instance of our class that contains the Encrypt and Decrypt methods. Then we call the code to assign a new key. In this example, the public and private keys are stored as members’ variables. Then we call the Encrypt method, but first, our data to encrypt, which is a string, is first converted into a byte array using Encoding.UTF8.GetBytes. Once the Encrypt method returns, we have our encrypted data (which used the public key for encryption) as a byte array.

Then the example code decrypts the data by passing in the encrypted byte array to Decrypt. This time the decryption happens using the private key to recover the plaintext, which is also sent back as a byte array. Then the sample code prints the original message, converts the encrypted ciphertext into a base64 string for display, and turns the decrypted plaintext back into a string. You can see the results of this in the screenshot in Figure 7-4.
../images/457525_1_En_7_Chapter/457525_1_En_7_Fig4_HTML.jpg
Figure 7-4

Example result of encrypting some text with RSA

Encrypted and decrypting with XML-based keys is very similar, except there is one small difference, as shown in the following code.
public byte[] EncryptData(string publicKeyPath,
                          byte[] dataToEncrypt)
{
    byte[] cipherbytes;
    using (var rsa = new RSACryptoServiceProvider(2048))
    {
        rsa.PersistKeyInCsp = false;
        rsa.FromXmlString(File.ReadAllText(publicKeyPath));
        cipherbytes = rsa.Encrypt(dataToEncrypt, false);
    }
    return cipherbytes;
}
public byte[] DecryptData(string privateKeyPath,
                          byte[] dataToEncrypt)
{
    byte[] plain;
    using (var rsa = new RSACryptoServiceProvider(2048))
    {
        rsa.PersistKeyInCsp = false;
        rsa.FromXmlString(File.ReadAllText(privateKeyPath));
        plain = rsa.Decrypt(dataToEncrypt, false);
    }
    return plain;
}

When encrypting and decrypting, instead of calling ImportParameters to load the keys, we call FromXmlString and pass in the results of loading the key from disk. Again, loading keys from disk like this is very risky, and I don’t recommend storing RSA keys as files, but you can do it if needed. Remember that using XML-based keys only works on the Windows version of .NET Framework and .NET Core. This operation is not supported on macOS and Linux as of .NET Core 2.1.

What if you are using the CSP key container? The code is very similar, but there is a small change to load the keys from the key container.
public byte[] EncryptData(byte[] dataToEncrypt)
{
    byte[] cipherbytes;
    var cspParams = new CspParameters
    {
       KeyContainerName = ContainerName
    };
  using (var rsa = new RSACryptoServiceProvider(2048,
            cspParams))
    {
        cipherbytes = rsa.Encrypt(dataToEncrypt, false);
    }
    return cipherbytes;
}
public byte[] DecryptData(byte[] dataToDecrypt)
{
    byte[] plain;
    var cspParams = new CspParameters
    {
        KeyContainerName = ContainerName
    };
    using (var rsa = new RSACryptoServiceProvider(2048,
        cspParams))
    {
        plain = rsa.Decrypt(dataToDecrypt, false);
    }
    return plain;
}

In both the Encrypt and Decrypt methods, we construct a new CspParameters object and load in the container name into the KeyContainerName property. Then when we construct the RSSCryptoServiceProvider class, we pass in the CspParameters instance as the seconds’ constructor parameter, which loads the relevant key, and there is no need to import or load the keys manually.

Summary

In this chapter, we covered the second part of our confidentiality pillar of cryptography. Asymmetric encryption involves using an algorithm like RSA that uses split public and private keys to encrypt data. By using split keys like this, we overcome the problem we discussed with symmetric encryption where the key is hard to share between a sender and receiver of a message. With asymmetric encryption, this isn’t an issue. The recipient of a message has two keys, a public, and a private key. The public key they can share with anyone, and the private key they keep secret. If you wanted to send a message to the recipient, you get their public key, and encrypt your message and send it to them. They then use their private key to recover the message.

If they want to send you a reply, they get your public key and encrypt their message, which they then send you, and you use your private key to recover the message. Using split public and private keys like this means that you need two sets of public and private key pairs for a two-way conversation, one for each receiver.

In the next chapter, we look at the final cryptographic pillar: non-repudiation.

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

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