Asymmetric encryption in C# using RSA

Feel free to re-use this code for asymmetric encryption/decryption of large data.

    /// <summary>
    /// Asymmetric key pair container.
    /// </summary>
    public class AsymmetricKeyPair
    {
        private readonly string _publicKey;
        private readonly string _privateKey;

        internal AsymmetricKeyPair(string publicKey, string privateKey)
        {
            _publicKey = publicKey;
            _privateKey = privateKey;
        }

        /// <summary>
        /// Asymmetric private RSA key.
        /// </summary>
        public string PrivateKey
        {
            get { return _privateKey; }
        }

        /// <summary>
        /// Asymmetric public RSA key.
        /// </summary>
        public string PublicKey
        {
            get { return _publicKey; }
        }
    }
    /// <summary>
    /// Asymmetric encryption class that provides:
    /// <ul>
    /// <li>Generation of Asymmetric encryption/decryption keys using RSA.</li>
    /// <li>Encryption of text using asymmetric public key.</li>
    /// <li>Decryption of text using asymmetric private key.</li>
    /// </ul>
    /// </summary>
    public static class AsymmetricEncryption
    {
        #region "Private members"
        private static bool _optimalAsymmetricEncryptionPadding = false;
        #endregion

        #region "Public members"
        public static bool OptimalAsymmetricEncryptionPadding
        {
            get { return _optimalAsymmetricEncryptionPadding; }
            set { _optimalAsymmetricEncryptionPadding = value; }
        }
        #endregion

        #region "Public methods"
        /// <summary>
        /// Generates a new pair of asymmetric encryption/decryption keys
        /// </summary>
        /// <param name="keySize">The size of the key to be used in bits. Recommended 1024.</param>
        /// <returns>Returns <see cref="AsymmetricKeyPair"/></returns>
        /// <exception cref="ArgumentException">Thrown in the following situations
        /// <ul>
        /// <li>An invalid key size is provided.</li>
        /// </ul>
        /// </exception>
        public static AsymmetricKeyPair GenerateKeys(int keySize)
        {
            if (!IsKeySizeValid(keySize)) throw new ArgumentException("The provided key size is invalid.", "keySize");
            using (var provider = new RSACryptoServiceProvider(keySize))
            {
                return new AsymmetricKeyPair(provider.ToXmlString(false), provider.ToXmlString(true));
            }
        }

        /// <summary>
        /// Encrypts the provided text using the public key.
        /// </summary>
        /// <param name="text">Text to be encrypted.</param>
        /// <param name="publicKeyXml">XML string containing the public RSA key.</param>
        /// <param name="keySize">Size of the key used when generating the key pair. Default 1024 bits.</param>
        /// <returns>Encrypted text.</returns>
        /// <exception cref="ArgumentException">Thrown in the following situations
        /// <ul>
        /// <li>The provided text is empty or null.</li>
        /// <li>Length of the provided text exceeds the maximum string length that can be encrypted for the specified key size.</li>
        /// <li>An invalid key size is provided.</li>
        /// <li>The provided key is null or empty.</li>
        /// </ul>
        /// </exception>
        /// <exception cref="CryptographicException">Thrown when the format of the provided key is invalid.</exception>
        public static string EncryptText(string text, string publicKeyXml, int keySize)
        {
            int byteKeySize = keySize / 8;
            byte[] bytes = Encoding.UTF32.GetBytes(text);
            int maxLength = byteKeySize - 42;
            int dataLength = bytes.Length;
            int iterations = dataLength / maxLength;
            StringBuilder stringBuilder = new StringBuilder();

            if (!IsKeySizeValid(keySize)) throw new ArgumentException("The provided key size is invalid.", "keySize");
            if (String.IsNullOrEmpty(publicKeyXml)) throw new ArgumentException("The provided key is null or empty", "publicKeyXml");

            RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider(keySize);
            rsaCryptoServiceProvider.FromXmlString(publicKeyXml);

            for (int counter = 0; counter <= iterations; counter++)
            {
                byte[] tempBytes = new byte[(dataLength - maxLength * counter > maxLength) ? maxLength : dataLength - maxLength * counter];
                Buffer.BlockCopy(bytes, maxLength * counter, tempBytes, 0, tempBytes.Length);
                byte[] encryptedBytes = rsaCryptoServiceProvider.Encrypt(tempBytes, true);
                Array.Reverse(encryptedBytes);
                stringBuilder.Append(Convert.ToBase64String(encryptedBytes));
            }
            return stringBuilder.ToString();

        }

        /// <summary>
        /// Decrypts the provided text using the public and private RSA key.
        /// </summary>
        /// <param name="text">Text to be decrypted.</param>
        /// <param name="publicAndPrivateKeyXml">XML string containing the public and private RSA key.</param>
        /// <param name="keySize">Size of the key used when generating the key pair. Default 1024 bits.</param>
        /// <returns>Decrypted text.</returns>
        /// <exception cref="ArgumentException">Thrown in the following situations
        /// <ul>
        /// <li>The provided text is empty or null.</li>
        /// <li>An invalid key size is provided.</li>
        /// <li>The provided key is null or empty.</li>
        /// </ul>
        /// </exception>
        /// <exception cref="CryptographicException">Thrown when the format of the provided key is invalid.</exception>
        public static string DecryptText(string text, string publicAndPrivateKeyXml, int keySize)
        {

            RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider(keySize);
            rsaCryptoServiceProvider.FromXmlString(publicAndPrivateKeyXml);

            int base64BlockSize = ((keySize / 8) % 3 != 0) ? (((keySize / 8) / 3) * 4) + 4 : ((keySize / 8) / 3) * 4;
            int iterations = text.Length / base64BlockSize;
            var arrayList = new ArrayList();

            for (int i = 0; i < iterations; i++)
            {
                byte[] encryptedBytes = Convert.FromBase64String(text.Substring(base64BlockSize * i, base64BlockSize));
                Array.Reverse(encryptedBytes);
                arrayList.AddRange(rsaCryptoServiceProvider.Decrypt(encryptedBytes, true));
            }
            return Encoding.UTF32.GetString(arrayList.ToArray(Type.GetType("System.Byte")) as byte[]);

        }

        #endregion

        #region "Private methods"
        private static bool IsKeySizeValid(int keySize)
        {
            return keySize >= 384 &&
                   keySize <= 16384 &&
                   keySize % 8 == 0;
        }

        private static int GetMaxDataLength(int keySize)
        {
            //Uncomment the following if we enable toggling of the OAEP option
            if (OptimalAsymmetricEncryptionPadding)
            {
                return ((keySize - 384) / 8) + 7;
            }
            return ((keySize - 384) / 8) + 37;
        }
        #endregion
    }

