Tuesday, June 29, 2010

Ruby and OpenSSL with SSH keys

One of the challenges of dealing with encryption is the management of keys. We have a SSH key, a PGP key (probably multiple), maybe a OpenSSL cert, perhaps another cert somewhere for S/MIME and maybe a ton more for various other purposes. This post is basically an addendum to my last File Encryption in Ruby with OpenSSL, but I will step through the code using an SSH key instead. This will minimize the amount of keys you have to manage and it will utilize a key that many people have readily available to them without the need to create yet another encryption key.

SSH Keys

On the chance that you do not have an ssh key (you really should you know) or you are using DSA keys you will need to create an RSA key with ssh-keygen.

home$ ssh-keygen -t rsa -b 2048 -N 'mypass'

Loading the SSH key in Ruby

Loading the SSH/RSA key is relatively straight forward. Instead of pointing to our generated RSA key from the last posting we will simply point it to the path of our SSH/RSA key and load it with the password we supplied during the ssh-keygen step. I will demonstrate in an irb session.

# Require the OpenSSL library
irb> require 'openssl'
# Load the SSH key from the default location (Linux/UNIX)
irb> key = OpenSSL::PKey::RSA.new(File.read("#{ENV['HOME']}/.ssh/id_rsa"),'mypass')

The generated public SSH/RSA key is not stored in PEM format so you cannot consume it with OpenSSL, but you can generate it from the private key and store it alongside the SSH formatted one.

# Write the PEM encoded public key alongside the SSH public key
irb> File.open("#{ENV['HOME']}/.ssh/id_rsa.pub.pem") do |f|
irb>     f.write(key.public_key.to_pem)
irb> end

So there you go. You can now load your SSH key for encryption/decryption purposes with the OpenSSL library and you won't have to worry about managing keys. All else remains the same and you can do everything that was done in my previous OpenSSL/Ruby posting.


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'


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.