locked
How do I encrypt and Decrypt arbitrary data using the certificates that are stored within the Azure Management Service? RRS feed

  • Question

  • I'm asking this on the assumption that 

    1) The Management service is the most secure location for critical certificates
    2) The Public Key is not exportable from the local Azure Instance
    3) It's not efficient or possible to store all certificates (for the entire application) in  the management service

    References

    http://social.msdn.microsoft.com/Forums/en-US/windowsazuresecurity/thread/16ebfc68-4b92-45b3-b323-65d49b9cc15c

     

    Goal

     My goal is to Hash and Encrypt Azure Table Data (as explained here http://social.msdn.microsoft.com/Forums/en-US/windowsazuredata/thread/897cbb10-cbe7-431b-b750-51652deb89f5 ) and to encrypt and store Private Keys in Blob storage. 

    Question(s)

     Once I upload a Public Private key pair into Azure Management service, how do I use that to encrypt/decrypt data?  The code I have works with a bit[] and not the CSP itself.

    Lastly, will your recommended approach work in Azure Limited Trust mode?

     


    Tuesday, May 10, 2011 6:09 PM

Answers

All replies

  • You need to create an X509Certificate2 object by using the public + private key from the management service.  I believe it allows you to pass a byte[] parameter.  Then from that certificate you need to create an RSACryptoServiceProvider from the certificate (http://msdn.microsoft.com/en-us/library/system.security.cryptography.rsacryptoserviceprovider.aspx).

    The sample at the bottom of the MSDN page shows you how to do the actual crypto.  I believe it works in partial trust.


    Developer Security MVP | http://www.steveonsecurity.com
    Tuesday, May 10, 2011 6:47 PM
  • Thanks Steve... the reason I'm asking here is because the other forum post mentions that the private key is non exportable.  That would mean to me that I can't turn it into a Byte[]

     

    http://social.msdn.microsoft.com/Forums/en-US/windowsazuresecurity/thread/16ebfc68-4b92-45b3-b323-65d49b9cc15c

    Tuesday, May 10, 2011 6:54 PM
  • Ah, yes.  It would help if I read the linked post.

    Since you cannot read the private key from the management service, using it is not possible.  Regardless of how secure the management service store is, if you can't read the private key, you just can't use it. 

    Therefore you need to store the key in a seperate location such as blob storage.


    Developer Security MVP | http://www.steveonsecurity.com
    Tuesday, May 10, 2011 7:11 PM
  • My threat model requires me to protect private keys that reside unprotected on table/blob storage.  I can't use a private key stored on table/blob storage, since that's the vulnerable area I'm trying to protect.

    Anyone have a tip on using certificates (and corresponding private keys) that were uploaded into the management portal to encrypt or decrypt arbitrary data?

    Wednesday, May 11, 2011 1:20 AM
  • Hello Chris,

    You want to upload cercificates using management portal and use it to encrypt or decrypt arbitrary data.

    If you mean "Management Certificates" which is designed to be used for service management purpose only. It cannot be used for other purposes such as encoding arbitrary data.

    If you mean "Service Certificates" as long as the certificate is uploaded and configured properly in the Role, the certificate is installed in the relevant certificate store on the VM. Then you can use the same code like you do in on-premises environment to do encoding/decoding.

    Resources:

    Working with the Windows Azure Certificate Store
    http://msdn.microsoft.com/en-us/library/ee758713.aspx

    Managing Service Certificates
    http://msdn.microsoft.com/en-us/library/ff793095.aspx

    Please let us know if you need more assistance on this.

    Thanks,


    Wengchao Zeng
    Please mark the replies as answers if they help or unmark if not.
    If you have any feedback about my replies, please contact msdnmg@microsoft.com.
    Microsoft One Code Framework
    Wednesday, May 11, 2011 10:21 AM
  • Thanks for clearing up the terminology.  I want to use certs uploaded to the Service Certificates store.

     

    I'm not currently using the Windows/.NET API to use certs, I'm using System.IO.  Can you tell me what the right way to use Windows certificates in the cert store is? 

     

    Just an API name may do the trick.

    Wednesday, May 11, 2011 12:46 PM
  • You cannot access the private key from any of the stores within the management API.  As Wenchao said you would have to use the certificate store on the VM.  I have a collection of useful code snippets that can show you around how to read from the store: http://blogs.objectsharp.com/cs/blogs/steve/archive/2010/08/05/working-with-certificates-in-code.aspx


    Developer Security MVP | http://www.steveonsecurity.com
    Wednesday, May 11, 2011 2:16 PM
  •  

    OK now I suppose I can access the certificate using this API, can someone from MSFT finish the implementation (todo sections)?

     

    I'm assuming that a symmetric key needs to be created for entries that are larger than the key modulus.  Can someone finish up the implementation for secure Blob or Table storage?

     

     static void Main(string[] args)
        {
           string thumbprint;
      StoreName storeName = StoreName.My;
      StoreLocation storeLocation = StoreLocation.LocalMachine;
      X509Certificate2 theCert;
    
          theCert = null;
          X509Store store = new X509Store(storeName, storeLocation);
    
          try
          {
            store.Open(OpenFlags.ReadOnly);
    
            foreach (var cert in store.Certificates)
            {
              if (cert.HasPrivateKey)
              {
                // Todo: Create symmetric key
    
                // todo: Encrypt data using symmetric key
    
                // todo: decrypt data using symmetric key
    
                // todo: Encrypt symmetric key and store with blob or in separate table property
              }
            }
          }
          finally
          {
            store.Close();
          }
        }

    Wednesday, May 11, 2011 3:32 PM
  • I cannot help with the blob/table storage bit, but...

    Do not just loop through the certificates in the store.  There are going to be dozens of them.  You will need to look for a specific certificate to use.  Here is the basic encrypt/decrypt:

    static void Main(string[] args)
    {
    	string thumbprint = "5c e1 3c a6 65 17 6c f3 6e e6 91 4b 6e ad ae 08 8c e4 35 41".Replace(" ", "").ToUpper();
    	StoreName storeName = StoreName.My;
    	StoreLocation storeLocation = StoreLocation.LocalMachine;
    
    	X509Store store = new X509Store(storeName, storeLocation);
    
    	try
    	{
    		store.Open(OpenFlags.ReadOnly);
    
    		foreach (var cert in store.Certificates)
    		{
    			if (!cert.Thumbprint.Equals(thumbprint))
    				continue;
    
    			string toEncrypt = "some value to encrypt";
    
    			Console.WriteLine(toEncrypt);
    
    			string encryptedValue = Encrypt(toEncrypt, cert);
    
    			Console.WriteLine("Encrypted value: {0}", encryptedValue);
    
    			string decryptedValue = "";
    
    			if (cert.HasPrivateKey)
    				decryptedValue = Decrypt(encryptedValue, cert);
    
    			Console.WriteLine("Decrypted value: {0}", decryptedValue);
    		}
    	}
    	finally
    	{
    		store.Close();
    	}
    }
    
    private static string Decrypt(string encryptedValue, X509Certificate2 cert)
    {
    	using (RSACryptoServiceProvider RSA = (RSACryptoServiceProvider)cert.PrivateKey)
    	{
    		return Encoding.Unicode.GetString(RSA.Decrypt(Convert.FromBase64String(encryptedValue), false));
    	}
    }
    
    private static string Encrypt(string value, X509Certificate2 cert)
    {
    	UnicodeEncoding ByteConverter = new UnicodeEncoding();
    
    	byte[] dataToEncrypt = ByteConverter.GetBytes(value);
    
    	using (RSACryptoServiceProvider RSA = (RSACryptoServiceProvider)cert.PublicKey.Key)
    	{
    		return Convert.ToBase64String(RSA.Encrypt(dataToEncrypt, false));
    	}
    }
    
    

     


    Developer Security MVP | http://www.steveonsecurity.com
    Wednesday, May 11, 2011 4:21 PM
  • True, certificate selection should only use one cert (I was debugging and didn't replace that line).

    But there is an issue with your code if encryptedValue is larger than the cert modulus.  For example, try encrypting a large blob or stream... an exception should be thrown.

    I'd like to see how I should store and use a encrypted symmetric key in conjunction with the RSACryptoprovider. 

    I would think the implementation would go like this:

    1) A symmetric key is generated from Crypto Random, at a defined length using a KDF

    2) This key is used to encrypt decrypt data (streams, or properties)

    3) The symmetric key is encrypted using the public key of the RSACryptoProvider

    This would allow very quick access to the data since the RSACryptoProvider incurs a very high overhead (time) that has size limitations.

     

    Decryption would go like this

    1) Decrypt the symmetric key using the RSA Private key

    2) Decrypt the table fields or blob data as needed

     

    Can anyone verify this logic, and/or assist in secure robust implementation? 





    • Edited by ChrisLaMont Wednesday, May 11, 2011 4:44 PM
    Wednesday, May 11, 2011 4:28 PM
  • ... as a followup to my question should a KDF be used to generate the symmetric key?  How should the KDF be stored?

     

    http://blogs.msdn.com/b/shawnfa/archive/2004/04/14/113514.aspx

    Wednesday, May 11, 2011 4:33 PM
  • Ahh, that makes sense with the arbitrary bit.  So branching off my above code, I think it would work something like:

     - Generate Cryptographically random symmetric key

     - Do symmetric encryption of arbitrary data with random key

     - Encrypt symmetric key with assymetric public key

     - Save arbitrary data + encrypted key to blob/table storage

    It should look something like this (I flubbed the last little decrypt piece because I didn't feel like splitting the stored string value):

    static void Main(string[] args)
    {
    	string thumbprint = "5c e1 fb a6 65 9c 6c f3 6e e6 9f 4b 6e ad ae 08 8c e4 35 41".Replace(" ", "").ToUpper();
    	StoreName storeName = StoreName.My;
    	StoreLocation storeLocation = StoreLocation.CurrentUser;
    	X509Certificate2 theCert = null;
    	X509Store store = new X509Store(storeName, storeLocation);
    
    	byte[] symmKey = CreateCryptograhicKey(32);
    	byte[] iv = CreateCryptograhicKey(16);
    
    	try
    	{
    		store.Open(OpenFlags.ReadOnly);
    
    		foreach (var cert in store.Certificates)
    		{
    			if (cert.Thumbprint.Equals(thumbprint))
    			{
    				theCert = cert;
    				break;
    			}
    		}
    	}
    	finally
    	{
    		store.Close();
    	}
    
    	if (theCert == null)
    		return;
    
    	string toEncrypt = "some arbitrary length data";
    
    	Console.WriteLine(toEncrypt);
    
    	string symmEncrypted = SymmetricEncrypt(toEncrypt, iv, symmKey);
    
    	string asymEncryptedKey = Encrypt(Convert.ToBase64String(symmKey), theCert);
    
    	string toStoreInBlobOrTable = string.Format("{0}|{1}|{2}", Convert.ToBase64String(iv), asymEncryptedKey, symmEncrypted);
    	Console.WriteLine("Put in storage: {0}", toStoreInBlobOrTable);
    
    	byte[] decryptedKey = Encoding.Unicode.GetBytes(Decrypt(asymEncryptedKey, theCert));
    
    	string decryptedValue = SymmetricDecrypt(symmEncrypted, iv, symmKey);
    
    	Console.WriteLine(decryptedValue);
    }
    
    public static string SymmetricEncrypt(string plainText, byte[] initVector, byte[] keyBytes)
    {
    	using (RijndaelManaged symmetricKey = new RijndaelManaged())
    	{
    		symmetricKey.Mode = CipherMode.CBC;
    
    		using (ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, initVector))
    		{
    			using (MemoryStream memoryStream = new MemoryStream())
    			using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
    			{
    				byte[] plainTextBytes = Encoding.Unicode.GetBytes(plainText);
    
    				cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
    
    				cryptoStream.FlushFinalBlock();
    
    				byte[] cipherTextBytes = memoryStream.ToArray();
    
    				return Convert.ToBase64String(cipherTextBytes);
    			}
    		}
    	}
    }
    
    public static string SymmetricDecrypt(string base64Encoded, byte[] initVector, byte[] keyBytes)
    {
    	using (RijndaelManaged symmetricKey = new RijndaelManaged())
    	{
    		symmetricKey.Mode = CipherMode.CBC;
    
    		using (ICryptoTransform decryptor = symmetricKey.CreateDecryptor(keyBytes, initVector))
    		{
    			using (MemoryStream memoryStream = new MemoryStream())
    			using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Write))
    			{
    				byte[] encryptedBytes = Convert.FromBase64String(base64Encoded);
    
    				cryptoStream.Write(encryptedBytes, 0, encryptedBytes.Length);
    
    				cryptoStream.FlushFinalBlock();
    
    				byte[] cipherTextBytes = memoryStream.ToArray();
    
    				return Convert.ToBase64String(cipherTextBytes);
    			}
    		}
    	}
    }
    
    private static byte[] CreateCryptograhicKey(int length)
    {
    	using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
    	{
    		byte[] key = new byte[length];
    
    		crypto.GetBytes(key);
    
    		return key;
    	}
    }
    
    private static string Decrypt(string encryptedValue, X509Certificate2 cert)
    {
    	using (RSACryptoServiceProvider RSA = (RSACryptoServiceProvider)cert.PrivateKey)
    	{
    		return Encoding.Unicode.GetString(RSA.Decrypt(Convert.FromBase64String(encryptedValue), false));
    	}
    }
    
    private static string Encrypt(string value, X509Certificate2 cert)
    {
    	UnicodeEncoding ByteConverter = new UnicodeEncoding();
    
    	byte[] dataToEncrypt = ByteConverter.GetBytes(value);
    
    	using (RSACryptoServiceProvider RSA = (RSACryptoServiceProvider)cert.PublicKey.Key)
    	{
    		return Convert.ToBase64String(RSA.Encrypt(dataToEncrypt, false));
    	}
    }
    
    


    Developer Security MVP | http://www.steveonsecurity.com
    Wednesday, May 11, 2011 5:14 PM
  • Looks good for getting started...

     

    Can you tell me what makes sense for values for iv and SymmKey?  Are the values you provided long enough?  What is the max/min they should be?

     

    Do you mind posting this on Security.StackExchange for complete vetting by the community?  I don't mind making minor alterations and cleaning it up then posting it.  I'm just looking for the most eyeballs...  but it is *your* code...



    Wednesday, May 11, 2011 5:28 PM
  • RijndaelManaged supports key lengths of 128, 160, 192, 224, 256 bits, and the recommended block size is 128 bits.  Here are the reasons behind the numbers: http://blogs.msdn.com/b/shawnfa/archive/2006/10/09/the-differences-between-rijndael-and-aes.aspx.

    The values I provided are:

    Symmetric key: 32 bytes (32 bytes * 8 bits in a byte == 256 bits)

    IV: 16 bytes (16 bytes * 8 bits in a byte == 128 bits) == 128 bit block size.

    Are they long enough?  According to MSDN, yes.

    You are welcome to post this code to Security.StackExchange for vetting.  It's relatively boiler plate coding, so I don't mind if you post it.  With that being said, there are 2 things that come to mind that are a problem:

    • Hardcoded lengths
    • Hardcoded encryption algorithm (Rijndael/AES)
    • EDIT: I think the cryptorandom object and constructor should be outside the scope of the method

    Developer Security MVP | http://www.steveonsecurity.com
    • Marked as answer by ChrisLaMont Wednesday, May 11, 2011 9:44 PM
    • Unmarked as answer by ChrisLaMont Thursday, May 12, 2011 5:03 AM
    Wednesday, May 11, 2011 5:43 PM
  • Found a few bugs regarding encoding in this thread.  I tested, and corrected them in the links below:

    Here is the Security.StackOverflow peer review.
    http://security.stackexchange.com/questions/3741/peer-review-of-c-cryptography-for-azure-table-security

     

    Here is the working version of the project on Codeplex.
    http://azuretableencrypt.codeplex.com/SourceControl/list/changesets

     

    I'm interested in hearing feedback and ways to use this to transparently hook into the StorageClient.



    • Marked as answer by ChrisLaMont Wednesday, May 11, 2011 9:44 PM
    • Unmarked as answer by ChrisLaMont Thursday, May 12, 2011 5:03 AM
    • Marked as answer by ChrisLaMont Thursday, May 12, 2011 1:42 PM
    Wednesday, May 11, 2011 9:43 PM