项目作者: fnando

项目描述 :
Simple encryption-at-rest with key rotation support for Ruby.
高级语言: Ruby
项目地址: git://github.com/fnando/attr_keyring.git
创建时间: 2018-12-02T01:56:37Z
项目社区:https://github.com/fnando/attr_keyring

开源协议:MIT License

下载


attr_keyring: Simple encryption-at-rest with key rotation support for Ruby.


Tests
Code Climate
Gem
Gem

N.B.: attr_keyring is not for encrypting passwords—for that, you should use
something like bcrypt. It’s meant for
encrypting sensitive data you will need to access in plain text (e.g. storing
OAuth token from users). Passwords do not fall in that category.

This library is heavily inspired by
attr_vault, and can read
encrypted messages if you encode them in base64 (e.g.
Base64.strict_encode64(encrypted_by_attr_vault)).

Installation

Add this line to your application’s Gemfile:

  1. gem "attr_keyring"

And then execute:

  1. $ bundle

Or install it yourself as:

  1. $ gem install attr_keyring

Usage

Basic usage

  1. gem "attr_keyring"
  2. require "keyring"
  3. keyring = Keyring.new(
  4. {"1" => "uDiMcWVNTuz//naQ88sOcN+E40CyBRGzGTT7OkoBS6M="},
  5. digest_salt: "<custom salt>"
  6. )
  7. # STEP 1: Encrypt message using latest encryption key.
  8. encrypted, keyring_id, digest = keyring.encrypt("super secret")
  9. puts "🔒 #{encrypted}"
  10. puts "🔑 #{keyring_id}"
  11. puts "🔎 #{digest}"
  12. # STEP 2: Decrypted message using encryption key defined by keyring id.
  13. decrypted = keyring.decrypt(encrypted, keyring_id)
  14. puts "✉️ #{decrypted}"

Change encryption algorithm

You can choose between AES-128-CBC, AES-192-CBC and AES-256-CBC. By
default, AES-128-CBC will be used.

To specify the encryption algorithm, set the encryption option. The following
example uses AES-256-CBC.

  1. keyring = Keyring.new(
  2. "1" => "uDiMcWVNTuz//naQ88sOcN+E40CyBRGzGTT7OkoBS6M=",
  3. encryptor: Keyring::Encryptor::AES::AES256CBC,
  4. digest_salt: "<custom salt>"
  5. )

Configuration

As far as database schema goes:

  1. You’ll need a column to track the key that was used for encryption; by
    default it’s called keyring_id.
  2. Every encrypted column must follow the name encrypted_<column name>.
  3. Optionally, you can also have a <column name>_digest to help with searching
    (see Lookup section below).

As far as model configuration goes, they’re pretty similar, as you can see
below:

ActiveRecord

From Rails 5+, ActiveRecord models now inherit from ApplicationRecord instead.
This is how you set it up:

  1. class ApplicationRecord < ActiveRecord::Base
  2. self.abstract_class = true
  3. include AttrKeyring.active_record
  4. end

Sequel

Sequel doesn’t have an abstract model class (but it could), so you can set up
the model class directly like the following:

  1. class User < Sequel::Model
  2. include AttrKeyring.sequel
  3. end

Defining encrypted attributes

To set up your model, you have to define the keyring (set of encryption keys)
and the attributes that will be encrypted. Both ActiveRecord and Sequel have the
same API, so the examples below work for both ORMs.

  1. class User < ApplicationRecord
  2. attr_keyring ENV["USER_KEYRING"],
  3. digest_salt: "<custom salt>"
  4. attr_encrypt :twitter_oauth_token, :social_security_number
  5. end

The code above will encrypt your columns with the current key. If you’re
updating a record, then the column will be migrated to the latest key available.

You can use the model as you would normally do.

  1. user = User.create(
  2. email: "john@example.com"
  3. )
  4. user.email
  5. #=> john@example.com
  6. user.keyring_id
  7. #=> 1
  8. user.encrypted_email
  9. #=> WG8Epo0ABz0Z1X5gX7kttc98w9Ei59B5uXGK36Zin9G0VqbxX3naOWOm4RI6w6Uu

If you want to store a hash, you can use the encoder: option.

  1. class User < ApplicationRecord
  2. attr_keyring ENV["USER_KEYRING"],
  3. digest_salt: "<custom salt>"
  4. attr_encrypt :data, encoder: JSON
  5. end

An encoder is just an object that responds to the methods dump(data) and
parse(data), just like the JSON interface. Alternatively, you can use
AttrKeyring::Encoders::JSON, which returns hashes with symbolized keys.

Encryption

By default, AES-128-CBC is the algorithm used for encryption. This algorithm
uses 16 bytes keys, but you’re required to use a key that’s double the size
because half of that keys will be used to generate the HMAC. The first 16 bytes
will be used as the encryption key, and the last 16 bytes will be used to
generate the HMAC.

Using random data base64-encoded is the recommended way. You can easily generate
keys by using the following command:

  1. $ dd if=/dev/urandom bs=32 count=1 2>/dev/null | openssl base64 -A
  2. qUjOJFgZsZbTICsN0TMkKqUvSgObYxnkHDsazTqE5tM=

Include the result of this command in the value section of the key description
in the keyring. Half this key is used for encryption, and half for the HMAC.

