Monday 23 April 2018

.Net Core 2.0 Public Key Infrastructure

You can find many blog posts and tutorials about how to perform cryptography functions using .Net and Bouncy castle. But it is hard to find information about performing fully featured crypto implementations with .Net core 2.0 as of this day of writing. This blog post will introduce you to the .Net core 2.0 based encryption and signing process. 

When it comes to encryption, we can identify two main variations as Symmetric Key encryption and Asymmetric Key encryption. The Symmetric key encryption is straight forward and  easy to implement because it is using the same key to perform encryption and decryption. In Symmetric key encryption, we always need to find a proper way to share the secret shared key securely. We are focusing on Asymmetric key encryption in this blog post. 

Encryption using Public and Private Key pairs

The first exercise we are going to do is performing encryption. Bob will encrypt a message using Alice's public key and sends the message to Alice. Then Alice can decrypt the message using her private key. 
In order to perform this Alice needs to create a key pair and distribute her public key securely to Bob and persist her private key securely. 

First of all we need to create this key generation application. For ease of demonstration I will create console application to perform this operation. Please create a .Net core 2.0 console application.

You need to add required nuget packages as follows. 

Install-Package Portable.BouncyCastle -Version 1.8.1.3

We are going to use Portable Bouncy castle by onovotny. A big thank goes to him. 

The fist method I'm implementing is GenerateKeys. This method generates a key pair using given key size and secure random generator build-in in the Bouncy castle package. The method returns AsymmetricCipherKeyPair. We are using RSA asymmetric encryption algorithm to perform this key generation. 

private static AsymmetricCipherKeyPair GenerateKeys(int keySizeInBits)
        {
            var r = new RsaKeyPairGenerator();
            CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
            r.Init(new KeyGenerationParameters(new SecureRandom(randomGenerator), keySizeInBits));
            var keys = r.GenerateKeyPair();
            return keys;
        }

In the Main method, Im gonna consume this GenerateKey method and extract private and public keys separately. Please note that in order to display  the keys I'm converting them to base64 encoded strings. But in a real application you should convert keys into strings. Especially the private keys. Because strings are immutable in the memory and any memory attack can get hold in to our pvt keys easily. it is always better to keep them as byte arrays in the memory when ever we are using them. 


var keys = GenerateKeys(2048);

   PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keys.Private);
   byte[] serializedPrivateBytes = privateKeyInfo.ToAsn1Object().GetDerEncoded();

   SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keys.Public);
   byte[] serializedPublicBytes = publicKeyInfo.ToAsn1Object().GetDerEncoded();

   Console.WriteLine("Pvt Key :" + Convert.ToBase64String(serializedPrivateBytes));
   Console.WriteLine("Pub Key :" + Convert.ToBase64String(serializedPublicBytes));

   Console.Read();

 The application output should display as follows. 




We can save these two values to test encryption and decryption process. 

Now we need to pass the public key securely to Bob. 

Encryption using Alice's Public key 

Now Bob has Alice's public key. So the next step is encrypt a secret message in order send to Alice. I'm going to create a new .Net core console application to perform this as follows. The Encrypt method accepts the public key and the message that needs to encrypt.

public static  string Encrypt(byte[] pubKey, string txtToEncrypt)
        {
            RsaKeyParameters publicKey = (RsaKeyParameters)PublicKeyFactory.CreateKey(pubKey);

            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            RSAParameters rsaParameters = new RSAParameters();
            rsaParameters.Modulus = publicKey.Modulus.ToByteArrayUnsigned();
            rsaParameters.Exponent = publicKey.Exponent.ToByteArrayUnsigned();
            rsa.ImportParameters(rsaParameters);

            byte[] bytes = Encoding.UTF8.GetBytes(txtToEncrypt);
            byte[] enc = rsa.Encrypt(bytes, false);
            string base64Enc = Convert.ToBase64String(enc);

            return base64Enc;
        }

The most important thing in this method is importing the Modulus and Exponent of the key as RSA parameters. 

Following is the output of the program when we pass in the pub key and the message to encrypt as console params.

Decryption using Alice's Private key 
Now Alice will receive this message and she needs to decrypt this using her private key. Now Alice needs to use her persisted pvt key. I have created a separate application for the decryption process as well ( Most of the crypto examples you can see in the Internet are using only one application to demonstrate this key generation , encryption and decryption process. . But you will never see such applications in real life).

