Ruby


I’ve given a number of examples of using Public-key cryptography in blog posts and in the Strongbox documentation, but I’ve always generated the RSA key pair using the openssl command line tool, i.e.

% openssl genrsa -des3 -out private.pem 2048
Generating RSA private key, 2048 bit long modulus
......+++
.+++
e is 65537 (0x10001)
Enter pass phrase for private.pem:
Verifying - Enter pass phrase for private.pem:
% openssl rsa -in private.pem -out public.pem -outform PEM -pubout
% cat private.pem public.pem > key_pair.pem

This is fine if you want to generate a key pair once, but what if you want to do it on the fly? The Ruby OpenSSL library has support for generating key pairs:

require ‘openssl’

rsa_key = OpenSSL::PKey::RSA.new(2048)

2048 is the key size, and a good value to use for it.

What’s not obvious is how to encrypt the private key.   You don’t have to encrypt it, but, if you don’t, anyone who gets a hold of the key can decrypt your data.  Using an unencrypted private key gives you one layer of security (something you have – the key), encrypting it gives you an additional layer (something you know – the password).

To encrypt the private key you need a Cipher object:

cipher =  OpenSSL::Cipher::Cipher.new('des3')

Then, using the Cipher object, you convert the the key_pair to PEM format:

private_key = rsa_key.to_pem(cipher,'password')
public_key = rsa_key.public_key.to_pem
key_pair = private_key + public_key

The resulting PEM strings be saved and then later fed to OpenSSL::PKey::RSA.new() or used with Strongbox.

I recently came across John Nunemaker’s take on using “unless” in Ruby and thought I’d share my two cents.

The key to making your code more readable with “unless” to is is to think semantics, not syntax. In spoken languages if there is more that one way to say something, there will be subtle differences in meaning. In English “unless” does not mean the same as “if not”, instead it indicates an exception or unlikely condition. For example, compare:

Unless it rains, we will go to the park.

to

If it’s not raining, we will go to the park.

The first sentence suggests that rain is possible, but not likely, the second that it is likely to be raining. To get the most out “unless” in your code, use it the same way.

John gives this as example of code that’s very readable with “unless”:

raise InvalidFormat unless AllowedFormats.include?(format)

Simple, elegant, and anyone reading it will understand that we expect the format to be allowed. In the unlikely case that the format falls outside of what we expect we raise an exception. Where as with “if”:

raise InvalidFormat if !AllowedFormats.include?(format)

is just a little bit ugly and says less about what is expected.

In his post, John argues against using “if” with “else” but I disagree. Again, I think in makes for more readable code when you are testing for an unexpected condition. Consider code that checks for an inactive user:

unless (@user.inactive?)
   #stuff
else
   redirect_to take_of_you_hoser_url
end

Reading the code, our expected course of action, doing stuff for the user, comes first, followed by the unlikely case that an inactive user is trying to do stuff. You could of course write:

if (@user.active?)

and there’s nothing wrong with that. However, I feel that using “unless” makes it clear what is expected.

In short, if you are using “unless” as a simple macro for “if !()”, then you shouldn’t be using it at all. However, “unless” is another tool which, when used with care, can make your code clearer.

Previously I wrote about how to use public key encryption to automatically encrypt data using Ruby (and thus Rails). Because this method can encrypt data without a password, it’s very useful for securing information received from a form, without the person entering the from having to do anything special. However public key encryption has limits; the amount of data you can encrypt with this method is limited by the key size you use, and, that after a point increasing the key size isn’t a practical an option. We solve this problem using a combination of public key encryption and symmetric-key encryption.

First a little theory; and when I say “a little” I mean it. Under the hood encryption is a headache inducing branch of mathematics. If you want to the literal truth, there is plenty of good reading out there.

Symmetric-key cryptography is what people tend to think of when, and if, they think of encryption. A password is used to encrypt some information, and that same password must be entered to retrieve the information. Under the hood is an algorithm or cipher, which simply put, is a mathematical function that transforms the data into something obscure and then back again. For our purpose we need a block ciphers.
A block cipher takes a small, typically 128 or 256 bit, chunk of data and encrypts it. There are many, but in this example we’ll use the Advanced Encryption Standard which is the de facto standard.

(There also exist stream ciphers which work on streams of data, encrypting a phone call for example, but that trade security for speed and can be difficult to use correctly.)

We also need a little glue. Because block ciphers operate on small chunks of data, they need to be applied again and again. However give the same input data the cipher will always produce the same encrypted output; any redundancies in the input will be exposed as redundancies in the output and make in vulnerable to a number of attacks. To avoid this we use a mode of operation called Cipher Block Chaining. CBC using data from one block to further obfuscate the data in the next, effectively hiding any redundancies.

