Encrypts/Decrypts, with key rotation, attributes on classes, using RbNaCl.
This library was developed for- and extracted from HireFire.
The documentation can be found on RubyDoc.
- Ruby 2.5+
- RbNaCl 7.0+
Ensure that you've installed libsodium, then add the gem to your Gemfile and run
gem "attr_encrypter", "~> 1"
class Account include :: # Defines the token accessor that seamlessly encrypts and decrypts # data stored in @token_digest. # # Note: This won't define a @token instance variable, as state is # managed by the token_digest accessor using @token_digest. # # Note: You can add multiple attributes in a single attr_encrypter call. # e.g. `attr_encrypter ENV["KEYCHAIN"], :attr_a, :attr_b, :attr_c` # attr_encrypter ENV["KEYCHAIN"], :token # Defines the token_digest accessor for storing and retrieving encrypted data. # You don't directly use this accessor. Both read and write operations should # always go through the token/token= methods provided by the attr_encrypter method. # # Consider making this accessor private. We'll leave it public for this demo. # attr_accessor :token_digest end
KEYCHAIN environment variable must contain one or more keys. To generate a key, use the following function.
Place the generated key in the
KEYCHAIN environment variable.
The key consists of two segments,
<version>.<secret>. The initial key has a version of 1. Versions determine which secrets will be used to encrypt and decrypt data.
With the key in place you'll be able to store and retrieve data using the dynamically defined
token accessor. Data assigned with
token= will automatically be encrypted and stored with
account = Account.new account.token = "my_secret_token" account.token_digest # => "1.yCZGH0QEk8+OPT0ad29wVmCkvr/7NXZjZtxu23v2j9HKfehndH0qv9MsF4ME\n" account.token # => "my_secret_token" account.instance_variable_get("@token") # => nil account.instance_variable_get("@token_digest") # => "1.yCZGH0QEk8+OPT0ad29wVmCkvr/7NXZjZtxu23v2j9HKfehndH0qv9MsF4ME\n"
@token_digest value, like the keychain, also consists of two segments,
<version>.<data>. The version matches the key version used to encrypt the data. This way, whenever data is accessed via
token, it knows which secret to use to decrypt the data.
To clear the token, simply assign
nil to it.
account.token = nil account.token_digest # => nil
This is a general purpose library, and while its only hard dependency is rbnacl (libsodium), it was designed to work seamlessly with Active Record.
class User < ActiveRecord::Base include :: attr_encrypter ENV["KEYCHAIN"], :token end
This assumes that you have a users table in the database, containing a column named
token_digest of type text.
You'll want to rotate your keys at some point.
First, add a second key to the keychain (
ENV["KEYCHAIN"] in this case).
To generate version 2 you can again use the following function, but this time using the version argument.
::. 2 # or 3, 4, 5...
The keychain format allows any kind of whitespace to delimit keys.
ENV["KEYCHAIN"] = "<version>.<secret> <version>.<secret> <version>.<secret>"
ENV["KEYCHAIN"] = <<-EOS 1.c1dbb0dd094d4f40916c9cc0d8c974151949f105c499366b2acd9da76de7e5e9 2.3054563b2d80ae13c6e115405d6263e70be004f81eb3982f4a61ff9e9821e7d4 EOS
Then simply reassign the token to re-encrypt the token with key version 2.
User.find_each do |user| user.token = user.token user.save end
user.token (reader) decrypts the existing encrypted token from
token_digest back to its unencrypted form using key version 1. It then uses the highest key version in the keychain, version 2, to re-encrypt the token with
user.token= which overwrites the key version 1-encrypted token in
token_digest with the key version 2-encrypted token. We then persist the key version 2-encrypted token to the database.
- The order of the keys in the keychain is irrelevant.
- The key with the highest version in the keychain is always used for encryption.
- The key version used to encrypt data is stored along with the encrypted data (
- The version of the encrypted data determines which key's secret to use in order to decrypt data.
- Keys can be safely removed from the keychain when none of the encrypted data requires it for decryption.
- It's recommended that you increment key versions by 1 when generating a new key.
Bug reports and pull requests are welcome on GitHub at https://github.com/mrrooijen/attr_encrypter.
First, install libsodium on your machine, and then install the remaining development dependencies:
To open an interactive console:
$ bundle console
To run the tests:
$ bundle exec rake
To view the code coverage (generated after each test run):
$ open coverage/index.html
To run the local documentation server:
$ bundle exec rake doc
To build a gem:
$ bundle exec rake build
To build and install a gem on your local machine:
$ bundle exec rake install
For a list of available tasks:
$ bundle exec rake --tasks