Encrypting Sensitive Data with Ruby (on Rails)

2 minute read

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.

Comments