While simple and secure, using symmetric-key cryptography can be problematic; everyone needs to know the password to encrypt data and everyone who has the password (or as the pros say, shared secret key) can decrypt data. This works well if you small set of people who need to know the password and a secure way to distribute it (over drinks in a dark corner of a seedy bar is poetic, if not necessarily secure) but in the case of a web site with hundreds or thousands of people entering data, it’s not practical.

Public-key cryptography can be thought of as Symmetric-key encryption with two passwords, or keys, called a key pair. One, the public key, that encrypts data and another, the private key that decrypts. Because the public key can not be used to decrypt data it encrypts it can be safely given out or installed on a web site, allowing anyone to encrypt data to be sent to the owner of the key. The private key is kept safe and is typically symmetric encrypted with an additional password

The solution is actually quite simple. We generate a random password and use that for the symmetric-key encryption. We then encrypt the random password using the public key, and store both the encrypted password, and encrypted data. When we need to get at the data, we use the private key, and its password to decrypt the random password which, in turn, is used to decrypt the data.

Well, it’s almost that simple. In order to randomize the data, the CBC glue requires a Initialization vector (IV). This article has a good explanation of why, but for our purposes we can just think of it as a second random password we need to encrypt and save.

OK, enough talk, let’s encrypt some text:

# OpenSSL provides both symmetric and public key encryption
require 'openssl'

# Encrypt with 256 bit AES with CBC
cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
cipher.encrypt # We are encypting
# The OpenSSL library will generate random keys and IVs
cipher.key = random_key = cipher.random_key
cipher.iv = random_iv = cipher.random_iv

encrypted_data = cipher.update(plain_data) # Encrypt the data.
encrypted_data << cipher.final

At this point we could just save encrypted_data in the database and it would be well protected. So well in fact that we couldn’t get it back. To do that we’re going to need to save the random password and IV.

Generate a key pair. Be sure to choose a good password as this is the one that will decrypt everything.


% openssl genrsa -des3 -out private.pem 2048
Generating RSA private key, 2048 bit long modulus
......+++
.+++
e is 65537 (0x10001)
Enter pass phrase for private.pem:
Verifying - Enter pass phrase for private.pem:

Now extract the public key:


openssl rsa -in private.pem -out public.pem -outform PEM -pubout
Enter pass phrase for private.pem:
writing RSA key

See my previous article for more details on what we’re doing here.

Now we can use the public key to encrypt the random key and IV:

public_key_file = 'public.pem';

public_key = OpenSSL::PKey::RSA.new(File.read(public_key_file))

encrypted_key = public_key.public_encrypt(random_key)
encrypted_iv = public_key.public_encrypt(random_iv)

Now if we store all three pieces, encrypted_key, encrypted_iv, and encrypted_data we have successfully encrypted our original data.

Of course we’ll want to get that data back, and to do so we reverse the process:

require 'openssl'

private_key_file = 'private.pem';

private_key =
   OpenSSL::PKey::RSA.new(File.read(private_key_file),password)

cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
cipher.decrypt
cipher.key = private_key.private_decrypt(encrypted_key)
cipher.iv = private_key.private_decrypt(encrypted_iv)

decrypted_data = cipher.update(encrypted_data)
decrypted_data << cipher.final

password is the password you used when generating the key-pair.

Now let’s put it all together in an Active Record model:

class Sensitive < ActiveRecord::Base

  attr_accessor :plain_data
  attr_protected :encrypted_data, :encrypted_key, :encrypted_iv
  before_save :encrypt_sensitive

  def decrypt_sensitive(password)
    if self.encrypted_data
      private_key = OpenSSL::PKey::RSA.new(File.read(APP_CONFIG['private_key']),password)
      cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
      cipher.decrypt
      cipher.key = private_key.private_decrypt(self.encrypted_key)
      cipher.iv = private_key.private_decrypt(self.encrypted_iv)

      decrypted_data = cipher.update(self.encrypted_data)
      decrypted_data << cipher.final
    else
      ''
    end
  end

  def clear_sensitive
    self.encrypted_data = self.encrypted_key = self.encrypted_iv = nil
  end

  private

  def encrypt_sensitive
    if !self.plain_data.blank?
      public_key = OpenSSL::PKey::RSA.new(File.read(APP_CONFIG['public_key']))
      cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
      cipher.encrypt
      cipher.key = random_key = cipher.random_key
      cipher.iv = random_iv = cipher.random_iv

      self.encrypted_data = cipher.update(self.plain_data)
      self.encrypted_data << cipher.final

      self.encrypted_key =  public_key.public_encrypt(random_key)
      self.encrypted_iv = public_key.public_encrypt(random_iv)
    end
  end
