Transparent ActiveRecord encryption
Provides transparent encryption for ActiveRecord. It is encryption agnostic.
You can guard your data with any encryption algorithm you want. All you need
is a simple class that does 3 things.
initialize
encrypt
method that returns the encrypted stringdecrypt
method that returns the plaintextNote: Any options defined using crypt_keeper
will be passed to new
as a
hash.
You can see an example here.
The options available were either too complicated under the hood or had weird
edge cases that made the library hard to use. I wanted to write something
simple that just works.
class MyModel < ActiveRecord::Base
crypt_keeper :field, :other_field, encryptor: :active_support, key: 'super_good_password', salt: 'salt'
end
model = MyModel.new(field: 'sometext')
model.save! #=> Your data is now encrypted
model.field #=> 'sometext'
It works with all persistences methods: update_attributes
, create
, save
etc.
Note: update_attribute
is deprecated in ActiveRecord 3.2.7. It is superseded
by update_column
which skips all validations, callbacks.
That means using update_column
will not perform any encryption. This is
expected behavior, and has its use cases. An example would be migrating from
one type of encryption to another. Using update_column
would allow you to
update the content without going through the current encryptor.
For encryptors requiring secret keys/salts, you can generate them via
rails secret
:
rails secret
ef209071bd76143a75eda57b99425da63ce6c2d44581d652aa4302a90dcd7d7e99cbc22091c01a19f93ea484f40b142612f9bf76de8eb2d51ff9b3eb02a7782c
Or manually (this is the same implementation that Rails uses):
ruby -e "require 'securerandom'; puts SecureRandom.hex(64)"
These values should be stored outside of your application repository for added
security. For example, one could use dotenv and reference them as ENV
variables.
# .env
CRYPT_KEEPER_KEY=75d942f3d3b3492772e0330f717eaf5e689673ea8b983475ef8f6551f6e99d280cd89972706e46b48240cc01c4d0f7df5ffa3524566b789d147ed04cc4ea4eab
CRYPT_KEEPER_SALT=b16a153e99a5db616a861ea5a6febc64d8a758c4aef3b8c8fc6675ac9daf03f7965f16e8b4b2bdfd28ff65f5203afb8102b8f41c514c3667bb3512015b1e77e8
Then in your model:
class MyModel < ActiveRecord::Base
crypt_keeper :field, :other_field, encryptor: :active_support, key: ENV["CRYPT_KEEPER_KEY"], salt: ENV["CRYPT_KEEPER_SALT"]
end
You can force an encoding on the plaintext before encryption and after decryption by using the encoding
option. This is useful when dealing with multibyte strings:
class MyModel < ActiveRecord::Base
crypt_keeper :field, :other_field, encryptor: :active_support, key: 'super_good_password', salt: 'salt', encoding: 'UTF-8'
end
model = MyModel.new(field: 'Tromsø')
model.save! #=> Your data is now encrypted
model.field #=> 'Tromsø'
model.field.encoding #=> #<Encoding:UTF-8>
If you are working with an existing table you would like to encrypt, you must use the MyExistingModel.encrypt_table!
class method.
class MyExistingModel < ActiveRecord::Base
crypt_keeper :field, :other_field, encryptor: :active_support, key: 'super_good_password', salt: 'salt'
end
MyExistingModel.encrypt_table!
Running encrypt_table!
will encrypt all rows in the database using the encryption method specificed by the crypt_keeper
line in your model.
There are four supported encryptors: active_support
, mysql_aes_new
, postgres_pgp
, postgres_pgp_public_key
.
pgcrypto
PostgresSQL extension:CREATE EXTENSION IF NOT EXISTS pgcrypto
:pgcrypto_options
. E.g. `crypt_keeper :field, encryptor: :postgres_pgp, pgcrypto_options: ‘compress-level=9’pgcrypto
PostgresSQL extension:CREATE EXTENSION IF NOT EXISTS pgcrypto
Searching ciphertext is a complex problem that varies depending on the encryption algorithm you choose. All of the bundled providers include search support, but they have some caveats.
ActiveSupport::MessageEncryptor
Mysql AES
PostgresSQL PGP
Model.search_by_plaintext(:field, 'searchstring')
# With a scope
Model.where(something: 'blah').search_by_plaintext(:field, 'searchstring')
Creating your own encryptor is easy. All you have to do is create a class
under the CryptKeeper::Provider
namespace, and inherit from the Base
encryptor,
like this:
module CryptKeeper
module Provider
class MyEncryptor < Base
def initialize(options = {})
end
def encrypt(value)
end
def decrypt(value)
end
end
end
end
Just require your code and setup your model to use it. Just pass the class name
as a string or an underscored symbol
class MyModel < ActiveRecord::Base
crypt_keeper :field, :other_field, encryptor: :my_encryptor, key: 'super_good_password'
end
CryptKeeper 2.0 removes the AES encryptor due to security issues in the
underlying AES gem. If you were previously using the aes_new
encryptor, you
will need to follow these instructions to reencrypt your data.
The general migration path is as follows:
TableName.encrypt_table!
TableName.first
In case you experience problems, the rollback procedure is as follows:
CryptKeeper has been tested against ActiveRecord 4.2 and 5.0 using Ruby
2.1.10, 2.2.5 and 2.3.1.
ActiveRecord 4.2 is supported starting with v0.19.0.
ActiveRecord 5.0 is supported starting with v0.23.0.
Add this line to your application’s Gemfile:
gem 'crypt_keeper'
And then execute:
$ bundle
Or install it yourself as:
$ gem install crypt_keeper
git checkout -b my-new-feature
)git commit -am 'Add some feature'
)git push origin my-new-feature
)