Cryptography in .NET : Hybrid Encryption Protocols

This is the 4th and final article in a series on cryptography with .NET. So far in this series we have looked at some of the built in cryptographic primitives build into .NET including, Symmetric Encryption with AES, Asymmetric encryption using RSA, Cryptographic random number generation and hashing.

Cryptography in .NET : Hybrid Encryption Protocols

Cryptography in .NET : Hybrid Encryption Protocols

What I want to do in this final part is talk about using these different cryptographic primitives to do what is called Hybrid Encryption.

What is Hybrid Encryption?

So, what is hybrid encryption? Let’s start off with Wikipedia’s definition.

In cryptography, public-key cryptosystems are convenient in that they do not require the sender and receiver to share a common secret in order to communicate securely (among other useful properties). However, they often rely on complicated mathematical computations and are thus generally much more inefficient than comparable symmetric-key cryptosystems. In many applications, the high cost of encrypting long messages in a public-key cryptosystem can be prohibitive. A hybrid cryptosystem is one which combines the convenience of a public-key cryptosystem with the efficiency of a symmetric-key cryptosystem.

A hybrid cryptosystem can be constructed using any two separate cryptosystems:

  • a key encapsulation scheme, which is a public-key cryptosystem, and
  • a data encapsulation scheme, which is a symmetric-key cryptosystem.

The hybrid cryptosystem is itself a public-key system, who’s public and private keys are the same as in the key encapsulation scheme.

Note that for very long messages the bulk of the work in encryption/decryption is done by the more efficient symmetric-key scheme, while the inefficient public-key scheme is used only to encrypt/decrypt a short key value.

The key thing here is that the definition states that because asymmetric algorithms are very math intensive they are not great at encrypting large volumes of data, so you can generate a temporary session key which you encrypt with RSA and then use that session key to encrypt your larger data with a symmetric algorithm like AES. This is what the example later in the article will do.

Security Requirements

When building an encryption protocol there are various security requirements that you can aim to achieve. These are Confidentiality, Data Integrity, Data Origin Authentication, and Non Repudiation.

Confidentiality

Confidentiality is the assurance that data cannot be viewed by an unauthorised user. Confidentiality is the classic security service can is provided by most applications.

Data Integrity

Data Integrity is the assurance that data has not been altered in an unauthorised manner. This assurance applies from the time that the data was last created, transmitted or stored by an authorised user. Data integrity isn’t concerned with the prevention of alteration of data, but provides a means for detecting whether data has been manipulated in an unauthorised way.

Data Origin Authentication

Data Origin Authentication is the assurance that a given entity was the original source of received data.  In other words, if a technique provides data origin authentication that some data came from Alice, then this means that the receiver bob can be sure that the data did originally come from Alice at some time in the past. Bob doesn’t care necessarily when she sent it, but he does care that Alice is the source of the data. Data Origin Authentication is sometimes referred to as message authentication since it is primarily concerned with the authentication of the data (message) and not who we are communicating with at the time the data is received.

Non-Repudiation

Non-Repudiation is the assurance that an entity cannot deny a previous commitment or action. Most commonly, non-repudiation is the assurance that the original source of some data cannot deny to a third party that this is the case. This is a stronger requirement than data origin authentication, since data origin authentication only requires this assurance to be provided to the receiver of the data. Non-Repudiation is a property that is most desirable in the situations where there is the potential for a dispute to arise over the exchange of data.

Creating a Hybrid Encryption Protocol

With what we now know about cryptography in .NET and the basic security requirements we can start to plan our protocol. I am going to base this protocol on something I built recently for a PCI DSS Card Vault. I used a hybrid scheme to securely transfer card numbers over the company network from an application (point of sale system for example) into a protected card environment where the number was safely re-encrypted and tokenised.

Below is a sequence diagram that demonstrates the basic sequence of events.

Cryptography in .NET : Hybrid Encryption Protocols

Cryptography in .NET : Hybrid Encryption Protocols

Out of the 4 security requirements stated above, this system aims to satisfy Confidentiality, Data Integrity, and Data Origin Authentication. Confidentiality because we want to keep the information secret whilst we transfer it, Data Integrity as we want to check that the data hasn’t been corrupted or deliberately tampered with in transit and Data Origin Authentication as we want to know where the data came from.

First of all let’s take a look at the interface for what we are building.

using System;
using System.Collections.Generic;
using System.Linq;

namespace CryptoLibrary
{
    public class Program
    {
        public static void Main ()
        {
            DataEncrypter encrypter = new DataEncrypter();

            string rhyme = "Mary had a little lamb, whose fleece was white as snow. And everywhere that Mary went," +
                           "the lamb was sure to go. It followed her to school one day which was against the rule. " +
                           "It made the children laugh and play, to see a lamb at school. And so the teacher turned " +
                           "it out, but still it lingered near, And waited patiently about, till Mary did appear. " +
                           "Why does the lamb love Mary so? the eager children cry. Why, Mary loves the lamb, you " +
                           "know. the teacher did reply.";

            EncryptedDataBlock encryptedDataBlock = encrypter.EncryptData(rhyme);

            string decryptedData = encrypter.DecryptDataBlock(encryptedDataBlock);

            if (String.Compare(decryptedData, rhyme, false) != 0)
            {
                throw new InvalidOperationException("Original data and Decrypted data do not match.");
            }
        }
    }
}

