Wednesday, June 23, 2010

File Encryption in Ruby with OpenSSL

Disclaimer: I am far from an expert on cryptography so there are probably a million things wrong with this. Any constructive criticism is always welcome... but be gentle ;-)
REQUIRED: Ruby 1.9 *because of new hash syntax.

For Starters....

I am often torn when programming between using libraries and following the DIY approach. On one hand you can accomplish a lot in a short time by including libraries that have the functionality you need and just bridging the gaps, but I am often left with the feeling of some loss-of-control and bloat. Obviously some library usage is inevitable, but I do like to take a minimalist approach to including external libraries because the management burden of updating libraries, the effort to make various libraries play together and because moving library-heavy applications between platforms can be... interesting. Most of all I like the learning that takes place when building it yourself. It was for this reason that I decided to see if I could effectively utilize the OpenSSL library that is typically part of a Ruby installation to create my own higher-level encryption/decryption class. Before we dive in I would like to layout the details of the application.

Password Management App


I have for years maintained a PGP encrypted file for my account passwords. I had the desire to centralize this and make it more web accessible. In order to do this I wanted assurance that the data was not only encrypted on the wire via SSL, but also at rest. I looked at various Ruby PGP/GPG libraries, but all of the ones I surveyed were wrappers around the GPG binary itself which didn't make it very web host friendly ( I planned on hosting it on Heroku with a Couchdb backend on Cloudant). That was when I decided to look at OpenSSL for file encryption since it is typically part of a Ruby installation and was available on Heroku. I also had the need for public-key encryption so a password store could be shared between two or more people (I share some accounts with my partner).

Take One... RSA

I thought I could simply use RSA encryption to meet all my needs and at first it worked quite well. Creating keys was straight forward and encryption/decryption worked well. One caveat is that the string passed to the FileEncryptor#encrypt_string method cannot be larger than the key size + PKCS padding size (11 bytes). So for a 128 byte (1024 bit) key the string should only be 117 bytes (see String#size).



This seemed to work quite well until I remembered that I needed to have the encrypted data be readable by multiple parties. I then had to change my approach and decided to use a symmetric AES key that is then shared between the multiple parties and each person encrypts the key with their RSA public key.

Take Two... RSA + AES

In order to accomplish secure use of the AES key we start out the same way as the previous solution by creating a RSA key. It will only be used to secure AES keys that we use to protect certain data stores. So if I have a data store I want to share with my brother I encrypt the data store with an AES key then I encrypt that key with my public RSA key and my brother's public RSA key. Now both he and I can access anything in that store and the AES key is still secure from prying eyes. Here is an overview of the steps taken before I post the code.
  1. Create a RSA key-pair
  2. Create an AES key to encrypt the data store
  3. Encrypt the AES key with your RSA public key
  4. To give access to the data store encrypt the AES key with the person's public RSA key send them back the cipher-text.

And here is the class I wrote to make this work:



Below is a demonstration of the use of this class to accomplish the needs I had for my password protector application. I am aware that this class needs some refactoring and it's a bit annoying to have to pass the AES key file to the methods, but logically it is working the way I had hoped.



fe = Encryptor.new('mysecret')

aesfile = 'aeskey.sec'

fe.gen_aes_key(aesfile)

etxt = fe.aes_encrypt('this is a test', aesfile)

txt = fe.aes_decrypt(etxt, aesfile)

pub_key = < Assume I got someone's public RSA Key somehow >

fe.give_aes_key(aesfile, pub_key)

Like I mentioned, this code needs to be cleaned up a bit, but it gives me all of the functionality I need to build my application and it doesn't require any external libraries except OpenSSL and YAML which are typically standard in any Ruby installation.

Hopefully someone finds this post useful and if you have any additions, corrections, criticisms please post them below. Feedback is always welcome.

Cheers!

2 comments:

  1. Hi! You post is very usefull for me. But now I have one issue related to heroku. Maybe you know the answer? I posted it into heroku google group too.

    Can anyone give me an advice about how to organize the open public key
    storage on heroku. Where should I put these keys? In active record
    (write my own storage based for example on certstore.rb in sample
    directory in ruby) or put the keys on amazon s3?
    Or more common solutions exist, gems or examples?

    In general, user signs a file with signature and encrypt it for both
    rsa and aes keys and send it to server (heroku). I have to decrypt it
    and check the signature.

    ReplyDelete
  2. I'm glad you found the post of use. I never know if anyone actually cares so it's nice to hear the feedback ;-)

    As for your issue, I don't think there is really a set pattern for what you're trying to accomplish. I would just choose a method works for you and that you're comfortable with. That said, I will conceded what I would do in your situation. Since Heroku has the Cloundant add-on and PEM Base64 formatted keys seemingly fit well into a document-oriented db I would probably go that route. That said, that's not the only way to do it or even the best, but that's what I would feel most comfortable doing. So I think the moral to my story is just be creative and do what works best for you.

    If you feel comfortable posting a link to your solution here once your come up with it that would be great. If not good luck!

    Cheers!

    -zen

    ReplyDelete