The Decryption process depicts in the following method where it accepts the private key and the encrypted message.

public string Decrypt(string pvtKey, string txtToDecrypt)
        {
            RsaPrivateCrtKeyParameters privateKey = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(pvtKey));
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();

            RSAParameters rsaParameters2 = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)privateKey);

            rsa.ImportParameters(rsaParameters2);

            byte[] dec = rsa.Decrypt(Convert.FromBase64String(txtToDecrypt), false);
            string decStr = Encoding.UTF8.GetString(dec);

            return decStr;
        }

Note that we are using slightly different method to load the private key parameters compared how we have load the public key parameters. 


You can see in this picture that the message being decrypted successfully. 

Message Signing 

Message signing is another application in the PKI domain. A signed message ensures the integrity of the message and we can verify the sender as well.


The message signing process uses Alice's private key to sign the message. In this example we are using the SHA256 algorithm as the signing algorithm along with the private key. 

Again, I have created a different application to perform this signing operation. The Sign method depicted below would perform the signing process.

static string Sign(byte[] pvtKey, string msgToSign)
        {
            RsaPrivateCrtKeyParameters privateKey = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(pvtKey);
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();

            RSAParameters rsaParameters = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)privateKey);

            rsa.ImportParameters(rsaParameters);
            byte[] dataBytes = Encoding.UTF8.GetBytes(msgToSign);
            byte[] signedBytes = rsa.SignData(dataBytes, "SHA256");

            return Convert.ToBase64String(signedBytes);

        }

Following is the output of the Signature program for a given text. 


Message Signature Verification 

So once Bob get the message along with its signature , he needs to verify it using Alice's public key. 

A different program being developed for this verification process. The Verify method verifies the signature along with the original message.

public static  bool Verify(byte[] pubKey, string originalMessage, string signedMessage)
        {


            RsaKeyParameters publicKey = (RsaKeyParameters)PublicKeyFactory.CreateKey(pubKey);

            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            RSAParameters rsaParameters = new RSAParameters();
            rsaParameters.Modulus = publicKey.Modulus.ToByteArrayUnsigned();
            rsaParameters.Exponent = publicKey.Exponent.ToByteArrayUnsigned();
            rsa.ImportParameters(rsaParameters);

            byte[] bytesToVerify = Convert.FromBase64String(originalMessage);
            byte[] signedBytes = Convert.FromBase64String(signedMessage);


            bool success = rsa.VerifyData(bytesToVerify, CryptoConfig.MapNameToOID("SHA256"), signedBytes);

            return success;
        }

You can find all source code in a one solution in following git repo. 

Use AWS S3 as a secure storage for secure .Net Core app data


When it comes to encryption, key storage becomes a major problem for developers. Most of the developers hard cording keys into the source codes or add them in the config file. Both these methods are in-secure in the context of application security. 

One of the best ways to secure your secrets are using a HSM ( Hardware Security Module). These devices are comparatively expensive and need  regular maintenance. Is there any cheaper method to do this ? especially when it comes to cloud hosting. 

The answer is Yes. If you are going for an Azure deployment , there is Azure Vault. Azure vault manages all your keys securely and most of the security features can be encapsulated from the application. 

But today, I am going to explain how we can use the AWS S3 bucket as a secure storage for your keys using the AWS Key Management service in .Net core 2.0 web application. 

To begin with, add following nuget packages which facilitates the AWS SDK functionality. 

AWS SDK Core : Install-Package AWSSDK.Core

AWS SDK Extensions for .Net Core : Install-Package AWSSDK.Extensions.NETCore.Setup -Version 3.3.4

AWS SDK Key management service :  Install-Package AWSSDK.KeyManagementService -Version 3.3.5

AWS SDK S3 : Install-Package AWSSDK.S3 -Version 3.3.17

Next step is to create key pair for a AWS user using the AWS IAM console. Think link provides a comprehensive guide. Make sure you save the generated AWS access keys at the end of the process and keep them securely. You need to grant admin privileges for S3 bucket for this newly created user. 

Create a S3 bucket using the AWS console. 

The next thing you need to do is, add the config entry to the appsettings.json to configure the AWS settings. Following is the entry you need to add

"AWS": {
    "Profile": "default",
    "Region": "ap-south-1",
    "ProfilesLocation": "/config/awsprofile"
  }

