Basic two-way encryption in .NET

There are lots cases when we need to encrypt and then decrypt data using .NET. A great example is if we want to store something secure in a cookie. We write the encrypted data to the web-response and decrypt it again again later from the next web-request.

I’ve been using the following algorithm for two-way encryption for a number of years now. I found it on the web a long time ago, so unfortunately don’t have a link to the original source any more. If anyone can help me here that would be appreciated.

First, let’s start with an interface as the chances are we’re going to need to inject encryption capabilities into other objects using dependency injection:

namespace Encryption
{
    public interface IEncryptor
    {
        string Encrypt(string text);
        string Decrypt(string encryptedText);
    }
}

The implementation for this is as follows:

using System;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.IO;
using System.Globalization;
using System.Runtime.InteropServices;

namespace Encryption
{
    public class Encryptor : IEncryptor
    {
        private readonly SecureString _password;

        public Encryptor(SecureString password)
        {
            _password = password;
        }

        public string Encrypt(string text)
        {
            return Encrypt(text, GetDefaultSalt());
        }

        public string Encrypt(string text, string salt)
        {
            if (text == null)
            {
                return null;
            }

            RijndaelManaged rijndaelCipher;
            byte[] textData;
            ICryptoTransform encryptor;

            using (rijndaelCipher = new RijndaelManaged())
            {
                var secretKey = GetSecretKey(salt);

                // First we need to turn the input strings into a byte array.
                textData = Encoding.Unicode.GetBytes(text);

                // Create a encryptor from the existing secretKey bytes.
                // We use 32 bytes for the secret key. The default Rijndael 
                // key length is 256 bit (32 bytes) and then 16 bytes for the 
                // Initialization Vector (IV). The default Rijndael IV length is 
                // 128 bit (16 bytes).
                encryptor = rijndaelCipher.CreateEncryptor(secretKey.GetBytes(32), secretKey.GetBytes(16));
            }

            MemoryStream memoryStream;
            byte[] encryptedData;

            // Create a MemoryStream that is going to hold the encrypted bytes:
            using (memoryStream = new MemoryStream())
            {
                // Create a CryptoStream through which we are going to be processing 
                // our data. CryptoStreamMode.Write means that we are going to be 
                // writing data to the stream and the output will be written in the 
                // MemoryStream we have provided.
                using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                {
                    // Start the encryption process.
                    cryptoStream.Write(textData, 0, textData.Length);

                    // Finish encrypting.
                    cryptoStream.FlushFinalBlock();

                    // Convert our encrypted data from a memoryStream into a byte array.
                    encryptedData = memoryStream.ToArray();

                    // Close both streams.
                    memoryStream.Close();
                    cryptoStream.Close();
                }
            }

            // Convert encrypted data into a base64-encoded string.
            // A common mistake would be to use an Encoding class for that.
            // It does not work, because not all byte values can be
            // represented by characters. We are going to be using Base64 encoding.
            // That is designed exactly for what we are trying to do.
            var encryptedText = Convert.ToBase64String(encryptedData);

            // Return encrypted string.
            return encryptedText;
        }

        public string Decrypt(string encryptedText)
        {
            return Decrypt(encryptedText, GetDefaultSalt());
        }

        public string Decrypt(string encryptedText, string salt)
        {
            if (encryptedText == null)
            {
                return null;
            }

            RijndaelManaged rijndaelCipher;
            byte[] encryptedData;
            ICryptoTransform decryptor;

            using (rijndaelCipher = new RijndaelManaged())
            {
                var secretKey = GetSecretKey(salt);

                // First we need to turn the input strings into a byte array.
                encryptedData = Convert.FromBase64String(encryptedText);

                // Create a decryptor from the existing SecretKey bytes.
                decryptor = rijndaelCipher.CreateDecryptor(secretKey.GetBytes(32), secretKey.GetBytes(16));
            }

            MemoryStream memoryStream;
            byte[] unencryptedData;
            int decryptedDataLength;

            using (memoryStream = new MemoryStream(encryptedData))
            {
                // Create a CryptoStream. Always use Read mode for decryption.
                using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                {
                    // Since at this point we don't know what the size of decrypted data
                    // will be, allocate the buffer long enough to hold EncryptedData;
                    // DecryptedData is never longer than EncryptedData.
                    unencryptedData = new byte[encryptedData.Length];

                    // Start decrypting.
                    try
                    {
                        decryptedDataLength = cryptoStream.Read(unencryptedData, 0, unencryptedData.Length);
                    }
                    catch
                    {
                        throw new CryptographicException("Unable to decrypt string");
                    }                    

                    cryptoStream.Close();
                    memoryStream.Close();
                }
            }

            // Convert decrypted data into a string.
            var decryptedText = Encoding.Unicode.GetString(unencryptedData, 0, decryptedDataLength);

            // Return decrypted string.  
            return decryptedText;
        }

        private PasswordDeriveBytes GetSecretKey(string salt)
        {
            // We are using salt to make it harder to guess our key
            // using a dictionary attack.
            var encodedSalt = Encoding.ASCII.GetBytes(salt);

            var valuePointer = IntPtr.Zero;
            try
            {
                // The Secret Key will be generated from the specified
                // password and salt.
                valuePointer = Marshal.SecureStringToGlobalAllocUnicode(_password);
                return new PasswordDeriveBytes(Marshal.PtrToStringUni(valuePointer), encodedSalt);
            }
            finally
            {
                Marshal.ZeroFreeGlobalAllocUnicode(valuePointer);
            }            
        }

        private string GetDefaultSalt()
        {
            return _password.Length.ToString(CultureInfo.InvariantCulture);
        }
    }
}

