Dynamic Keys for Strongbox
Previously, Strongbox, my gem for using Public Key Encryption with ActiveRecord, allowed only one key pair for encrypting all of the records for a given ActiveRecord model. I’ve had a number of requests to make it possible to dynamically choose the keys on a per record basic and version 0.6.0 adds this feature.
The values of :public_key
, :private_key
, and :key_pair
can be in one of the following formats:
A string containing path to a file. This is the default interpretation of a string.
encrypt_with_public_key :secret,
:key_pair => File.join(RAILS_ROOT,'config','keypair.pem')
A string contanting a key in PEM format, needs to match this the regex /^-+BEGIN .* KEY-+$/
encrypt_with_public_key :secret,
:key_pair =>
"-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,BFE700E4DDA4C434\n\nDfZD7FKM4zLJdb[...]"
A symbol naming a method to call. Can return any of the other valid key formats.
encrypt_with_public_key :secret,
:public_key => :public_key_method,
:private_key => :private_key_attribute
An instance of OpenSSL::PKey::RSA
. Must be unlocked to be used as the private key.
KEY_PAIR = OpenSSL::PKey::RSA.new(key,password)
# [...]
encrypt_with_public_key :secret,
:key_pair => KEY_PAIR
Using this, you can automatically create per record public keys:
require 'openssl'
# Assumes the migration contains:
# t.string :description
# t.binary :secret
# that you are collecting a password to encrypt the private key,
# and that the secret is small
class User < ActiveRecord::Base
attr_accessor :password
encrypt_with_public_key :secret,
:key_pair => :key_pair
def after_initialize
rsa_key = OpenSSL::PKey::RSA.new(2048)
cipher = OpenSSL::Cipher::Cipher.new('des3')
key_pair = rsa_key.to_pem(cipher,self.password) + rsa_key.public_key.to_pem
end
end
Important Caveat -
Currently, Strongbox encrypts the attribute as soon as it’s assigned (this will change in version 1.0). The means that the public key must be available before the attribute is assigned, hence the use of after_initialize
to generate the key pair. Even so, this will fail if you do something like:
user = User.new(params[:user])
because the attributes are set before after_initialize
is called.
Instead, use something like:
user = User.new
user.password = params[:password]
user.attributes = params[:user]
Version 1.0 will allow you to control when the encryption occurs, making this less of an issue.
Comments