You can download the nuget package here.

Advertisements

Azure may not be Azure anymore

After the decision to retire the “Live” brand, Microsoft seems to be taking the next step to rename the “Azure” brand. In a recent mail to customers, Microsoft has suggested the new naming. While doing this, I hope Microsoft comes up with names that will qualify the services as cloud-based services. At least for the next few years, we will see the co-existence of cloud-based and on-premise solutions. And we need a set of terms that clearly qualify solution components as either residing on-premise or on the cloud. Hope that gets taken care of…My 2c

Prior Service Name New Service Name
Windows Azure Compute Cloud Services
Windows Azure Platform – All Services All Services
Windows Azure CDN CDN
Windows Azure Storage Storage
Windows Azure Traffic Manager Traffic Manager
Windows Azure Virtual Network Virtual Network
AppFabric Cache Cache
AppFabric Service Bus Service Bus
AppFabric Access Control Access Control
SQL Azure SQL Database
SQL Azure Reporting Service SQL Reporting

Could not connect to net.tcp://xxxxxxx.servicebus.windows.net:9354

While learning to use the ServiceBus, I wrote a small application that sends messages to a queue and retrieves based on filters. Initially the application was working fine. But when I wanted to demo it to my collegue, it just wouldn’t send or receive messages. I constantly saw the exception


Could not connect to net.tcp://XXXXX.servicebus.windows.net:9354/. The connection attempt lasted for a time span of 00:00:21.0254249. TCP error code 10060: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond 65.52.0.98:9354.



After some investigation, I figured out that the issue was due to change in environment. When I had initially developed the application, I was working on a network which had no restrictions. But my demo was on my office network that had a firewall with ports locked down. To communicate with the ServiceBus, the SDK uses tcp port 9354.


You can open this port and make the application work. Or if you are going to run your application in an environment where IT will not open ports for your application, you can communicate with the ServiceBus using HTTP.


To communicate using HTTP, before you perform any operations, set the connectivity mode to http.


Microsoft.ServiceBus.ServiceBusEnvironment.SystemConnectivity.Mode = ConnectivityMode.Http;


This mode setting applies to all endpoints being used by your application.


Using Http mode has a performance downside compared to Tcp. I will update this post soon with some performance numbers comparing the 2 modes.