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'
endTo 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
endRun 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
endCreate new columns in database.
class Migration1 < ActiveRecord::Migration
def up
add_column :users, :encrypted_new_ssn
add_column :users, :encrypted_new_ssn_iv
end
endAdd 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
endCreate 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
endRemove & 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
endMove attr_encrypted configuration to original attribute and remove all migration code.
class User < ActiveRecord::Base
attr_encrypted :ssn, key: '2asd2asd2asd2asd2asd2asd2asd2asd', algorithm: 'aes-256-gcm'
endDone!
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'
)
endAfter 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.