From this usage you can see what we are doing is quite simple. We initialise a string that contains a nursery rhyme. We then call EncryptData() on the DataEncrypter object and pass the rhyme in. This method encrypts and signs the data and returns an EncryptedCardBlock object. When then pass this into the DecryptDataBlock() method to retrieve the original nursery rhyme.

The EncryptedDataBlock is an important object for this exercise so let’s take a look.

using System;
using System.Collections.Generic;
using System.Linq;

namespace CryptoLibrary
{
    public class EncryptedDataBlock
    {
        public string EncryptedNonce;
        public string EncryptedData;
        public string DigitalSignature;
        public string EncryptedIPAddress;
        public string EncrytedMACAddress;
    }
}

This class contains 5 strings. They are:

EncryptedNonce : This is a temporary generated session key that is a cryptographic random number that is then encrypted with the RSA public key.

EncryptedData : This is the data (in this case the nursery rhyme) encrypted using AES and the Nonce (the random number session key).

EncryptedSignature : This is a hash of the EncryptedData, users IP address and the users MAC address. This hash is then encrypted with the RSA public key.

EncryptedIPAddress : This is the user IP address encrypted with the RSA public key.

EncryptedMACAccress : This is the users MAC address encrypted with the RSA public key.

The method to encrypt the data looks like the following.

        public EncryptedDataBlock EncryptData(string dataToEncrypt)
        {
            EncryptedDataBlock encryptedDataBlock = new EncryptedDataBlock();

            // Generate a NONCE
            string nonce = Convert.ToBase64String(rnd.Generate(30));

            // Encrypt NONCE with public key.
            encryptedDataBlock.EncryptedNonce = rsa.EncryptData(@"C:\RSAKeys\publickey.xml", nonce);

            // Encrypt card number using nonce.
            encryptedDataBlock.EncryptedData = aes.Encrypt(dataToEncrypt, nonce);

            // Calculate Digital Signature of encrtyted card.
            string hash = Convert.ToBase64String(sha.ComputeHash(encryptedDataBlock.EncryptedData + ipAddress + macAddress));
            encryptedDataBlock.DigitalSignature = rsa.EncryptData(@"C:\RSAKeys\publickey.xml", hash);

            // Encrypt IP Address and MAC Address.
            encryptedDataBlock.EncryptedIPAddress = rsa.EncryptData(@"C:\RSAKeys\publickey.xml", ipAddress);
            encryptedDataBlock.EncrytedMACAddress = rsa.EncryptData(@"C:\RSAKeys\publickey.xml", macAddress);

            return encryptedDataBlock;
        }

The comments in the code speak for themselves. First we generate a session key from a cryptographic random number (this is called a nonce). We then encrypt this session key with the public key. Next we encrypt our data (the nursery rhyme) with AES using the generated session key. Next we calculate a hash of the encrypted data plus the users IP address and MAC address. This is then encrypted using the public key. Next we protect the users IP and MAC address with the public key. All this data is put into the EncryptedDataBlock and then returned from the method.

Once you have run this method, the EncryptedDataBlock looks like the following:

Cryptography in .NET : Hybrid Encryption Protocols

Cryptography in .NET : Hybrid Encryption Protocols

To decrypt the data we need to first decrypt enough of the data to verify the digital signature to check that that data integrity hasn’t been compromised. If this verification passes ok, we can then progress to decrypt the actual data.

        public string DecryptDataBlock(EncryptedDataBlock dataBlock)
        {
            // Check digital signature for signs of tampering.
            ValidateDigitalSignature(dataBlock);

            // DecryptCardBlock Card
            string nonce = rsa.DecryptData(@"c:\privatekey.xml", dataBlock.EncryptedNonce);
            return aes.Decrypt(dataBlock.EncryptedData, nonce);
        }

        private void ValidateDigitalSignature(EncryptedDataBlock dataBlock)
        {
            try
            {
                string ipAddress = rsa.DecryptData(@"c:\privatekey.xml", dataBlock.EncryptedIPAddress);
                string macAddress = rsa.DecryptData(@"c:\privatekey.xml", dataBlock.EncrytedMACAddress);

                string decryptedSignature = rsa.DecryptData(@"c:\privatekey.xml", dataBlock.DigitalSignature);
                string hashedCardBlock = Convert.ToBase64String(sha.ComputeHash(dataBlock.EncryptedData + ipAddress + macAddress));

                if (String.Compare(decryptedSignature, hashedCardBlock, StringComparison.OrdinalIgnoreCase) != 0)
                {
                    throw new InvalidOperationException("The computed digital signature for the card block does not match the original digital signature.");
                }
            }
            catch (CryptographicException exception)
            {
                throw new InvalidOperationException("There was a problem decrypting the card block. Potential data corruption or packet tampering has occured.", exception);
            }
        }

