Strongbox provides Public Key Encryption for ActiveRecord. By using a public key
sensitive information can be encrypted and stored automatically. Once stored, the
private key and password are required to access the information.
Because the largest amount of data that can practically be encrypted with a 2048-bit
public key is 245 bytes, by default Strongbox uses a two layer approach. First it
encrypts the attribute using symmetric encryption with a randomly generated key and
initialization vector (IV) (which can just be thought of as a second key), then it
encrypts those with the public key.
Strongbox stores the encrypted attribute in a database column by the same name, i.e.
if you tell Strongbox to encrypt “secret” then it will be store in “secret” in the
database, just as the unencrypted attribute would have been. If symmetric encryption
is used (the default) two additional columns “secret_key” and “secret_iv” are needed
as well.
The attribute is automatically and immediately encrypted simply by setting it:
user.secret = “Shhhhhhh…”and decrypted by calling the “decrypt” method with the private key password.
plain_text = user.secret.decrypt ‘letmein’The password for the private key can be empty if you are protecting the key through a
different method. In that case, provide the empty string ’’ to decrypt() in order to
decrypt the data.
This fork of Strongbox is also able to perform symmetric-only encryption. If you
do this, be sure that you’re protecting the symmetric key. One application of this is
for encrypting data to a private password known by the user. See below for more options.
In your model:
class User < ActiveRecord::Base
encrypt_with_public_key :secret,
:key_pair => File.join(RAILS_ROOT,'config','keypair.pem')
end
In your migrations:
class AddSecretColumnsToUser < ActiveRecord::Migration
def self.up
add_column :users, :secret, :binary
add_column :users, :secret_key, :binary
add_column :users, :secret_iv, :binary
end
def self.down
remove_column :users, :secret
remove_column :users, :secret_key
remove_column :users, :secret_iv
end
end
Generate a key pair:
(Choose a strong password.)
openssl genrsa -des3 -out config/private.pem 2048
openssl rsa -in config/private.pem -out config/public.pem -outform PEM -pubout
cat config/private.pem config/public.pem >> config/keypair.pem
In your views and forms you don’t need to do anything special to encrypt data. To
decrypt call:
user.secret.decrypt 'password'
In config/environment.rb:
config.gem "strongbox"
encrypt_with_public_key sets up the attribute it’s called on for automatic
encryption. It’s simplest form is:
class User < ActiveRecord::Base
encrypt_with_public_key :secret,
:key_pair => File.join(RAILS_ROOT,'config','keypair.pem')
end
Which will encrypt the attribute “secret”. The attribute will be encrypted using
symmetric encryption with an automatically generated key and IV encrypted using the
public key. This requires three columns in the database “secret”, “secret_key”, and
“secret_iv” (see below).
Options to encrypt_with_public_key are:
:public_key – Path to the public key file. Overrides :keypair.
:private_key – Path to the private key file. Overrides :keypair.
:keypair – Path to a file containing both the public and private keys.
:symmetric :always/:never – Encrypt the date using symmetric encryption. The public
key is used to encrypt an automatically generated key and IV. This allows for large
amounts of data to be encrypted. The size of data that can be encrypted directly with
the public is limit to key size (in bytes) – 11. So a 2048 key can encrypt 245 bytes. Defaults to :always
:symmetric_cipher – Cipher to use for symmetric encryption. Defaults to ‘aes-256-cbc’. Other ciphers support by OpenSSL may be used.
:base64 true/false – Use Base64 encoding to convert encrypted data to text. Use when
binary save data storage is not available. Defaults to false
:padding – Method used to pad data encrypted with the public key. Defaults to
RSA_PKCS1_PADDING. The default should be fine unless you are dealing with legacy
data.
:encrypt_iv true/false – Default is true for backward compatibility, but it is not
necessary to encrypt the initialization vector to maintain security. For first-time
installations, you might choose to set this to false, which cuts the encryption and
decryption overhead approximately in half. There is currently no method for
migrating encrypted iv’s to clear-text iv’s, but you could add a second set of columns
with a different configuration and write a script that decrypts values from one encrypted
column and stores them into to the second decrypted column.
For example, encrypting a small attribute, providing only the public key for extra
security, and Base64 encoding the encrypted data:
class User < ActiveRecord::Base
validates_length_of :pin_code, :is => 4
encrypt_with_public_key :pin_code,
:symmetric => :never,
:base64 => true,
:public_key => File.join(RAILS_ROOT,'config','public.pem')
end
Generate a key pair:
openssl genrsa -des3 -out config/private.pem 2048
Generating RSA private key, 2048 bit long modulus
......+++
.+++
e is 65537 (0x10001)
Enter pass phrase for config/private.pem:
Verifying - Enter pass phrase for config/private.pem:
and extract the the public key:
openssl rsa -in config/private.pem -out config/public.pem -outform PEM -pubout
Enter pass phrase for config/private.pem:
writing RSA key
If you are going to leave the private key installed it’s easiest to create a single
key pair file:
cat config/private.pem config/public.pem >> config/keypair.pem
Or, for added security, store the private key file else where, leaving only the public key.
In it’s default configuration Strongbox requires three columns, one the encrypted
data, one for the encrypted symmetric key, and one for the encrypted symmetric IV. If
symmetric encryption is disabled then only the columns for the data being encrypted
is needed.
If your underlying database allows, use the binary column type. If you must store
your data in text format be sure to enable Base64 encoding and to use the text
column type. If you use a string column and encrypt anything greater than 186
bytes (245 bytes if you don’t enable Base64 encoding) your data will be lost.
Because Strongbox immediately encrypts the data as you assign it into the model,
the amount of validation that can be done is minimal, being limited to
validates_size_of and validates_presence_of.
If you require additional validation for your encrypted columns, this should be done
before assigning into encrypted attributes. That, or you might want to contribute a
patch that delays the encryption step until right before save.
Asymmetric encryption is generally far preferred over symmetric-only encryption.
Being able to physically separate and protect the private decryption key from the
public encryption key creates a level of potential security unmatchable with
symmetric encryption, which is reversible using the single encryption key.
However, symmetric keys can be useful in certain circumstances – for example the
combined symmetric/asymmetric encryption provided by default with
encrypt_with_public_key. Other advanced examples include:
- Encrypt data to a PIN code known only to the user (retrieve it only when they re-type their PIN)
- Encrypt a PIN code with public key, then use the PIN to symmetrically encrypt other data, so
that it can be retrieved directly by the user but not by an attacker. Combined with pubkey
encryption for the same data, much flexibility is gained with a minimum of risk exposure – though
it comes with a tradeoff: increased code complexity.
If you don’t understand the caveats above, please re-read them. Then, if you are
prepared to do symmetric encryption, use encrypt_with_symmetric_key:
class User < ActiveRecord::Base
validates_length_of :pin_code, :is => 4
encrypt_with_symmetric_key :some_data, :encrypt_iv => false, :key_proc => :pin_key
attr_accessor :pin_key
end
All options are the same as for pubkey encryption, except that no keys may be specified.
Additionally, :encrypt_iv must be set to false, and the :key_proc must be specified as a
symbol referring to a function which will return the symmetric key.
Two examples should demonstrate the use of a function that returns symmetric keys. They
correspond to the two use cases mentioned above.
Encrypting with a key not stored on the server: This is easily implemented by creating
an attr_accessor on the model having the encrypted field.
class User < ActiveRecord::Base
encrypt_with_symmetric_key :some_data, :encrypt_iv => false, :key_proc => :pin_key
attr_accessor :pin_key
end
In your controller, put the key into the model prior to storing or retrieving encrypted data.
In the second example, we store the user’s PIN, encrypted asymmetrically. To encrypt
data to the PIN code, we go inside the security perimeter where we have the private key;
decrypt the PIN, then set it, possibly using the attr_accessor method, prior to encrypting
the symmetric data.
Decryption for user-facing content is done the same way as in the first example.
Another method of implementing the :key_proc is as follows:
attr_writer :pin_key
def pin_key
@pin_key || self.pin.decrypt('password')
end
If you don’t encrypt your data, then an attacker only needs to steal that data to get
your secrets.
If encrypt your data using symmetric encrypts and a stored key, then the attacker
needs the data and the key stored on the server.
If you use public key encryption, the attacker needs the data, the private key, and
the password. This means the attacker has to sniff the password somehow, so that’s
what you need to protect against.
Spike Ilacqua
Randy Harmon
Strongbox’s implementation drew inspiration from Thoughtbot’s Paperclip gem
http://www.thoughtbot.com/projects/paperclip