Dynamic Keys for Strongbox

1 minute read

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