When DecryptCard() is called we first call ValidateDigitalSignature(). To validate the digital signature we decrypt the IP and MAC addresses using the private key. Then we decrypt the digital signature with the private key to give us the original hash.

Then we recalculate the hash using the encrypted data, IP and MAC address. Then we verify that the data integrity is intact. We compare the newly generated hash to the hash retrieved from the digital signature. If they are the same then we can guarantee that the data is intact. The inclusion of the IP address and MAC address in the digital signature from the original caller gives us the data origin authentication as we are checking the addressed encoded in the signature with the RSA encrypted addresses.

That is really all there is too it. I hope you’ll agree that this protocol is not that hard to understand, but the use of the lower level cryptographic primitives gives us a powerful set of tools for creating hybrid encryption protocols. This is just one example to illustrate the point, but you can use these building blocks create lots of different protocols. Because each of the primitives, AES and RSA specifically, are recognised and recommended primitives, you can be assured of their strength and protection from attackers. The beauty of this example is that we use a generated session key called a Nonce (Number Once). This key is never reused. Every time we encrypt data with this scheme we generate a new session key and protect it with RSA. Should an attacker ever manage to recover that key, it is only useful for that particular packet as it will be thrown away for the next call.

Here is the complete source code for the DataEncrypter class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;

namespace CryptoLibrary
{
    public class DataEncrypter
    {
        private string ipAddress = "10.0.0.1";
        private string macAddress = "00-B0-D0-86-BB-F7";
        private RandomNumberGenerator rnd = new RandomNumberGenerator();
        private RSA rsa = new RSA();
        private AES aes = new AES();
        private SecureHash sha = new SecureHash();

        public EncryptedDataBlock EncryptData(string dataToEncrypt)
        {
            EncryptedDataBlock encryptedDataBlock = new EncryptedDataBlock();

            // Generate a NONCE
            string nonce = Convert.ToBase64String(rnd.Generate(30));

            // Encrypt NONCE with public key.
            encryptedDataBlock.EncryptedNonce = rsa.EncryptData(@"C:\RSAKeys\publickey.xml", nonce);

            // Encrypt card number using nonce.
            encryptedDataBlock.EncryptedData = aes.Encrypt(dataToEncrypt, nonce);

            // Calculate Digital Signature of encrtyted card.
            string hash = Convert.ToBase64String(sha.ComputeHash(encryptedDataBlock.EncryptedData + ipAddress + macAddress));
            encryptedDataBlock.DigitalSignature = rsa.EncryptData(@"C:\RSAKeys\publickey.xml", hash);

            // Encrypt IP Address and MAC Address.
            encryptedDataBlock.EncryptedIPAddress = rsa.EncryptData(@"C:\RSAKeys\publickey.xml", ipAddress);
            encryptedDataBlock.EncrytedMACAddress = rsa.EncryptData(@"C:\RSAKeys\publickey.xml", macAddress);

            return encryptedDataBlock;
        }

        public string DecryptDataBlock(EncryptedDataBlock dataBlock)
        {
            // Check digital signature for signs of tampering.
            ValidateDigitalSignature(dataBlock);

            // DecryptCardBlock Card
            string nonce = rsa.DecryptData(@"c:\privatekey.xml", dataBlock.EncryptedNonce);
            return aes.Decrypt(dataBlock.EncryptedData, nonce);
        }

        private void ValidateDigitalSignature(EncryptedDataBlock dataBlock)
        {
            try
            {
                string ipAddress = rsa.DecryptData(@"c:\privatekey.xml", dataBlock.EncryptedIPAddress);
                string macAddress = rsa.DecryptData(@"c:\privatekey.xml", dataBlock.EncrytedMACAddress);

                string decryptedSignature = rsa.DecryptData(@"c:\privatekey.xml", dataBlock.DigitalSignature);
                string hashedCardBlock = Convert.ToBase64String(sha.ComputeHash(dataBlock.EncryptedData + ipAddress + macAddress));

                if (String.Compare(decryptedSignature, hashedCardBlock, StringComparison.OrdinalIgnoreCase) != 0)
                {
                    throw new InvalidOperationException("The computed digital signature for the card block does not match the original digital signature.");
                }
            }
            catch (CryptographicException exception)
            {
                throw new InvalidOperationException("There was a problem decrypting the card block. Potential data corruption or packet tampering has occured.";, exception);
            }
        }
    }
}

That concludes this small article on Hybrid Cryptography with .NET. To summarise so far we have covered symmetric encryption using AES, Asymmetric encryption using RSA, and secure hashes and random number generations. When have then used these cryptographic primitives to build what is called a hybrid encryption protocol. In the next article we will look at using Digital Signatures in .NET.

Participate with Coding in the Trenches on Facebook

Participate with Coding in the Trenches on Facebook by Click the button above.

3 thoughts on “Cryptography in .NET : Hybrid Encryption Protocols

  1. Pingback: Introduction to AES Encryption | Stephen Haunts { Coding in the Trenches }

  2. Pingback: Cryptography in .NET : Digital Signatures | Stephen Haunts { Coding in the Trenches }

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s