I wrote and maintain (though not as attentively as I’d like) a Ruby Gem, Strongbox, which adds Public-key Encryption support to Rails’ ActiveRecord. Simply put, Public-key Encryption is a form of encryption with two password, one to encrypt data and another to decrypt it. This is handy for web applications, any visitor can encrypt data using encryption password (the public key). However, if an attacker gains access to the server and steals the data and the app’s code, they still can’t decrypt the data because they lack the decrypt password (the private key).
The problem is you’re doing it wrong. OK, not all of you. However, I get a fair number of support questions around storing the private key and it’s password on the server. I’ve even see a few tutorials showing how to use Strongbox this way.
If you’re going to do this, don’t use Strongbox. No, I’m not going to get all righteous about how you protect your data, I’m talking about being inefficient.
First, a bit of threat analysis:
- If you don’t encrypt you data and someone gets you data in any way, then your secrets are exposed.
- If you encrypt your data and store the decryption password on the server and your data is stolen, say through an SQL injection attack, your secrets are safe. However, if your server is breached, the password can be stolen with your data and thus your secrets.
- If you encrypt your data using public-key and do not store the unlocked private-key on the server, if your server is breached your secrets are still safe. (Though if the attacker hangs out on your server they could modify your code to capture new secrets.)
If the second option works for you, then you should use Symmetric-key Encryption as it’s much faster and easier than public-key encryption. Symmetric-key Encryption is what people think of when they think of encryption, there’s just one password which both encrypts and decrypts the data.
In Ruby, Symmetric-key encryption is provided by
(in old Ruby versions it’s
First you need to choose an encryption algorithm. You can see the full list with:
The simple choice is
aes-256-cbc. The Advanced Encryption Standard (AES)
is a open encryption standard that is well studied and well
understood. It’s what the U.S. Government uses.
That U.S. Government connection makes some people leery of AES. However, it was developed by two Belgian cryptographers, the winner of a very public challenge, and the professionals believe it a good choice. However, this is security, don’t take my word for it. Do the research and especially look at discussions around AES vs Blowfish and Twofish
In the string
aes-256-cbc the 256 is the key (password) size in bits. AES
supports 128, 192 and 256 bit keys. Unless you are running on a device
without much CPU, there’s no reason to not use 256 bits (32 bytes).
cbc stands for
Cipher-block chaining. Ciphers
can only encrypt data in small chunks, called blocks, which are glued
together to form the whole of the cipher text. CBC is the glue. To
ensure that blocks containing the same data are encrypted differently,
some randomness is need. For block cipher this randomness is the
Initialization vector (IV). A
good, detail explanation of block ciphers and the IV can be found
Back to the code. To use OpenSSL::Cipher, we need to instantiate a cipher and provide it with a key and an IV.
1 2 3 4 5
#decrypt) sets the mode we are working in. You must
call it before calling
cipher instance will not return the key or the IV once they are
set, which it why we’re saving them in the
If you create your own key, make sure it’s 256 bits long, short keys
OpenSSL::Cipher::CipherError: key length too short. Keys
longer than 256 bits are truncated and work, but are likely bite you
Once the cipher is configured we can encrypt:
#update encrypts the text passed to it. You can call it more that once
if you want to encrypt text in chunks to avoid file slurping:
1 2 3 4 5
#final flushes the cipher object. The data is encrypted in fixed
size blocks. If the data passed to
#update is not exactly divisible
by the block size, some will be left in the buffer. Calling
pads out the remaining data to the block size, encrypts, and returns
#final a second time, or calling
#update after calling
#final will return garbage, so don’t.
1 2 3 4 5 6
If something goes wrong, you’ll get an unhelpful (but secure)
OpenSSL::Cipher::CipherError when calling
#final. It’s going to be
one two things: you have the wrong key or you forgot to call
when encrypting. If you instead get random garbage, then you have the
How you use this in a Rails app?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
ivneed to be a binary columns or your data will be lost. Alternatively, Base64 encode then first.
This assumes you set the key in the environment, which may not be the best approach.
#clear_secret!is a convenience method to bypass the encryption in the setter remove the encrypted data.
So, if you’re comfortable with storing your encryption key on your server, save the public-key overhead and skip right to symmetric-key encryption. Leave a comment if you’d like to see this turned into a gem.