Friday, 14 December 2012

Signed Encryption

I think I am one of those people who are happy often with being 95% there. If the system is pretty solid or pretty unlikely to fail then I am happy.
Take something like encryption. Sadly massively underused despite being easy enough to setup in web applications but it would be easy for me to think, "I have encryption on this data, therefore it is safe". Now ignoring the fact that you need to manage decryption keys and use a decent algorithm etc, what is your gut feel? Is encrypted data safe or not? Our language is casual and we can say that no-one can tamper with it but that is not strictly true. In a decent system, no-one other than the authorised people are allowed to decrypt/read the data but that is not the same as saying it can't be tampered with.
An example I heard of before was, imagine that you have a table with employees salaries in it, you notice that the salary column contains encrypted data and you can see your bosses salary, e.g. ABCD and your own salary EFGH. Now although you don't know what the figure for your boss actually is (and you may or may not know what your figure is) what you DO know is that your boss gets paid more than you do so if you were able to update the database so that your salary was also ABCD then you could reasonably imagine that your next pay packet would be larger than the last one! This is nothing to do with encryption but relates to the fact that if the data is encrypted in a deterministic way (which bare algorithms do) then I know I can carry out this attack. In this instance, what I need to do is include some check data in the encrypted column so that I can ensure this copy-in-place attack will fail. This could be anything from some hash derived from the user id to something more complex but breaks the attack.
There is, however, another way in which encrypted data can be 'broken' which may or may not be thwarted by the above solution. Although I don't know what the encrypted data means, if I change the encrypted data usually, it will still decrypt but into something else. This may or may not cause a problem but it can potentially break a system. Imagine I managed to tamper with my encrypted salary, sure I might change it from 25,000 to (DHk)(897 but I might also be able to change it to 100,000 by trial and error! The way round it? Some kind of signature. Again, you could perform your own home brew way of checking the integrity of the data but you can also use an industry standard technique to create a digital signature. The reason for this is that a home brew technique might only work some of the time and you really need it to be bomb-proof.
I ended up stumbling across a library that worked under .Net alongside the MS implementations of cryptography but which provided this mechanism. It can either use Galois/Counter Mode or Counter with CBC/MAC. This article is here. Fortunately, it was a reasonably straight-forward download and install and fortunately, the implementation was fairly straight-forward except for a few important points:
  1. Since a tag is generated by the encryption process, you must store this and set it in the decryption process otherwise you are scuppered. You cannot for instance wire in the encryption now and worry about the tag later.
  2. You need to use the correct types in SQL server to store the tag so that it can be restored later in the correct format. Since it is a byte array, it is easy to get confused with string representations. For this reason, I used binary(16) in SQL Server for the tag and cast the dataReader result to byte[] to retain the correct format and structure.
PLEASE NOTE: The following is code modified from OWASP ESAPI to make it work with the AuthenitcatedAesCng but I am not providing any guarantees that this is the best or only way to implement encryption.
The following is the encryption method. In my case, tag is an output parameter since the method itself returns the ciphertext. This tag is saved into the database in another column to the cipher text and obviously retrieved during decryption.
using (var symmetricAlgorithm = new AuthenticatedAesCng())
{
    symmetricAlgorithm.CngMode = CngChainingMode.Gcm;
    symmetricAlgorithm.GenerateIV();
    byte[] iv = symmetricAlgorithm.IV;
    symmetricAlgorithm.AuthenticatedData = Encoding.GetEncoding(encoding).GetBytes("nprvaewuybnppnawn");

    using (MemoryStream msEncrypt = new MemoryStream())
    {
        using (IAuthenticatedCryptoTransform encryptor = symmetricAlgorithm.CreateAuthenticatedEncryptor(secretKey, iv))
        {
            using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
            {
                try
                {
                    Encoding textConverter = Encoding.GetEncoding(encoding);
                    byte[] plaintextBytes = textConverter.GetBytes(plaintext);
                    csEncrypt.Write(plaintextBytes, 0, plaintextBytes.Length);
                    csEncrypt.FlushFinalBlock();

                    byte[] encryptedBytes = msEncrypt.ToArray();
                    byte[] encryptedBytesPlusIv = Combine(iv, encryptedBytes);
                    tag = encryptor.GetTag();
                    return Convert.ToBase64String(encryptedBytesPlusIv);
                }
                finally
                {
                    symmetricAlgorithm.Clear();
                }
            }
        }
    }
}

And the decryption:
using (var symmetricAlgorithm = new AuthenticatedAesCng())
{
    symmetricAlgorithm.CngMode = CngChainingMode.Gcm;
    byte[] ciphertextBytes = Convert.FromBase64String(ciphertext);

    int ivLength = symmetricAlgorithm.IV.Length;
    byte[] ivBytes = new byte[ivLength];
    Array.Copy(ciphertextBytes, ivBytes, ivLength);

    int onlyCiphertextLength = ciphertextBytes.Length - ivLength;
    byte[] onlyCiphertextBytes = new byte[onlyCiphertextLength];
    Array.Copy(ciphertextBytes, ivLength, onlyCiphertextBytes, 0, onlyCiphertextLength);

    symmetricAlgorithm.AuthenticatedData = Encoding.GetEncoding(encoding).GetBytes("nprvaewuybnppnawn");
    symmetricAlgorithm.Tag = tag;

    using (MemoryStream msDecrypt = new MemoryStream(onlyCiphertextBytes))
    {
        using (ICryptoTransform decryptor = symmetricAlgorithm.CreateDecryptor(secretKey, ivBytes))
        {
            using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
            {
                try
                {
                    byte[] plaintextBytes = new byte[onlyCiphertextLength];
                    int decryptedBytes = csDecrypt.Read(plaintextBytes, 0, onlyCiphertextLength);
                    Encoding textConverter = Encoding.GetEncoding(encoding);
                    return textConverter.GetString(plaintextBytes, 0, decryptedBytes);
                }
                finally
                {
                    symmetricAlgorithm.Clear();
                }
            }
        }
    }
}
The thing to realise is that if someone wanted to tamper with the encrypted data, they would have to tamper with the signature to make it match. The chances of being able to do this are so low as to not present a major risk. Notice the random text I use as a data salt which adds secrecy to the process and makes it harder for someone to try and duplicate.
Post a Comment