-
Notifications
You must be signed in to change notification settings - Fork 427
Migrating your data from v1.x to v3.x format
While the insecure_mode
described above will keep you operating short term, what you really want is to move forward to the newer, more secure settings that v3.x makes available. However, if you have a production system that has data you care about saved away using 1.x encryption, you need get that data converted.
After updating the gem as described above, we are going to do that using the following logical steps:
- Rename the existing DB columns that have encrypted data (so we have access to it during the conversion)
- Add the new columns needed for v3.x format encryption to the database
- Update your model so it can access the old format data and the new format data
- Go through all the rows decrypting the old data and re-encrypting / re-saving it in v3.x format
- Verify that the old and new data is the same
- Update your model to remove the temporary code that accessed the old data
- Remove the columns in the DB that hold the old, outdated encrypted values
For the purposes of this example, let's assume that we have User
that have account_number
which is encrypted. So that means, that in the users
table, there is a column encrypted_account_number
.
and in the models/user.rb
we have
attr_encrypted :account_number, :key => 'ReallyLongKeyWithLotsOfRandomness'
generate a migration to create the save the old values and add new columns
rails g migration add_iv_to_users encrypted_account_name_iv:string
using that as a starting point, change the def change
to a two methods def up
and def down
so you can explicitly control the order in which things are changed.
class AddIvToUsers < ActiveRecord::Migration
def up
rename_column :users, :encrypted_account_number, :encrypted_account_number_old
add_column :users, :encrypted_account_number, :string
add_column :users, :encrypted_account_number_iv, :string
end
def down
remove_column :users, :encrypted_account_number, :string
remove_column :users, :encrypted_account_number_iv, :string
rename_column :users, :encrypted_account_number_old, :encrypted_account_number
end
end
We need to change the model so that it uses insecure mode to access the _old
value and the new v3.x settings for the new columns.
in models/user.rb
change things so:
attr_encrypted :account_number, :key => 'ReallyLongKeyWithLotsOfRandomness'
attr_encrypted :account_number_old, :key => 'ReallyLongKeyWithLotsOfRandomness', algorithm: 'aes-256-cbc', mode: :single_iv_and_salt, insecure_mode: true
Test that you can access the old v1.x values in the console
$ rails console
2.3.0 :001 > u = User.first
2.3.0 :002 > u.account_number_old
=> "123456"
Test that you can save encrypted values in the new format
2.3.0 :003 > u.account_number = u.account_number_old
=> "123456"
2.3.0 :004 > u.save
=> true
2.3.0 :005 > u.reload
2.3.0 :006 > u.account_number
=> "123456"
create a new file lib/tasks/encrypt.rake
with 2 tasks
namespace :encrypt do
desc "Migrate user info to new encryption format"
task :user => :environment do
updated_count = 0
error_count = 0
User.find_each do |user|
user.account_number = user.account_number_old
if user.save
updated_count += 1
else
puts "** Error while updating ID: #{user.id}"
error_count += 1
end
end
puts "Update complete. Total rows in table: #{User.count}"
puts "Updated #{updated_count} record(s). Hit #{error_count} error(s)."
end
desc "Verify update was successful"
task :verify_user => :environment do
verified_count = 0
error_count = 0
User.find_each do |user|
if user.account_number == user.account_number_old
verified_count += 1
else
puts "** Error values did not match for ID: #{user.id}"
error_count += 1
end
end
if verified_count == User.count
puts "All #{verified_count} row(s) match."
else
puts "ERROR -- #{error_count} row(s) do not match"
end
end
end
Run this code against a copy of your database & make sure everything runs without error. Then you'll need to run this task against your production data.
Once you've successfully updated your production data, you should remove the line:
attr_encrypted :account_number_old, :key => 'ReallyLongKeyWithLotsOfRandomness', algorithm: 'aes-256-cbc', mode: :single_iv_and_salt, insecure_mode: true
from your models/user.rb
Create a new migration
rails g migration remove_old_encrypted_values_from_user
this should look something like:
class RemoveOldEncryptedValuesFromUser < ActiveRecord::Migration
def up
remove_column :users, :encrypted_account_number_old, :string
end
def down
add_column :users, :encrypted_account_number_old, :string
puts "**** WARNING: you've just rolled and you have a column for the old value, but it's empty. ***"
end
end