README.md in attr_keyring-0.3.1 vs README.md in attr_keyring-0.4.0
- old
+ new
@@ -1,6 +1,6 @@
-![attr_keyring: Simple encryption-at-rest with key rotation support for ActiveRecord.](https://raw.githubusercontent.com/fnando/attr_keyring/master/attr_keyring.png)
+![attr_keyring: Simple encryption-at-rest with key rotation support for Ruby.](https://raw.githubusercontent.com/fnando/attr_keyring/master/attr_keyring.png)
<p align="center">
<a href="https://travis-ci.org/fnando/attr_keyring"><img src="https://travis-ci.org/fnando/attr_keyring.svg" alt="Travis-CI"></a>
<a href="https://codeclimate.com/github/fnando/attr_keyring"><img src="https://codeclimate.com/github/fnando/attr_keyring/badges/gpa.svg" alt="Code Climate"></a>
<a href="https://codeclimate.com/github/fnando/attr_keyring/coverage"><img src="https://codeclimate.com/github/fnando/attr_keyring/badges/coverage.svg" alt="Test Coverage"></a>
@@ -28,49 +28,46 @@
$ gem install attr_keyring
## Usage
-### Model Configuration
+### Configuration
-#### Migration
+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 columns 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).
-The following example shows how to create a column `twitter_oauth_token` without the digest, and another one called `social_security_number` with the digest column.
+As far as model configuration goes, they're pretty similar, as you can see below:
-```ruby
-class CreateUsers < ActiveRecord::Migration[5.2]
- def change
- create_table :users do |t|
- t.citext :email, null: false
- t.timestamps
+#### ActiveRecord
- # The following columns are used for encryption.
- t.binary :encrypted_twitter_oauth_token
- t.binary :encrypted_social_security_number
- t.text :social_security_number_digest
- t.integer :keyring_id
- end
+From Rails 5+, ActiveRecord models now inherit from `ApplicationRecord` instead. This is how you set it up:
- add_index :users, :email, unique: true
- add_index :users, :social_security_number_digest, unique: true
- end
+```ruby
+class ApplicationRecord < ActiveRecord::Base
+ self.abstract_class = true
+ include AttrKeyring.active_record
end
```
-#### ActiveRecord
+#### Sequel
-```ruby
-class ApplicationRecord < ActiveRecord::Base
- self.abstract_class = true
+Sequel doesn't have an abstract model class (but it could), so you can set up the model class directly like the following:
- include AttrKeyring
+```ruby
+class User < Sequel::Model
+ include AttrKeyring.sequel
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.
+
+```ruby
class User < ApplicationRecord
attr_keyring ENV["USER_KEYRING"]
attr_encrypt :twitter_oauth_token, :social_security_number
end
```
@@ -79,36 +76,23 @@
You can use the model as you would normally do.
```ruby
user = User.create(
- email: "john@example.com",
- twitter_oauth_token: "TOKEN",
- social_security_number: "SSN"
+ email: "john@example.com"
)
-user.twitter_oauth_token
-#=> TOKEN
+user.email
+#=> john@example.com
user.keyring_id
#=> 1
-user.encrypted_twitter_oauth_token
-#=> "\xF0\xFD\xE3\x98\x98\xBBBp\xCCV45\x17\xA8\xF2r\x99\xC8W\xB2i\xD0;\xC2>7[\xF0R\xAC\x00s\x8F\x82QW{\x0F\x01\x88\x86\x03w\x0E\xCBJ\xC6q"
+user.encrypted_email
+#=> WG8Epo0ABz0Z1X5gX7kttc98w9Ei59B5uXGK36Zin9G0VqbxX3naOWOm4RI6w6Uu
```
-You may want to store a Base64 version instead of binary data (e.g. `jsonb` column with `store_accessor`). In this case, you may specify the option `encode: true`.
-
-```ruby
-class User < ApplicationRecord
- store_accessor :meta, :twitter_oauth_token
-
- attr_keyring ENV["USER_KEYRING"]
- attr_encrypt :twitter_oauth_token, encode: true
-end
-```
-
### Encryption
By default, AES-128-CBC is the algorithm used for encryption. This algorithm uses 16 bytes keys. Using 16-bytes of random data base64-encoded is the recommended way. You can easily generate keys by using the following command:
```console
@@ -124,15 +108,15 @@
attr_keyring ENV["USER_KEYRING"],
encryptor: AttrKeyring::Encryptor::AES256CBC
end
```
-To generate keys, use `bs=32` instead.
+#### Key size
-```console
-$ dd if=/dev/urandom bs=32 count=1 2>/dev/null | openssl base64
-```
+- `aes-128-cbc`: 16 bytes.
+- `aes-192-cbc`: 24 bytes.
+- `aes-256-cbc`: 32 bytes.
#### 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.
@@ -171,19 +155,19 @@
### Lookup
One tricky aspect of encryption is looking up records by known secret. E.g.,
```ruby
-User.where(twitter_oauth_token: "241F596D-79FF-4C08-921A-A19E533B4F52")
+User.where(email: "john@example.com")
```
is trivial with plain text fields, but impossible with the model defined as above.
-If add 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.
+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.
```ruby
-User.where(twitter_oauth_token_digest: Digest::SHA1.hexdigest("241F596D-79FF-4C08-921A-A19E533B4F52"))
+User.where(email: Digest::SHA1.hexdigest("john@example.com"))
```
### 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.
@@ -200,9 +184,39 @@
```ruby
User.where(keyring_id: 1234).find_each do |user|
user.keyring_rotate!
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.
+
+```ruby
+require "keyring"
+
+keyring = Keyring.new("1" => "QSXyoiRDPoJmfkJUZ4hJeQ==")
+
+encrypted, keyring_id, digest = keyring.encrypt("super secret")
+
+puts encrypted
+#=> encrypted: +mOWmIWKMV01nCm076OBnzgPGhWAZqNs8Etaad/0s3I=
+
+puts keyring_id
+#=> 1
+
+puts digest
+#=> e24fe0dea7f9abe8cbb192702578715079689a3e
+
+decrypted = keyring.decrypt(encrypted, keyring_id)
+
+puts decrypted
+#=> 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.