This can then be used as follows:

using System;
using System.Security;

namespace Encryption
{
    public static class Program
    {
        public static void Main()
        {
            using (var password = GetPassword())
            {
                var encryptor = new Encryptor(password);

                const string text = "Hello World!";

                var encryptedText = encryptor.Encrypt(text);
                var decryptedText = encryptor.Decrypt(encryptedText);

                Console.WriteLine(encryptedText);
                Console.WriteLine(decryptedText);

                Console.ReadLine();
            }
        }

        private static SecureString GetPassword()
        {
            var password = new SecureString();

            password.AppendChar((char)80); // P
            password.AppendChar((char)97); // a
            password.AppendChar((char)115); // s
            password.AppendChar((char)115); // s
            password.AppendChar((char)119); // w
            password.AppendChar((char)111); // o
            password.AppendChar((char)114); // r
            password.AppendChar((char)100); // d
            password.AppendChar((char)49); // 1

            return password;
        }
    }
}

Note that we store the password using the SecureString class. This makes it harder to read the string from the memory while the program is running in the event that the host machine is hacked. Also, as I’m super-paranoid, I write to the SecureString using ASCII values converted to chars to prevent any readable values from appearing in string tables in the compiled code.

When using the Encryptor class you also have the option to add “salt”. If no salt is supplied a default based on the length of the password is used. Salt is effectively an additional piece of information you must supply in order to decrypt information, which becomes part of the decryption password. It is recommended that you use salt wherever possible as it makes it much harder for a hacker to decrypt sets of data using brute-force attacks.

Happy encrypting!

Are your SSL certificates secure?

SSL encryption is vital when sending sensitive information over the internet from browser to server, but just how secure are your sites? I used to naively believe that all I had to do was whack a certificate on the web-server and all would be well. However, a friend of mine recently had one of their web-sites audited (I won’t name the site for obvious reasons!) and found more holes in their security defenses than a block of Swiss cheese.

As well as the certificate, here are some other things you need to take care of:

  • Protocols (SSL, TLS, PCT and their various version numbers)
  • Ciphers (DES, AES, etc…)
  • Hashes (SHA x, MD5, etc…)
  • Which key exchanges are enabled
  • The order that SSL ciphers are used

Fortunately, help is at hand using these two great tools:

You can use the Qualys SSL Labs SSL Server Test to find out how secure your site is. Just enter your URL in the box and they’ll give you a full report on how secure the SSL for your site is.

If there are issues the IIS Crypto tool from Nartac Software will help you make the right remedial registry changes without having to tinker around in their yourself, thus vastly reducing the possibility you’ll trash your server.

Clever stuff!

Granting folder/file access permissions to IIS AppPool users

By default, processes than run under IIS won’t have permission to access folders you create on your server hard drive. So, if you want your web-sites to interact with folders and files you’ll need add access permissions using the standard folder properties dialog:

Security Permissions Dialog

Permissions must be granted to the AppPool that your site uses. To grant permissions to AppPool users, the user name you enter in the Select Users or Groups dialog:

Select User or Group Dialog

…should match the following pattern:

IIS AppPool\[AppPool Name]

When you click Check Names the text you entered will magically detect the AppPool.

Prevent malicious image uploads

If you allow users to upload images to your site, you may be opening up your site to malicious use. This is because particularly cunning hackers can hide commands to do just about anything in the data that makes up the image.

For a great example of an infected image file, see the following page:

http://www.eicar.org/86-0-Intended-use.html

Note that you’ll need to disable your anti-virus if you want to create this file on your computer.

If you allow users to upload this file, it will produce some interesting results!

To prevent the vulnerability (at least when using C#) you simply need to try and load the image data into an Image object. This will throw an exception if the image contains anything nasty. Here is an example:

var image = Image.FromStream(imageDataStream);
using (var memoryStream = new MemoryStream())
{
    image.Save(memoryStream, ImageFormat.Png);
}

Note that you’ll need to load the uploaded data into a stream (imageDataStream in the example above) to get this to work.

Simple trick to help prevent click-jacking

Click-jacking is a technique used by hackers to get you to interact with websites without your consent or knowledge. An example would be to get you to click a Like button on a Facebook page without you realising it. The trick with click-jacking is misdirection. One way to achieve this is to show you a friendly looking site, preferably a site which you are already familiar with, but where the buttons perform actions other than those you expect. This can be achieved by hosting the friendly site in an iframe, with fake buttons floated over the top of the real buttons. These fake buttons can then perform unintended actions, such as submitting a Like to Facebook.

You can test whether your website is vulnerable using the following HTML snippet:

<html>
<head>
<title>Clickjack test page</title>
</head>
<body>
<p>Website is vulnerable to clickjacking!</p>
<iframe src="http://www.mywebsite.com" width="1000" height="1000"></iframe>
</body>
</html>

If you can see the text Website is vulnerable to clickjacking! when you look at the code in a browser your site could be used as a target for click-jacking.

A simple way to prevent this kind of attack is to include the following JavaScript at the top of your site:

<script type="text/javascript">
// Prevent the page from being vulnerable to click jacking
if (self == top) {
var theBody = document.getElementsByTagName('body')[0];
theBody.style.display = "block";
} else {
top.location = self.location;
}
</script>

The JavaScript checks to see if the containing page is the top-most entity in the HTML served to the user. If not, then the whole browser is re-directed to the address of the containing page, thus removing the iframe.

Obviously this won’t work if users don’t have JavaScript enabled, but for the sake of a few lines of trivial JavaScript it’s worth adding this to your site.