end

When creating or updating your model you don’t have to do anything, if “plain_data” is present it will be automatically encrypted. When you want to view the plain text you call “@record.decrypt_sensitive(‘passwd’)”; that could be done with a little AJAX that prompts for a password and populates the “plain_data” field. The encrypted data is only updated when “plain_data” is present. This done so that the record can be updated without decrypting (and re-encrypting) encrypted data (handy in an application were not everyone has access to the sensitive data). To actually clear the encrypted data call “@record.clear_sensitive” and then save.

Setting up the APP_CONFIG hash is left as an exercise for the reader.

Clearly, this screams for a plugin; watch this space.

In Encrypting Sensitive Data with Perl I wrote about how to use public key encryption to automatically and securely encrypt information with Perl. This allows you encryption things like credit card numbers, bank routing information, or that winning PowerBall number in a unattended fashion. Typically, you would use this in a situation where a user needs to enter sensitive information into a form which need to be stored in a secure manner. We can do this with Ruby (on Rails) as well, and it’s even easier.

First we need to generate a key pair. This creates two keys, a public key which will only be used to encrypt data, and a private key, which will only be used to decrypt data. The private key is protected by a password know only to us. When it comes to choosing strong passwords, I suggest using Diceware. 2048 is the key size in bits. Bigger is better, but also slower; 2048 is considered a good trade off between speed and encryption strength. We are also limited by this to encrypting as most 2048 bits, more on this below.


% openssl genrsa -des3 -out private.pem 2048
Generating RSA private key, 2048 bit long modulus
......+++
.+++
e is 65537 (0x10001)
Enter pass phrase for private.pem:
Verifying - Enter pass phrase for private.pem:

Then we extract the public key:


openssl rsa -in private.pem -out public.pem -outform PEM -pubout
Enter pass phrase for private.pem:
writing RSA key

Once we have the keys, we can encrypt data using the following:

#!/usr/bin/env ruby

require 'openssl'
require 'base64'

public_key_file = 'public.pem';
string = 'Hello World!';

public_key =
   OpenSSL::PKey::RSA.new(File.read(public_key_file))
encrypted_string =
   Base64.encode64(public_key.public_encrypt(string))

print encrypted_string, "\n"

Simply, public_key_file is path to the file containing the public key, and string is the string to encrypt. We open the public key and then use public_encrypt to encrypt it. Because the encrypted string is binary I have converted to text using Base64. If your are storing the encrypted string in a database that can hold binary data, you could change:

encrypted_string = Base64.encode64(public_key.public_encrypt(string))

to:

encrypted_string = public_key.public_encrypt(string)

Now that we have encrypted data, we’ll want to be able to get it back.

#!/usr/bin/env ruby

require 'openssl'
require 'base64'

private_key_file = 'private.pem';
password = 'boost facile'

encrypted_string = %Q{
qBF3gjF8iKhDh+g+TOvAzBkJA/1d2lD8RUyz2Ol+s1OpLB5aA3RA7EHm0KGL
XaP3upvJ7I5rN1yO9Qat9kyRQu9OMqAUmFvwUaiW/1NPjxnpmcFn9mhkttP9
qfO6iIfyxErUqKIxHYqavyPmivre9eEcXiBdtIK6NJJKG3WmSfIFgpZ6eBWI
wxlZg+x0fI4L2JsODMGx5Khn7CUt0bTkH6HMHwxEG24NbsmrqtC2zn8Hm/87
UyN5ZCDyJ/mtIHAjzPry6vbVPTF0QCR4lZ7uSt/W7JZ0tNgX7eQQwoPCgbqU
/uwRCwww/c407jw7YEE5Lgpx20/jyLXJwvZHxNEcxA==
}

private_key =
  OpenSSL::PKey::RSA.new(File.read(private_key_file),password)

string =
  private_key.private_decrypt(Base64.decode64(encrypted_string))

print string, "\n"

Here private_key_file is path to the file containing the private key, password and encrypted_string is the string to decrypt. In a real application you would not want to hard-code the password, rather you should prompt for it in some way.

Again we are using Base64 to make the encrypted string human readable. If this is not necessary, change:

string = private_key.private_decrypt(Base64.decode64(encrypted_string))

to:

string = private_key.private_decrypt(encrypted_string)

As noted above, you can not use this method to encrypt anything larger than the key size minus 11 bytes of overhead (padding). In this case we have a 2048 bit key which gives 256 – 11 = 245 bytes. The temptation is to increase the key size to accommodate more data, but this quickly become to slow to be useful. The correct way to accomplish this is to use public key encryption to encrypt random password, which, in turn is used to encrypt the data using symmetric-key encryption. I’ll cover this next time.