Transcryptor provides utility functions to help migrate records encrypted with attr_encrypted
from one encryption configuration to another.
Add this line to your application’s Gemfile:
gem 'transcryptor', github: 'riboseinc/transcryptor'
And then execute:
bundle
Or install it yourself as:
gem install transcryptor
You have a User
with ssn
attribute which needs to be re-encrypted. User
has next configuration:
class User < ActiveRecord::Base
attr_encrypted :ssn, key: ->(u) { ENV['USER_SSN_ENC_KEY'] },
mode: :per_attribute_iv_and_salt,
algorithm: 'aes-256-gcm'
end
To re-ecrypt this column with new key (ENV['NEW_USER_SSN_ENC_KEY']
), algorithm (aes-256-cbc
) and mode (per_attribute_iv
) you can easily define migration.
class ReEncryptUserSsn < ActiveRecord::Migration
def up
re_encrypt_column :users, :ssn,
{ # old configuration of attr_encrypted for :ssn column
key: ->(u) { ENV['USER_SSN_ENC_KEY'] },
mode: :per_attribute_iv_and_salt,
algorithm: 'aes-256-gcm'
},
{ # new configuration of attr_encrypted for :ssn column
key: ->(u) { ENV['NEW_USER_SSN_ENC_KEY'] },
mode: :per_attribute_iv,
algorithm: 'aes-256-cbc'
}
end
end
Run bundle exec rake db:migrate
. Done!
See ActiveRecord::Migration for initial data. And then DataMapper
migration is:
require 'dm-migrations/migration_runner'
migration 1, :re_encrypt_user_ssn do
up do
re_encrypt_column :users, :ssn,
{ # old configuration of attr_encrypted for :ssn column
key: ->(u) { ENV['USER_SSN_ENC_KEY'] },
mode: :per_attribute_iv_and_salt,
algorithm: 'aes-256-gcm'
},
{ # new configuration of attr_encrypted for :ssn column
key: ->(u) { ENV['NEW_USER_SSN_ENC_KEY'] },
mode: :per_attribute_iv,
algorithm: 'aes-256-cbc'
}
end
end
migrate_up!
Taking ActiveRecord
as an example (the following applies to DataMapper
as well):
Specify an SQL string (or a proc
returning one) inside the where
option to
limit the range on which transcryption is run:
class ReEncryptUserSsn < ActiveRecord::Migration
def up
re_encrypt_column :users, :ssn,
{ # old configuration of attr_encrypted for :ssn column
key: ->(u) { ENV['USER_SSN_ENC_KEY'] },
mode: :per_attribute_iv_and_salt,
algorithm: 'aes-256-gcm'
},
{ # new configuration of attr_encrypted for :ssn column
key: ->(u) { ENV['NEW_USER_SSN_ENC_KEY'] },
mode: :per_attribute_iv,
algorithm: 'aes-256-cbc'
},
where: -> { "encrypted_ssn IS NOT NULL" }
end
end
Create new columns in database.
class Migration1 < ActiveRecord::Migration
def up
add_column :users, :encrypted_new_ssn
add_column :users, :encrypted_new_ssn_iv
end
end
Add attr_encrypted
for new columns, include Transcryptor::ActiveRecord::ZeroDowntime
, and transcryptor_migrate :old_attribute_name, :new_attribute_name
.
class User < ActiveRecord::Base
include Transcryptor::ActiveRecord::ZeroDowntime
attr_encrypted :ssn, key: '1qwe1qwe1qwe1qwe1qwe1qwe1qwe1qwe', algorithm: 'aes-256-cbc'
attr_encrypted :new_ssn, key: '2asd2asd2asd2asd2asd2asd2asd2asd', algorithm: 'aes-256-gcm'
transcryptor_migrate :ssn, :new_ssn
end
Create rake
task for zero downtime migration. Or any other way you prefer.
namespace :zero_downtime
desc 'migrate attr_encrypted for User'
task user: :environment do
User.find_each { |user| user.save! }
end
end
Remove & rename columns in database after finishing of rake
task.
class Migration2 < ActiveRecord::Migration
def up
remove_column :users, :encrypted_ssn
remove_column :users, :encrypted_ssn_iv
rename_column :users, :encrypted_new_ssn, :encrypted_ssn
rename_column :users, :encrypted_new_ssn_iv, :encrypted_ssn_iv
end
end
Move attr_encrypted
configuration to original attribute and remove all migration code.
class User < ActiveRecord::Base
attr_encrypted :ssn, key: '2asd2asd2asd2asd2asd2asd2asd2asd', algorithm: 'aes-256-gcm'
end
Done!
Default options for old and new configuration are absolutelly the same as it is defined in attr_encrypted
gem.
{
prefix: 'encrypted_',
suffix: '',
if: true,
unless: false,
encode: true, # changed from false to true as transcryptor works with DB rows
encode_iv: true,
encode_salt: true,
default_encoding: 'm',
marshal: false,
marshaler: Marshal,
dump_method: 'dump',
load_method: 'load',
encryptor: Encryptor,
encrypt_method: 'encrypt',
decrypt_method: 'decrypt',
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm',
}
You can use transcryptor to migrate your encrypted fields in zero downtime using versioned_fields gem.
Just include Transcryptor::Decoder
module and use decode_with_previous_settings
to decode your fields with specific settings:
# db/migrate_versioned_fields/user/ssn.rb
VersionedFields::Migration.draw_for(User, :ssn) do
config.include Transcryptor::Decoder
version 1
version(2) do
decode_with_previous_settings(migration,
key: '67c3800d1572d9d964a6ff3bd821ed02',
algorithm: 'aes-256-gcm'
)
end
version(3) do |migration|
decode_with_previous_settings(migration,
key: '94dd7e2c40a3d51a8dd0a9137356a18e',
algorithm: 'RC2-64-CBC'
)
end
After checking out the repo, run bin/setup
to install dependencies. Then, run
rake spec
to run the tests. You can also run bin/console
for an interactive
prompt that will allow you to experiment.
Bug reports and pull requests are welcome on GitHub at https://github.com/riboseinc/transcryptor. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
The gem is available as open source under the terms of the MIT License.