Key size

The key size depends on the algorithm being used. The key size should be double
the size as half of it is used for HMAC computation.

  • aes-128-cbc: 16 bytes (encryption) + 16 bytes (HMAC).
  • aes-192-cbc: 24 bytes (encryption) + 24 bytes (HMAC).
  • aes-256-cbc: 32 bytes (encryption) + 32 bytes (HMAC).

About the encrypted message

Initialization vectors (IV) should be unpredictable and unique; ideally, they
will be cryptographically random. They do not have to be secret: IVs are
typically just added to ciphertext messages unencrypted. It may sound
contradictory that something has to be unpredictable and unique, but does not
have to be secret; it is important to remember that an attacker must not be able
to predict ahead of time what a given IV will be.

With that in mind, attr_keyring uses
base64(hmac(unencrypted iv + encrypted message) + unencrypted iv + encrypted message)
as the final message. If you’re planning to migrate from other encryption
mechanisms or read encrypted values from the database without using
attr_keyring, make sure you account for this. The HMAC is 32-bytes long and
the IV is 16-bytes long.

Keyring

Keys are managed through a keyring—a short JSON document describing your
encryption keys. The keyring must be a JSON object mapping numeric ids of the
keys to the key values. A keyring must have at least one key. For example:

  1. {
  2. "1": "uDiMcWVNTuz//naQ88sOcN+E40CyBRGzGTT7OkoBS6M=",
  3. "2": "VN8UXRVMNbIh9FWEFVde0q7GUA1SGOie1+FgAKlNYHc="
  4. }

The id is used to track which key encrypted which piece of data; a key with a
larger id is assumed to be newer. The value is the actual bytes of the
encryption key.

Dynamically loading keyring

If you’re using Rails 5.2+, you can use credentials to define your keyring. Your
credentials.yml must be define like the following:

  1. ---
  2. user_keyring:
  3. "1": "QSXyoiRDPoJmfkJUZ4hJeQ=="
  4. "2": "r6AfOeilPDJomFsiOXLdfQ=="

Then you can setup your model by using
attr_keyring Rails.application.credentials.user_keyring.

Other possibilities (e.g. the keyring file is provided by configuration
management):

  • attr_keyring YAML.load_file(keyring_file), digest_salt: "<custom salt>"
  • attr_keyring JSON.parse(File.read(keyring_file)), digest_salt: "<custom salt>".

Lookup

One tricky aspect of encryption is looking up records by known secret. E.g.,

  1. User.where(email: "john@example.com")

is trivial with plain text fields, but impossible with the model defined as
above.

If a column <attribute>_digest exists, then a SHA1 digest from the value will
be saved. This will allow you to lookup by that value instead and add unique
indexes. You don’t have to use a hashing salt, but it’s highly recommended; this
way you can avoid leaking your users’ info via rainbow tables.

  1. User.where(email: User.keyring.digest("john@example.com")).first

Key Rotation

Because attr_keyring uses a keyring, with access to multiple keys at once, key
rotation is fairly straightforward: if you add a key to the keyring with a
higher id than any other key, that key will automatically be used for encryption
when records are either created or updated. Any keys that are no longer in use
can be safely removed from the keyring.

To check if an existing key with id 123 is still in use, run:

  1. # For a large dataset, you may want to index the `keyring_id` column.
  2. User.where(keyring_id: 123).empty?

You may not want to wait for records to be updated (e.g. key leaking). In that
case, you can rollout a key rotation:

  1. User.where(keyring_id: 1234).find_each do |user|
  2. user.keyring_rotate!
  3. end

What if I don’t use ActiveRecord/Sequel?

You can also leverage the encryption mechanism of attr_keyring totally
decoupled from ActiveRecord/Sequel. First, make sure you load keyring instead.
Then you can create a keyring to encrypt/decrypt strings, without even touching
the database.

  1. require "keyring"
  2. keyring = Keyring.new(
  3. {"1" => "QSXyoiRDPoJmfkJUZ4hJeQ=="},
  4. digest_salt: "<custom salt>"
  5. )
  6. encrypted, keyring_id, digest = keyring.encrypt("super secret")
  7. puts encrypted
  8. #=> encrypted: +mOWmIWKMV01nCm076OBnzgPGhWAZqNs8Etaad/0s3I=
  9. puts keyring_id
  10. #=> 1
  11. puts digest
  12. #=> e24fe0dea7f9abe8cbb192702578715079689a3e
  13. decrypted = keyring.decrypt(encrypted, keyring_id)
  14. puts decrypted
  15. #=> super secret

Exchange data with Node.js

If you use Node.js, you may be interested in
https://github.com/fnando/keyring-node, which is able to read and write
messages using the same format.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run
rake test to run the tests. You can also run bin/console for an interactive
prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To
release a new version, update the version number in version.rb, and then run
bundle exec rake release, which will create a git tag for the version, push
git commits and tags, and push the .gem file to
rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at
https://github.com/fnando/attr_keyring. 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.

License

The gem is available as open source under the terms of the
MIT License.

Icon

Icon made by Icongeek26 from
Flaticon is licensed by Creative Commons BY 3.0.

Code of Conduct

Everyone interacting in the attr_keyring project’s codebases, issue trackers,
chat rooms and mailing lists is expected to follow the
code of conduct.