You need to persist your user's keys in the awsprofile file which is mentioned in the ProfilesLocation attribute. Make sure that you are keep this file in a secured place in your drive and not the app folder itself. ( This file persist your AWS access keys in plain text format which is not secured. AWS provides best practices how to keep your AWS key file securely in following link

ProfilesLcoation file should be like this 

[aunex-aws-profile]
aws_access_key_id=<your access key ID>
aws_secret_access_key=<Your secret access key>

As the next step, you need to load AWSOptions into your application. In windows you do not need to do anything than call the following code segment.

Configuration.GetAWSOptions();

Since this method not working in Linux, I had to manually load the AWS params into application's Environment variables as follows. 

I added the keys in two lines in my key file.

private void LoadAwsConfig(string fileLocation, string region)
        {
            try
            {
                string line;
                int counter = 0;
                System.IO.StreamReader file =
                    new System.IO.StreamReader(fileLocation);
                while ((line = file.ReadLine()) != null)
                {
                    if (counter == 0)
                        Environment.SetEnvironmentVariable("AWS_ACCESS_KEY_ID", line);
                    if (counter == 1)
                        Environment.SetEnvironmentVariable("AWS_SECRET_ACCESS_KEY", line);
                    counter++;
                }
                Environment.SetEnvironmentVariable("AWS_REGION", region);
                file.Close();
            }
            catch{}
        }

Then Add following interface and class which implements date securing and retrieval  functionality. You need to inject this into .net core default IoC container. 

The Interface
public interface ISecureEnclave
    {
        Task<string> ReadValue(string key);

        Task<bool> WriteValue(string key, string value);
    }

The Implementation ( Write secured data into AWS S3) 
public async Task<bool> WriteValue(string key, string value)
        {
            using (var kmsClient = new AmazonKeyManagementServiceClient())
            {
                var response = await kmsClient.CreateKeyAsync(new CreateKeyRequest());
                string kmsKeyID = response.KeyMetadata.KeyId;

                var keyMetadata = response.KeyMetadata; 
                var bucketName = "<your s3 bucket name>";
                var objectKey = key;

                var kmsEncryptionMaterials = new EncryptionMaterials(kmsKeyID);
                var config = new AmazonS3CryptoConfiguration()
                {
                    StorageMode = CryptoStorageMode.ObjectMetadata
                };

                using (var s3Client = new AmazonS3EncryptionClient(config, kmsEncryptionMaterials))
                {
                    var putRequest = new PutObjectRequest
                    {
                        BucketName = bucketName,
                        Key = objectKey,
                        ContentBody = value
                    };
                    var obj = await s3Client.PutObjectAsync(putRequest);
                    if (obj.HttpStatusCode == HttpStatusCode.OK)
                        return true;

                }
            }
            return false;

The Implementation ( Read secured data from AWS S3 based on given key)

public async Task<string> ReadValue(string key)
        {
            using (var kmsClient = new AmazonKeyManagementServiceClient())
            {
                string kmsKeyID = null;
                var response = await kmsClient.CreateKeyAsync(new CreateKeyRequest());
                 kmsKeyID = response.KeyMetadata.KeyId;

                var keyMetadata = response.KeyMetadata; 
                var bucketName = "<your s3 bucket name>";
                var objectKey = key;

                var kmsEncryptionMaterials = new EncryptionMaterials(key);
                var config = new AmazonS3CryptoConfiguration()
                {
                    StorageMode = CryptoStorageMode.ObjectMetadata
                };

                using (var s3Client = new AmazonS3EncryptionClient(config, kmsEncryptionMaterials))
                {
                    var getRequest = new GetObjectRequest
                    {
                        BucketName = bucketName,
                        Key = objectKey
                    };

                    using (var getResponse = await s3Client.GetObjectAsync(getRequest))
                    using (var stream = getResponse.ResponseStream)
                    using (var reader = new StreamReader(stream))
                    {
                        return reader.ReadToEnd();
                    }
                }
            }
        }

Now you can use this implemented functionality in your web application using the dependency that injected in the startup class.
private ISecureEnclave _secureEnclave;

        public HomeController( ISecureEnclave secureEnclave)
        {
            _secureEnclave = secureEnclave;
        }
        
        public IActionResult Index()
        {
            _secureEnclave.WriteValue("MyKey123", "This is the data to secure");
            return View();
        }

You can find the git repo in this location

Stay tuned for another secure programming post :)