Mon 11 Jun 2007
Encrypting Sensitive Data with Ruby (on Rails)
Posted by Spike under Ruby, Ruby on Rails, security
[10] Comments
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.
10 Responses to “ Encrypting Sensitive Data with Ruby (on Rails) ”
Trackbacks & Pingbacks:
-
[...] Encrypting Sensitive Data with Ruby (on Rails) [...]
Hey Great Post. I found it after wading through lots of garbage. I even gave you props on my site. Thank you so much for the info!
http://blainegarrett.com/blog/2007/09/15/storing-social-security-numbers-in-rails/
THANKS! JUSTO LO QUE NECESITABA! JUST WHAT I NEED IT!
Thanks for the great post, always nice to see actual snippets.
Readers should be aware, however, that using public key (asymmetric) encryption is much much slower than symmetric encryption. Many (most?) apps just need to encrypt/decrypt sensitive data in the app before storing in the DB / sending to the user; in this case, symmetric would be preferred for performance reasons.
Very nice, simple. works perfect!
Cheers
No question, symmetric encryption would be faster, but it means the password which can decrypt your sensitive data is exposed somewhere in you code/config. If you use a public key approach, even if an attacher walked away with your server they still wouldn’t have the information they needed to access your sensitive data.
In practice, the difference in speed between asymmetric and symmetric encryption of small amounts of data is pretty negligible; in simple tests I’ve run it’s measured in ten thousands of seconds.
Thanks a lot
… just when I was looking for one.
Hey Spike,
I have a question regarding best practices on keeping the private key secure? Like if you open up the private key and use it to decrypt the data when you need it, how different is that than having a password hardcoded in your code? How do you keep it seperated?
@Tony The private key is only decrypted in memory while your using it. So to decrypt your information someone would have to get , your data, your key, and intercept the password. Possible for sure, but less likely than someone just breaking in and copying the database and code.
Depending on your application, it might be possible to split up the functionally. You need only the public key to encrypt data, so that could be all you have on the public facing server. You could then have an internal, well protected machine where things can be decrypted using the private key.
Hey, thanks for this! A really simple step-by-step on something that’s often overly complex. (Especially if you try to sit down and read the man pages.) I went ahead and factored what I used in my project here at PGP into a small, simple rails plugin, based entirely on your example. http://github.com/dukejones/pgp Cheers!