Document level encryption in RavenDB

You may run into situation where you want to have the RavenDB documents saved encrypted on the disk. A typical scenario for that would be storing credit card information with PCI compliance. Or maybe you have an application that runs RavenDB embedded and stores the data locally on the users computer. In that case you may want to have the documents encrypted, so that no one else can take ravens data folder and open it with another RavenDB instance.

RavenDB has no out-of-the-box implementation of this kind of encryption, but through its extensibility model, it is very easy to implement it on your own. Here we go…

Step 1 – Create a RavenDB plugin

Open up Visual Studio and create a new class library project. Then use NuGet package manager to download the RavenDB package.

Step 2 – Implement AbstractDocumentCodec

RavenDB has a nice extension point for this. It will use the implementations of AbstractDocumentCodec at a very low level, just above loading and saving in its storage engines (either ESENT oder Munin). The base class you need to implement is very simple:

public abstract class AbstractDocumentCodec
{
    public abstract Stream Encode(string key, RavenJObject data, RavenJObject metadata, Stream dataStream);

    public abstract Stream Decode(string key, RavenJObject metadata, Stream dataStream);
}

Basically you can use these two methods for whatever encryption you want. This is a very simple example using a strong TripleDES encryption:

public class DocumentCodec : AbstractDocumentCodec
{
    private const string YourPassword = "super-secret-password";

    public override Stream Encode(string key, RavenJObject data, RavenJObject metadata, Stream dataStream)
    {
        return new CryptoStream(dataStream, GetCryptoProvider(key).CreateEncryptor(), CryptoStreamMode.Write);
    }

    public override Stream Decode(string key, RavenJObject metadata, Stream dataStream)
    {
        return new CryptoStream(dataStream, GetCryptoProvider(key).CreateDecryptor(), CryptoStreamMode.Read);
    }

    private static SymmetricAlgorithm GetCryptoProvider(string key)
    {
        var passwordBytes = new Rfc2898DeriveBytes(YourPassword, GetSaltFromDocumentKey(key));
        return new TripleDESCryptoServiceProvider
        {
            Key = passwordBytes.GetBytes(24),
            IV = passwordBytes.GetBytes(8)
        };
    }

    private static byte[] GetSaltFromDocumentKey(string key)
    {
        return MD5.Create().ComputeHash(Encoding.ASCII.GetBytes(key));
    }
}

Step 3 – Compile and deploy

Now you just need to compile and put the dll into ravens plugin-folder. That’s it.

For the lazy one, check out this tiny RavenCrypt solution on my github.

Subscribe

Subscribe to my e-mail newsletter to receive updates whenever there is a new post.

12 Responses to Document level encryption in RavenDB

  1. Sean Kearon January 26, 2012 at 09:15 #

    A great post and really useful, Daniel.

    I am looking to use Raven in an embedded scenario on workstations. I am wondering what happens if the DLL is removed from the plugin folder? Would this then remove the encryption capabilities? If so, can you register the plugin from inside your application somehow to prevent this happening?

    • Daniel Lang January 26, 2012 at 09:39 #

      Sean, when you remove the dll from the plugin folder, RavenDB will crash because it doesn’t know what to do with the data since it’s encrypted. That’s the whole idea of it, so no one else but the one who has the decryption algorithm (and the key obviously) can use the database.

      What do you mean by “register the plugin from inside your application”?

    • Matt Warren January 26, 2012 at 10:34 #

      You could embedded the encryption plugin dll inside your application as a resource. Then at run time you can write it out to the “plugins” folder before initialising RavenDB.

      The code is something like this:

      using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) {

      Byte[] assemblyData = new Byte[stream.Length];

      stream.Read(assemblyData, 0, assemblyData.Length);

      … //write the stream out to a file in the “Plugins” folder
      }

      I’ve used this scenario before to make deployment easier.

      But is there a reason that you think the plugin would be deleted from the plugins folder in normal operation of your app?

  2. Sean Kearon January 26, 2012 at 12:14 #

    @Daniel – thanks, that makes sense. Or course, they’ll only have access to the encrypted data if the DLL is removed.

    @Matt – that’s a useful technique, thanks for posting that. The scenario I wanted to prevent is removal of the plugin allowing access to the data. This is not a concern, as shown by Daniel above.

    Thanks guys :)

  3. Ayende Rahien January 26, 2012 at 13:20 #

    Sean,
    If you are running embedded, you can register that in code, no need to copy files around.

    • Daniel Lang January 26, 2012 at 13:21 #

      Yes, that’s what I have done in my project at github. Thanks for clarifying that.

  4. HCN January 26, 2012 at 22:27 #

    How can I register that in code using the embeddable database? The github project seems to deal with the “regular” RavenDB, not the embedded version

    • Daniel Lang January 27, 2012 at 08:48 #

      HCN, I changed the test to use EmbeddableDocumentStore. Hope that helps.

      • HCN January 27, 2012 at 13:47 #

        Sweet, thanks dude! I’ve managed to figure it out on my own soon after making the comment – that’s the beauty of open source, a quick glimpse at the source code and there it was the property right in front of my eyes!

  5. cocowalla February 2, 2012 at 09:26 #

    Looking at the code on GitHub, it looks like you are going to use the same initialisation vector every time – this can leak information about the data that was encrypted.

    You should not re-use initialisation vectors, and should instead generate a new one each time using SymmetricAlgorithm.GenerateIV(). Now, you’re going to have to store that IV somewhere – it doesn’t need to be kept secret, so you could just tag it onto the start of the stream during Encode, and read it back during Decode.

    You may also want to consider using AesCryptoServiceProvider instead of RijndaelManaged, as AesCryptoServiceProvider is FIPS compliant (though it is only supported in .NET 3.5+, whereas RijndaelManaged is supported in .NET 2.0).

    • Daniel Lang February 2, 2012 at 10:47 #

      In the original version (which is also the one using the code above) there is a different IV on every document, because both the key and the IV are derived from the password with the document id as salt. The problem with this approach is, that its performance is really bad as someone pointed out on github and provided a pull-request. Although I was aware of this lower security (and we discussed it in the comments of the pull-request) I accepted it because it performs _much_ better.

      Thanks for pointing out the security issues and thanks for the suggestions. Unfortunately, I don’t know how to incorporate them without sucking database performance. Maybe you can do something about that and provide a pull request?

      • cocowalla February 2, 2012 at 14:29 #

        TBH, I’m rather new to RavenDB, and I’d probably just break it :)

        Having said that, the performance killer is going to be the use of Rfc2898DeriveBytes, which is very slow. Actually, it’s *supposed* to be slow, to slow down attacks!

        There is almost zero overhead to create an IV using GenerateIV() (a quick and dirty test shows 10,000 times takes only 19ms on my laptop). If you have a fixed salt (a unique salt per document is overkill anyway), but always generate a unique IV, you get extra security with almost no perf hit.

        Something weird I noticed on the latest version on GitHub is generating the salt using “Guid.NewGuid()” – won’t that mean anything encrypted during once run of the application can’t be decrypted on the next run?

Leave a Reply